Add ComfyUI models documentation and download script
- Complete MODELS.md with detailed model descriptions - Interactive download script for 5+ specialized SDXL models - Updated README with model selection guide - Fix docker-compose volumes path (/root/ComfyUI) - API workflows for OpenWebUI integration Models documented: - RealVisXL (portraits) - Juggernaut XL (versatile) - DreamShaper (art/fantasy) - SDXL Lightning (fast) - Proteus (realism)
This commit is contained in:
@@ -0,0 +1,567 @@
|
||||
<img src="https://r2cdn.perplexity.ai/pplx-full-logo-primary-dark%402x.png" style="height:64px;margin-right:32px"/>
|
||||
|
||||
# CONTEXTE TECHNIQUE
|
||||
|
||||
Je cherche à créer un assistant IA personnel qui utilise Open WebUI comme interface, Ollama pour l'inférence LLM, et AFFiNE comme base de connaissance/mémoire.
|
||||
Infrastructure actuelle :
|
||||
|
||||
1. NAS Synology (192.168.1.25)
|
||||
Open WebUI (container Docker, port 3000)
|
||||
AFFiNE (container Docker, port 3010) avec PostgreSQL + Redis
|
||||
Compose AFFiNE complet fonctionnel (affine, migration, postgres, redis)
|
||||
2. PC Gamer (192.168.1.11)
|
||||
Ollama (port 11434) avec modèles 14B paramètres (Qwen3-VL, Ministral, etc.)
|
||||
Accessible via un WOL proxy (container sur NAS, port 3011) qui réveille le PC à la demande
|
||||
3. WOL Proxy fonctionnel
|
||||
Python/Flask sur NAS (192.168.1.25:3011)
|
||||
Réveille le PC gamer automatiquement via Wake-on-LAN
|
||||
Proxifie ensuite toutes les requêtes vers Ollama de manière transparente
|
||||
Open WebUI configuré avec OLLAMA_BASE_URL=http://wol-proxy:3010
|
||||
CONFIGURATION AFFINE ACTUELLE
|
||||
text
|
||||
services:
|
||||
affine:
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
ports: ['3010:3010']
|
||||
environment:
|
||||
- AFFINE_COPILOT_ENABLED=true
|
||||
- AFFINE_AI_PROVIDER=openai
|
||||
- AFFINE_OPENAI_BASE_URL=http://192.168.1.10:11434 \# À modifier ?
|
||||
- AFFINE_OPENAI_API_KEY=ollama
|
||||
depends_on: [redis, postgres, affine_migration]
|
||||
|
||||
# + services redis, postgres, migration (fonctionnels)
|
||||
|
||||
OBJECTIF
|
||||
Je veux que :
|
||||
Open WebUI soit l'orchestrateur central (UI unique, gestion conversation, config assistant)
|
||||
Ollama sur PC gamer fasse l'inférence (via WOL proxy pour économie énergie)
|
||||
AFFiNE serve de base de connaissance persistante où l'assistant peut :
|
||||
Rechercher dans mes documents/notes
|
||||
Créer/modifier des notes automatiquement
|
||||
Récupérer du contexte pour les conversations
|
||||
Architecture souhaitée :
|
||||
text
|
||||
Utilisateur → Open WebUI (NAS) → WOL Proxy → Ollama (PC)
|
||||
↓
|
||||
[Tools]
|
||||
↓
|
||||
AFFiNE (NAS) ← GraphQL API
|
||||
|
||||
CONTRAINTES \& PRÉFÉRENCES
|
||||
✅ Tout en Docker/Docker Compose sur le NAS (sauf Ollama sur PC)
|
||||
✅ Solution simple et maintenable (pas de dépendances npm complexes si possible)
|
||||
✅ Pas de duplication d'historique (Open WebUI = source de vérité)
|
||||
✅ AFFiNE doit rester utilisable indépendamment (pas couplé à l'IA)
|
||||
⚠️ J'ai essayé d'installer le package npm @dawncr0w/affine-mcp-server mais il n'existe pas dans le registre npm
|
||||
QUESTIONS CONCRÈTES
|
||||
Quelle est l'approche la plus robuste : MCP server + proxy HTTP, ou outil Python custom dans Open WebUI qui appelle directement l'API GraphQL d'AFFiNE ?
|
||||
Comment configurer proprement l'intégration pour que le modèle LLM puisse faire du function calling vers AFFiNE (recherche, création docs, etc.) ?
|
||||
Dois-je modifier l'URL Ollama dans AFFiNE (AFFINE_OPENAI_BASE_URL) pour qu'elle passe aussi par le WOL proxy ? Actuellement elle pointe directement sur le PC.
|
||||
Peux-tu me fournir :
|
||||
Le docker-compose.yml complet modifié (si ajout de services)
|
||||
Le code de l'outil/intégration (Python, MCP config, etc.)
|
||||
Les étapes de configuration dans Open WebUI
|
||||
VERSION LOGICIELS
|
||||
Open WebUI : 0.8.2
|
||||
AFFiNE : stable (ghcr.io/toeverything/affine:stable)
|
||||
Ollama : 0.16.1
|
||||
IMPORTANT : Je préfère une solution qui minimise le nombre de conteneurs supplémentaires et qui soit facile à débugger. Si MCP est trop complexe, une intégration directe via GraphQL + Python dans Open WebUI me convient aussi.
|
||||
|
||||
Basé sur votre infrastructure et vos contraintes, je recommande **une intégration Python directe via GraphQL** plutôt que MCP, car c'est plus simple, maintenable et parfaitement adapté à votre cas.[^1_1][^1_2][^1_3]
|
||||
|
||||
## Approche recommandée : Outil Python Custom dans Open WebUI
|
||||
|
||||
Open WebUI supporte nativement le function calling via des **Tools** Python qui permettent au LLM d'appeler des fonctions externes. Cette approche évite d'ajouter des conteneurs supplémentaires et reste facilement débuggable.[^1_2][^1_3][^1_1]
|
||||
|
||||
### Architecture finale
|
||||
|
||||
```
|
||||
Utilisateur → Open WebUI (NAS:3000) → Ollama (via WOL Proxy)
|
||||
↓
|
||||
[AFFiNE Tool]
|
||||
↓
|
||||
AFFiNE GraphQL API (NAS:3010)
|
||||
```
|
||||
|
||||
|
||||
## Solution complète
|
||||
|
||||
### 1. Docker Compose modifié
|
||||
|
||||
```yaml
|
||||
services:
|
||||
# Configuration AFFiNE existante
|
||||
affine:
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
ports: ['3010:3010']
|
||||
environment:
|
||||
- AFFINE_COPILOT_ENABLED=true
|
||||
- AFFINE_AI_PROVIDER=openai
|
||||
# Modifier pour passer par le WOL proxy
|
||||
- AFFINE_OPENAI_BASE_URL=http://wol-proxy:3011/v1
|
||||
- AFFINE_OPENAI_API_KEY=ollama
|
||||
depends_on: [redis, postgres, affine_migration]
|
||||
networks:
|
||||
- affine_network
|
||||
|
||||
postgres:
|
||||
# Votre config existante
|
||||
networks:
|
||||
- affine_network
|
||||
|
||||
redis:
|
||||
# Votre config existante
|
||||
networks:
|
||||
- affine_network
|
||||
|
||||
affine_migration:
|
||||
# Votre config existante
|
||||
networks:
|
||||
- affine_network
|
||||
|
||||
# Configuration Open WebUI
|
||||
open-webui:
|
||||
image: ghcr.io/open-webui/open-webui:latest
|
||||
ports: ['3000:8080']
|
||||
environment:
|
||||
- OLLAMA_BASE_URL=http://wol-proxy:3011
|
||||
- WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}
|
||||
volumes:
|
||||
- open-webui-data:/app/backend/data
|
||||
networks:
|
||||
- affine_network
|
||||
|
||||
# WOL Proxy existant
|
||||
wol-proxy:
|
||||
# Votre config existante
|
||||
ports: ['3011:3011']
|
||||
networks:
|
||||
- affine_network
|
||||
|
||||
networks:
|
||||
affine_network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
open-webui-data:
|
||||
```
|
||||
|
||||
|
||||
### 2. Tool Python pour Open WebUI
|
||||
|
||||
Créez un fichier `affine_tool.py` à importer dans Open WebUI:[^1_3]
|
||||
|
||||
```python
|
||||
"""
|
||||
title: AFFiNE Knowledge Base Tool
|
||||
author: Your Name
|
||||
version: 1.0.0
|
||||
description: Interact with AFFiNE workspace for document search, creation, and retrieval
|
||||
required_open_webui_version: 0.8.0
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
from typing import Optional, Callable, Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Tools:
|
||||
class Valves(BaseModel):
|
||||
AFFINE_BASE_URL: str = Field(
|
||||
default="http://affine:3010",
|
||||
description="AFFiNE server base URL"
|
||||
)
|
||||
AFFINE_WORKSPACE_ID: str = Field(
|
||||
default="",
|
||||
description="Default workspace ID"
|
||||
)
|
||||
AFFINE_API_TOKEN: str = Field(
|
||||
default="",
|
||||
description="AFFiNE personal access token (optional)"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
self.graphql_endpoint = None
|
||||
|
||||
def _get_endpoint(self):
|
||||
"""Construire l'URL GraphQL"""
|
||||
if not self.graphql_endpoint:
|
||||
base = self.valves.AFFINE_BASE_URL.rstrip('/')
|
||||
self.graphql_endpoint = f"{base}/graphql"
|
||||
return self.graphql_endpoint
|
||||
|
||||
def _execute_graphql(self, query: str, variables: dict = None) -> dict:
|
||||
"""Exécuter une requête GraphQL"""
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
# Ajouter le token si disponible
|
||||
if self.valves.AFFINE_API_TOKEN:
|
||||
headers["Authorization"] = f"Bearer {self.valves.AFFINE_API_TOKEN}"
|
||||
|
||||
payload = {"query": query}
|
||||
if variables:
|
||||
payload["variables"] = variables
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
self._get_endpoint(),
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def search_documents(
|
||||
self,
|
||||
search_query: str,
|
||||
workspace_id: Optional[str] = None,
|
||||
__event_emitter__: Callable[[dict], Any] = None
|
||||
) -> str:
|
||||
"""
|
||||
Search for documents in AFFiNE workspace.
|
||||
|
||||
:param search_query: Text to search for in documents
|
||||
:param workspace_id: Workspace ID (uses default if not provided)
|
||||
:return: Search results with document titles and IDs
|
||||
"""
|
||||
if __event_emitter__:
|
||||
__event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": f"Searching AFFiNE for: {search_query}", "done": False}
|
||||
})
|
||||
|
||||
ws_id = workspace_id or self.valves.AFFINE_WORKSPACE_ID
|
||||
|
||||
query = """
|
||||
query SearchDocs($workspaceId: String!, $query: String!) {
|
||||
workspace(id: $workspaceId) {
|
||||
docs {
|
||||
id
|
||||
title
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
result = self._execute_graphql(query, {
|
||||
"workspaceId": ws_id,
|
||||
"query": search_query
|
||||
})
|
||||
|
||||
if __event_emitter__:
|
||||
__event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": "Search completed", "done": True}
|
||||
})
|
||||
|
||||
if "error" in result:
|
||||
return f"Error searching documents: {result['error']}"
|
||||
|
||||
if "data" in result and result["data"].get("workspace"):
|
||||
docs = result["data"]["workspace"]["docs"]
|
||||
if docs:
|
||||
response = f"Found {len(docs)} documents:\n"
|
||||
for doc in docs[:10]: # Limiter à 10 résultats
|
||||
response += f"- {doc['title']} (ID: {doc['id']})\n"
|
||||
return response
|
||||
return "No documents found matching your query."
|
||||
|
||||
return "Error retrieving search results."
|
||||
|
||||
def get_document_content(
|
||||
self,
|
||||
doc_id: str,
|
||||
workspace_id: Optional[str] = None,
|
||||
__event_emitter__: Callable[[dict], Any] = None
|
||||
) -> str:
|
||||
"""
|
||||
Retrieve the content of a specific document.
|
||||
|
||||
:param doc_id: Document ID to retrieve
|
||||
:param workspace_id: Workspace ID (uses default if not provided)
|
||||
:return: Document content
|
||||
"""
|
||||
if __event_emitter__:
|
||||
__event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": f"Retrieving document {doc_id}", "done": False}
|
||||
})
|
||||
|
||||
ws_id = workspace_id or self.valves.AFFINE_WORKSPACE_ID
|
||||
|
||||
query = """
|
||||
query GetDoc($workspaceId: String!, $docId: String!) {
|
||||
workspace(id: $workspaceId) {
|
||||
doc(id: $docId) {
|
||||
id
|
||||
title
|
||||
content
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
result = self._execute_graphql(query, {
|
||||
"workspaceId": ws_id,
|
||||
"docId": doc_id
|
||||
})
|
||||
|
||||
if __event_emitter__:
|
||||
__event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": "Document retrieved", "done": True}
|
||||
})
|
||||
|
||||
if "error" in result:
|
||||
return f"Error retrieving document: {result['error']}"
|
||||
|
||||
if "data" in result and result["data"].get("workspace", {}).get("doc"):
|
||||
doc = result["data"]["workspace"]["doc"]
|
||||
return f"# {doc['title']}\n\nLast updated: {doc['updatedAt']}\n\n{doc.get('content', 'No content available')}"
|
||||
|
||||
return "Document not found or error retrieving content."
|
||||
|
||||
def create_document(
|
||||
self,
|
||||
title: str,
|
||||
content: str,
|
||||
workspace_id: Optional[str] = None,
|
||||
__event_emitter__: Callable[[dict], Any] = None
|
||||
) -> str:
|
||||
"""
|
||||
Create a new document in AFFiNE workspace.
|
||||
|
||||
:param title: Title of the new document
|
||||
:param content: Content of the document
|
||||
:param workspace_id: Workspace ID (uses default if not provided)
|
||||
:return: Created document ID and confirmation
|
||||
"""
|
||||
if __event_emitter__:
|
||||
__event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": f"Creating document: {title}", "done": False}
|
||||
})
|
||||
|
||||
ws_id = workspace_id or self.valves.AFFINE_WORKSPACE_ID
|
||||
|
||||
mutation = """
|
||||
mutation CreateDoc($workspaceId: String!, $title: String!, $content: String!) {
|
||||
createDoc(workspaceId: $workspaceId, title: $title, content: $content) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
result = self._execute_graphql(mutation, {
|
||||
"workspaceId": ws_id,
|
||||
"title": title,
|
||||
"content": content
|
||||
})
|
||||
|
||||
if __event_emitter__:
|
||||
__event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": "Document created successfully", "done": True}
|
||||
})
|
||||
|
||||
if "error" in result:
|
||||
return f"Error creating document: {result['error']}"
|
||||
|
||||
if "data" in result and result["data"].get("createDoc"):
|
||||
doc = result["data"]["createDoc"]
|
||||
return f"Successfully created document '{doc['title']}' with ID: {doc['id']}"
|
||||
|
||||
return "Error creating document."
|
||||
|
||||
def list_workspaces(
|
||||
self,
|
||||
__event_emitter__: Callable[[dict], Any] = None
|
||||
) -> str:
|
||||
"""
|
||||
List all available workspaces.
|
||||
|
||||
:return: List of workspace names and IDs
|
||||
"""
|
||||
if __event_emitter__:
|
||||
__event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": "Fetching workspaces", "done": False}
|
||||
})
|
||||
|
||||
query = """
|
||||
query {
|
||||
workspaces {
|
||||
id
|
||||
name
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
result = self._execute_graphql(query)
|
||||
|
||||
if __event_emitter__:
|
||||
__event_emitter__({
|
||||
"type": "status",
|
||||
"data": {"description": "Workspaces retrieved", "done": True}
|
||||
})
|
||||
|
||||
if "error" in result:
|
||||
return f"Error listing workspaces: {result['error']}"
|
||||
|
||||
if "data" in result and result["data"].get("workspaces"):
|
||||
workspaces = result["data"]["workspaces"]
|
||||
if workspaces:
|
||||
response = f"Available workspaces ({len(workspaces)}):\n"
|
||||
for ws in workspaces:
|
||||
response += f"- {ws['name']} (ID: {ws['id']})\n"
|
||||
return response
|
||||
return "No workspaces found."
|
||||
|
||||
return "Error retrieving workspaces."
|
||||
```
|
||||
|
||||
|
||||
### 3. Configuration dans Open WebUI
|
||||
|
||||
#### Étape A : Importer le Tool
|
||||
|
||||
1. Connectez-vous à Open WebUI (http://192.168.1.25:3000)
|
||||
2. Allez dans **Workspace → Tools**
|
||||
3. Cliquez sur **+ Create Tool**
|
||||
4. Collez le code Python ci-dessus
|
||||
5. Cliquez sur **Save**
|
||||
|
||||
#### Étape B : Configurer les Valves[^1_3]
|
||||
|
||||
1. Cliquez sur l'icône d'engrenage du tool **AFFiNE Knowledge Base**
|
||||
2. Configurez les valeurs :
|
||||
- `AFFINE_BASE_URL`: `http://affine:3010` (communication inter-conteneur)
|
||||
- `AFFINE_WORKSPACE_ID`: Votre ID de workspace (à récupérer)
|
||||
- `AFFINE_API_TOKEN`: Laissez vide pour l'instant
|
||||
|
||||
#### Étape C : Récupérer le Workspace ID[^1_4][^1_5]
|
||||
|
||||
Depuis le NAS, exécutez :
|
||||
|
||||
```bash
|
||||
curl -X POST http://192.168.1.25:3010/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ workspaces { id name } }"}'
|
||||
```
|
||||
|
||||
Copiez l'ID du workspace et mettez-le dans les Valves.
|
||||
|
||||
#### Étape D : Activer le Tool
|
||||
|
||||
1. Dans **Workspace → Models**, sélectionnez votre modèle Ollama
|
||||
2. Cliquez sur l'icône crayon pour éditer
|
||||
3. Scrollez jusqu'à **Functions**
|
||||
4. Cochez **AFFiNE Knowledge Base Tool**
|
||||
5. Cliquez sur **Save**
|
||||
|
||||
## Réponses aux questions
|
||||
|
||||
### 1. Approche la plus robuste
|
||||
|
||||
**Python Tool directement dans Open WebUI** est supérieure à MCP dans votre cas car:[^1_6][^1_3]
|
||||
|
||||
- Pas de conteneur supplémentaire
|
||||
- Débugage simple (logs Python natifs)
|
||||
- Configuration unifiée dans Open WebUI
|
||||
- Le package npm `@dawncr0w/affine-mcp-server` existe mais nécessite Node.js + conteneur dédié[^1_7][^1_5]
|
||||
|
||||
|
||||
### 2. Configuration function calling
|
||||
|
||||
Le code fourni utilise les **Sphinx-style docstrings** qu'Open WebUI convertit automatiquement en spécifications de tools pour le LLM. Les paramètres typés permettent au modèle de comprendre comment appeler les fonctions.[^1_2][^1_3]
|
||||
|
||||
### 3. URL Ollama dans AFFiNE
|
||||
|
||||
**Oui, modifiez-la** pour utiliser le WOL proxy. Changez :[^1_1]
|
||||
|
||||
```yaml
|
||||
- AFFINE_OPENAI_BASE_URL=http://wol-proxy:3011/v1
|
||||
```
|
||||
|
||||
**Important** : Ajoutez `/v1` car AFFiNE attend une API compatible OpenAI. Votre WOL proxy doit donc exposer `/v1/chat/completions` (pattern standard Ollama).[^1_4]
|
||||
|
||||
## Avantages de cette solution
|
||||
|
||||
✅ **Un seul conteneur supplémentaire** : Open WebUI gère tout
|
||||
✅ **Debugging facile** : Logs Python visibles dans Open WebUI
|
||||
✅ **Pas de duplication** : Open WebUI reste la source de vérité
|
||||
✅ **AFFiNE indépendant** : Fonctionne normalement sans l'IA
|
||||
✅ **Économie d'énergie** : Le WOL proxy réveille le PC uniquement quand nécessaire
|
||||
|
||||
## Limitations et évolutions
|
||||
|
||||
- Les requêtes GraphQL ci-dessus sont des **exemples** : l'API réelle d'AFFiNE peut différer[^1_8][^1_9]
|
||||
- Testez les requêtes manuellement avec `curl` avant de les intégrer
|
||||
- Pour l'authentification avancée, générez un Personal Access Token via l'API GraphQL d'AFFiNE[^1_10][^1_11]
|
||||
- Si vous avez besoin de capacités MCP plus tard, le projet `ivanuser/open-webui-mcp-connector` existe[^1_6]
|
||||
|
||||
Cette architecture vous permet de commencer simplement et d'étendre progressivement les fonctionnalités selon vos besoins.[^1_1][^1_3]
|
||||
<span style="display:none">[^1_12][^1_13][^1_14][^1_15][^1_16][^1_17][^1_18][^1_19][^1_20][^1_21][^1_22][^1_23][^1_24][^1_25]</span>
|
||||
|
||||
<div align="center">⁂</div>
|
||||
|
||||
[^1_1]: https://github.com/open-webui/open-webui/discussions/11820
|
||||
|
||||
[^1_2]: https://github.com/open-webui/open-webui/discussions/3134
|
||||
|
||||
[^1_3]: https://docs.openwebui.com/tutorials/plugin/functions/
|
||||
|
||||
[^1_4]: https://lobehub.com/mcp/dawncr0w-affine-mcp-server
|
||||
|
||||
[^1_5]: https://github.com/dawncr0w/affine-mcp-server
|
||||
|
||||
[^1_6]: https://github.com/ivanuser/open-webui-mcp-connector
|
||||
|
||||
[^1_7]: https://playbooks.com/mcp/dawncr0w/affine-mcp-server
|
||||
|
||||
[^1_8]: https://deepwiki.com/toeverything/AFFiNE/3.1-server-and-api
|
||||
|
||||
[^1_9]: https://opendeep.wiki/toeverything/AFFiNE/integrating-external-app
|
||||
|
||||
[^1_10]: https://github.com/toeverything/AFFiNE/issues/13262
|
||||
|
||||
[^1_11]: https://glama.ai/mcp/servers/@DAWNCR0W/affine-mcp-server/tools/affine_generate_access_token
|
||||
|
||||
[^1_12]: https://www.reddit.com/r/OpenWebUI/comments/1jeqj9u/is_function_calling_possible_in_open_webui/
|
||||
|
||||
[^1_13]: https://www.youtube.com/watch?v=04h9XlOkhCE
|
||||
|
||||
[^1_14]: https://www.reddit.com/r/mcp/comments/1oqj7hp/affine_mcp_server_enables_interaction_with_affine/
|
||||
|
||||
[^1_15]: https://github.com/open-webui/open-webui/issues/2825
|
||||
|
||||
[^1_16]: https://dahvc-docs.colab.duke.edu/mcp/open-web-ui-and-mcpo.html
|
||||
|
||||
[^1_17]: https://github.com/open-webui/open-webui/discussions/1629
|
||||
|
||||
[^1_18]: https://mcpmarket.com/server/affine
|
||||
|
||||
[^1_19]: https://rainbowbreeze.it/openwebui-mcp-http/
|
||||
|
||||
[^1_20]: https://www.reddit.com/r/OpenWebUI/comments/1oqh3au/open_webui_now_supports_native_sequential_tool/
|
||||
|
||||
[^1_21]: https://docs.affine.pro/self-host-affine/install/upgrade
|
||||
|
||||
[^1_22]: https://developers.cloudflare.com/analytics/graphql-api/getting-started/authentication/api-token-auth/
|
||||
|
||||
[^1_23]: https://developers.openai.com/api/docs/guides/function-calling/
|
||||
|
||||
[^1_24]: https://community.retool.com/t/graphql-authorization-with-url-token/4638
|
||||
|
||||
[^1_25]: https://shyft.ai/mcp/dawncr0w-affine-mcp-server-github
|
||||
|
||||
Reference in New Issue
Block a user