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:
Folzo
2026-04-06 01:05:18 +02:00
parent ad2f7bb221
commit 97348e9a12
21 changed files with 23379 additions and 18 deletions
@@ -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