Latest fixxes
This commit is contained in:
@@ -8,6 +8,7 @@ A backend service for OpenWebUI that provides OpenWebUI-compatible API endpoints
|
|||||||
- Ollama API proxy
|
- Ollama API proxy
|
||||||
- Chat functionality with model switching
|
- Chat functionality with model switching
|
||||||
- Support for multiple LLM models (gemma3, llama3.3, llama3.1, mistral, deepseek)
|
- Support for multiple LLM models (gemma3, llama3.3, llama3.1, mistral, deepseek)
|
||||||
|
- Team chat integration with OpenWebUI channels
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ ai_service/
|
|||||||
├── embeddings/ # Document processing for RAG
|
├── embeddings/ # Document processing for RAG
|
||||||
│ └── document_service.py
|
│ └── document_service.py
|
||||||
├── openwebui_api.py # OpenWebUI-compatible API endpoints
|
├── openwebui_api.py # OpenWebUI-compatible API endpoints
|
||||||
|
├── openwebui_channels.py # OpenWebUI channels integration
|
||||||
├── config.py # Configuration settings
|
├── config.py # Configuration settings
|
||||||
├── api.py # FastAPI application
|
├── api.py # FastAPI application
|
||||||
└── deploy.sh # Deployment script
|
└── deploy.sh # Deployment script
|
||||||
@@ -88,3 +90,7 @@ To configure OpenWebUI to use this service as its backend:
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. Restart OpenWebUI to apply the changes.
|
2. Restart OpenWebUI to apply the changes.
|
||||||
|
|
||||||
|
## Team Chat Feature
|
||||||
|
|
||||||
|
The service now integrates with OpenWebUI's channels feature to provide team chat functionality. See [TEAM_CHAT_GUIDE.md](TEAM_CHAT_GUIDE.md) for detailed instructions on how to use team chats.
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# Team Chat Guide
|
||||||
|
|
||||||
|
This guide explains how to use the team chat feature with OpenWebUI channels.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The chatbot now integrates with OpenWebUI's channels feature to provide team chat functionality. When you create a team chat in the chatbot, it automatically creates a corresponding channel in OpenWebUI, allowing multiple users to participate in the same conversation.
|
||||||
|
|
||||||
|
## Using Team Chats
|
||||||
|
|
||||||
|
### AI Responses in Team Chats
|
||||||
|
|
||||||
|
When you send a message in a team chat, the AI will respond both in your local chat and in the OpenWebUI channel. The AI's responses in OpenWebUI channels:
|
||||||
|
|
||||||
|
- Are prefixed with a robot emoji (🤖) to clearly identify them as AI responses
|
||||||
|
- Appear with a special "ai-assistant" user ID to distinguish them from human users
|
||||||
|
- Are visible to all members of the channel in real-time
|
||||||
|
|
||||||
|
This allows all team members to see the conversation with the AI and collaborate effectively.
|
||||||
|
|
||||||
|
### How the AI Bot is Triggered
|
||||||
|
|
||||||
|
The AI bot can be triggered to respond in two ways:
|
||||||
|
|
||||||
|
1. **Through the API**: When you send a message using the `/chats/{chat_id}/messages` endpoint, the AI automatically processes it and responds.
|
||||||
|
|
||||||
|
2. **Directly in OpenWebUI**: When someone mentions the AI in an OpenWebUI channel that's linked to a team chat:
|
||||||
|
- The message is sent to your service through a webhook
|
||||||
|
- If it contains an AI mention (like `@ai`, `@bot`, etc.), the AI processes it
|
||||||
|
- The AI sends its response back to the OpenWebUI channel
|
||||||
|
|
||||||
|
By default, the AI only responds when explicitly mentioned with one of these triggers:
|
||||||
|
- `@ai`
|
||||||
|
- `@bot`
|
||||||
|
- `@assistant`
|
||||||
|
- `@chatbot`
|
||||||
|
|
||||||
|
You can customize these triggers or make the AI respond to all messages by changing the settings in your `.env` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
# To customize the triggers that activate the AI
|
||||||
|
AI_TRIGGERS=@ai,@bot,@assistant,@chatbot
|
||||||
|
|
||||||
|
# To make the AI respond to all messages (not just mentions)
|
||||||
|
AI_RESPOND_TO_ALL=false # Change to 'true' to respond to everything
|
||||||
|
```
|
||||||
|
|
||||||
|
This mention-based approach ensures the AI only joins the conversation when explicitly invited, making team chats more focused and preventing the AI from responding to every message.
|
||||||
|
|
||||||
|
### Creating a Team Chat
|
||||||
|
|
||||||
|
You can create a team chat in two ways:
|
||||||
|
|
||||||
|
1. **Through the API**:
|
||||||
|
```
|
||||||
|
POST /chats
|
||||||
|
{
|
||||||
|
"user_id": "your-user-id",
|
||||||
|
"title": "Team Chat Name",
|
||||||
|
"model_id": "llama3.1",
|
||||||
|
"is_team_chat": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Through OpenWebUI**:
|
||||||
|
- Log in to OpenWebUI at http://104.225.217.215:8080/
|
||||||
|
- Navigate to the Channels section (look for a "#" or group icon in the sidebar)
|
||||||
|
- Click "Create Channel" or "+" button
|
||||||
|
- Give your channel a name and description
|
||||||
|
- Choose whether it should be private or public
|
||||||
|
- Click "Create"
|
||||||
|
|
||||||
|
### Adding Members to a Team Chat
|
||||||
|
|
||||||
|
1. **Through the API**:
|
||||||
|
```
|
||||||
|
POST /chats/{chat_id}/members/{user_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Through OpenWebUI**:
|
||||||
|
- Open the channel in OpenWebUI
|
||||||
|
- Look for a "Members" or "Invite" option
|
||||||
|
- Add users by their username or email
|
||||||
|
|
||||||
|
### Sending Messages in a Team Chat
|
||||||
|
|
||||||
|
1. **Through the API**:
|
||||||
|
```
|
||||||
|
POST /chats/{chat_id}/messages
|
||||||
|
{
|
||||||
|
"message": "Your message",
|
||||||
|
"user_id": "your-user-id"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Through OpenWebUI**:
|
||||||
|
- Open the channel in OpenWebUI
|
||||||
|
- Type your message in the input box
|
||||||
|
- Press Enter to send
|
||||||
|
|
||||||
|
### Viewing Team Chats
|
||||||
|
|
||||||
|
1. **Through the API**:
|
||||||
|
```
|
||||||
|
GET /chats/user/{user_id}
|
||||||
|
```
|
||||||
|
This will return all chats for the user, including team chats where they are a member.
|
||||||
|
|
||||||
|
2. **Through OpenWebUI**:
|
||||||
|
- Log in to OpenWebUI
|
||||||
|
- Navigate to the Channels section
|
||||||
|
- You'll see all channels you're a member of
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
The team chat feature works by:
|
||||||
|
|
||||||
|
1. Creating an OpenWebUI channel when a team chat is created
|
||||||
|
2. Adding members to both the local team chat and the OpenWebUI channel
|
||||||
|
3. Sending messages to both the local chat and the OpenWebUI channel
|
||||||
|
4. Deleting the OpenWebUI channel when the team chat is deleted
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you encounter issues with team chats:
|
||||||
|
|
||||||
|
1. **Channel not appearing in OpenWebUI**:
|
||||||
|
- Check if the OpenWebUI server is running
|
||||||
|
- Verify that the OpenWebUI URL and API key are correctly configured in the `.env` file
|
||||||
|
|
||||||
|
2. **Cannot add members to a team chat**:
|
||||||
|
- Ensure the user exists in OpenWebUI
|
||||||
|
- Check if the team chat was properly created with `is_team_chat: true`
|
||||||
|
|
||||||
|
3. **Messages not appearing in OpenWebUI channel**:
|
||||||
|
- Check the logs for any errors when sending messages
|
||||||
|
- Verify that the OpenWebUI channel ID is correctly stored in the chat data
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Team Chat Endpoints
|
||||||
|
|
||||||
|
- `POST /chats` - Create a new chat (set `is_team_chat: true` for team chats)
|
||||||
|
- `GET /chats/user/{user_id}` - Get all chats for a user (includes team chats)
|
||||||
|
- `POST /chats/{chat_id}/members/{user_id}` - Add a user to a team chat
|
||||||
|
- `DELETE /chats/{chat_id}/members/{user_id}` - Remove a user from a team chat
|
||||||
|
- `DELETE /chats/{chat_id}` - Delete a chat (also deletes the OpenWebUI channel)
|
||||||
|
|
||||||
|
### OpenWebUI Channel Endpoints
|
||||||
|
|
||||||
|
- `GET /channels` - Get all OpenWebUI channels
|
||||||
|
- `GET /channels/{channel_id}` - Get an OpenWebUI channel by ID
|
||||||
|
- `POST /channels` - Create a new OpenWebUI channel
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# API configuration
|
# API configuration
|
||||||
API_HOST=0.0.0.0
|
API_HOST=0.0.0.0
|
||||||
API_PORT=5251
|
API_PORT=5251
|
||||||
|
PUBLIC_URL=http://your-public-url:5251 # Public URL for webhooks, needed for OpenWebUI to send channel messages
|
||||||
|
|
||||||
# OpenWebUI configuration
|
# OpenWebUI configuration
|
||||||
OPENWEBUI_URL=http://104.225.217.215:8080
|
OPENWEBUI_URL=http://104.225.217.215:8080
|
||||||
@@ -13,3 +14,7 @@ DEFAULT_MODEL=llama3.1
|
|||||||
# Document processing
|
# Document processing
|
||||||
CHUNK_SIZE=1000
|
CHUNK_SIZE=1000
|
||||||
CHUNK_OVERLAP=200
|
CHUNK_OVERLAP=200
|
||||||
|
|
||||||
|
# AI bot configuration
|
||||||
|
AI_TRIGGERS=@ai,@bot,@assistant,@chatbot # Comma-separated list of triggers that will make the AI respond
|
||||||
|
AI_RESPOND_TO_ALL=false # Set to 'true' to make the AI respond to all messages, 'false' to only respond to mentions
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from datetime import datetime, timezone
|
|||||||
from ai_service.models.model_service import model_service
|
from ai_service.models.model_service import model_service
|
||||||
from ai_service.models.chat_service import chat_service
|
from ai_service.models.chat_service import chat_service
|
||||||
from ai_service.openwebui_api import router as openwebui_router
|
from ai_service.openwebui_api import router as openwebui_router
|
||||||
|
from ai_service.openwebui_channels import openwebui_channels
|
||||||
from ai_service.config import config
|
from ai_service.config import config
|
||||||
|
|
||||||
# Create FastAPI app
|
# Create FastAPI app
|
||||||
@@ -37,6 +38,27 @@ app.include_router(openwebui_router, prefix="/api")
|
|||||||
# Include Ollama proxy routes
|
# Include Ollama proxy routes
|
||||||
app.include_router(openwebui_router, prefix="/ollama")
|
app.include_router(openwebui_router, prefix="/ollama")
|
||||||
|
|
||||||
|
# Register webhook for channel messages on startup
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def startup_event():
|
||||||
|
"""
|
||||||
|
Register webhook for channel messages on startup.
|
||||||
|
"""
|
||||||
|
# Get the public URL of this service
|
||||||
|
service_url = f"http://{config.API_HOST}:{config.API_PORT}"
|
||||||
|
if config.PUBLIC_URL:
|
||||||
|
service_url = config.PUBLIC_URL
|
||||||
|
|
||||||
|
# Register webhook
|
||||||
|
webhook_url = f"{service_url}/webhooks/channel-message"
|
||||||
|
print(f"Registering webhook for channel messages: {webhook_url}")
|
||||||
|
|
||||||
|
success = openwebui_channels.register_webhook(webhook_url)
|
||||||
|
if success:
|
||||||
|
print("Successfully registered webhook for channel messages")
|
||||||
|
else:
|
||||||
|
print("Failed to register webhook for channel messages")
|
||||||
|
|
||||||
# Define API models for health check
|
# Define API models for health check
|
||||||
class HealthResponse(BaseModel):
|
class HealthResponse(BaseModel):
|
||||||
"""Response model for health check."""
|
"""Response model for health check."""
|
||||||
@@ -450,3 +472,133 @@ async def delete_chat(chat_id: str):
|
|||||||
raise HTTPException(status_code=404, detail="Chat not found")
|
raise HTTPException(status_code=404, detail="Chat not found")
|
||||||
|
|
||||||
return {"status": "success", "message": "Chat deleted"}
|
return {"status": "success", "message": "Chat deleted"}
|
||||||
|
|
||||||
|
# OpenWebUI Channels endpoints
|
||||||
|
@app.get("/channels")
|
||||||
|
async def get_openwebui_channels():
|
||||||
|
"""
|
||||||
|
Get all OpenWebUI channels.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of channels.
|
||||||
|
"""
|
||||||
|
channels = openwebui_channels.get_channels()
|
||||||
|
return channels
|
||||||
|
|
||||||
|
@app.get("/channels/{channel_id}")
|
||||||
|
async def get_openwebui_channel(channel_id: str):
|
||||||
|
"""
|
||||||
|
Get an OpenWebUI channel by ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_id: Channel ID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Channel information.
|
||||||
|
"""
|
||||||
|
channel = openwebui_channels.get_channel(channel_id)
|
||||||
|
if not channel:
|
||||||
|
raise HTTPException(status_code=404, detail="Channel not found")
|
||||||
|
|
||||||
|
return channel
|
||||||
|
|
||||||
|
@app.post("/channels")
|
||||||
|
async def create_openwebui_channel(name: str, description: str = "", is_private: bool = False):
|
||||||
|
"""
|
||||||
|
Create a new OpenWebUI channel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Channel name.
|
||||||
|
description: Channel description.
|
||||||
|
is_private: Whether the channel is private.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created channel.
|
||||||
|
"""
|
||||||
|
channel = openwebui_channels.create_channel(name, description, is_private)
|
||||||
|
if not channel:
|
||||||
|
raise HTTPException(status_code=400, detail="Failed to create channel")
|
||||||
|
|
||||||
|
return channel
|
||||||
|
|
||||||
|
# Webhook endpoint for OpenWebUI channel messages
|
||||||
|
class ChannelMessageWebhook(BaseModel):
|
||||||
|
"""Model for channel message webhook."""
|
||||||
|
channel_id: str = Field(..., description="Channel ID")
|
||||||
|
message: str = Field(..., description="Message content")
|
||||||
|
user_id: str = Field(..., description="User ID")
|
||||||
|
timestamp: Optional[str] = Field(None, description="Message timestamp")
|
||||||
|
|
||||||
|
@app.post("/webhooks/channel-message")
|
||||||
|
async def channel_message_webhook(request: ChannelMessageWebhook):
|
||||||
|
"""
|
||||||
|
Webhook endpoint for receiving messages from OpenWebUI channels.
|
||||||
|
|
||||||
|
This endpoint is called by OpenWebUI when a message is sent in a channel.
|
||||||
|
The AI service will process the message and respond in the channel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: Channel message webhook request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Processing status.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
print(f"Received channel message webhook: {request.channel_id}, {request.user_id}, {request.message}")
|
||||||
|
|
||||||
|
# Find the chat associated with this OpenWebUI channel
|
||||||
|
chat_id = None
|
||||||
|
for cid, chat in chat_service.chats.items():
|
||||||
|
if chat.get('is_team_chat') and chat.get('openwebui_channel_id') == request.channel_id:
|
||||||
|
chat_id = cid
|
||||||
|
break
|
||||||
|
|
||||||
|
if not chat_id:
|
||||||
|
print(f"No chat found for OpenWebUI channel {request.channel_id}")
|
||||||
|
return {"status": "error", "message": "No chat found for this channel"}
|
||||||
|
|
||||||
|
# Skip messages from the AI assistant to avoid loops
|
||||||
|
if request.user_id == "ai-assistant":
|
||||||
|
return {"status": "skipped", "message": "Skipping AI assistant message"}
|
||||||
|
|
||||||
|
# Check if we should respond to all messages or only to mentions
|
||||||
|
if not config.AI_RESPOND_TO_ALL:
|
||||||
|
# Check if the message mentions the AI using configured triggers
|
||||||
|
message_lower = request.message.lower()
|
||||||
|
|
||||||
|
is_triggered = False
|
||||||
|
for trigger in config.AI_TRIGGERS:
|
||||||
|
if trigger.lower() in message_lower:
|
||||||
|
is_triggered = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# If no trigger is found, skip processing
|
||||||
|
if not is_triggered:
|
||||||
|
print(f"No AI mention found in message, skipping: {request.message[:50]}...")
|
||||||
|
return {"status": "skipped", "message": "No AI mention found in message"}
|
||||||
|
|
||||||
|
# Extract the actual message content (remove the trigger)
|
||||||
|
# This is a simple approach - for more complex cases, you might want more sophisticated parsing
|
||||||
|
processed_message = request.message
|
||||||
|
message_lower = request.message.lower()
|
||||||
|
|
||||||
|
# Only try to remove triggers if we're not responding to all messages
|
||||||
|
if not config.AI_RESPOND_TO_ALL:
|
||||||
|
for trigger in config.AI_TRIGGERS:
|
||||||
|
if trigger.lower() in message_lower:
|
||||||
|
# Remove the trigger from the message
|
||||||
|
processed_message = request.message.replace(trigger, "").strip()
|
||||||
|
break
|
||||||
|
|
||||||
|
# Process the message and generate a response
|
||||||
|
response = chat_service.get_chat_response(
|
||||||
|
chat_id=chat_id,
|
||||||
|
message=processed_message,
|
||||||
|
user_id=request.user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"status": "success", "message": "Message processed", "response": response}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing channel message webhook: {str(e)}")
|
||||||
|
return {"status": "error", "message": f"Error processing message: {str(e)}"}
|
||||||
|
|||||||
+11
-3
@@ -3,12 +3,15 @@ Configuration settings for the AI service.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
# Load environment variables from .env file
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
# Try to load environment variables from .env file
|
||||||
|
try:
|
||||||
|
from dotenv import load_dotenv
|
||||||
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
|
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
|
||||||
load_dotenv(dotenv_path=dotenv_path)
|
load_dotenv(dotenv_path=dotenv_path)
|
||||||
|
except ImportError:
|
||||||
|
print("Warning: python-dotenv not installed. Using environment variables directly.")
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
"""Base configuration."""
|
"""Base configuration."""
|
||||||
@@ -16,6 +19,7 @@ class Config:
|
|||||||
# API configuration
|
# API configuration
|
||||||
API_HOST = os.environ.get('API_HOST', '0.0.0.0')
|
API_HOST = os.environ.get('API_HOST', '0.0.0.0')
|
||||||
API_PORT = int(os.environ.get('API_PORT', 5252))
|
API_PORT = int(os.environ.get('API_PORT', 5252))
|
||||||
|
PUBLIC_URL = os.environ.get('PUBLIC_URL', '') # Public URL for webhooks
|
||||||
|
|
||||||
# OpenWebUI configuration
|
# OpenWebUI configuration
|
||||||
OPENWEBUI_URL = os.environ.get('OPENWEBUI_URL', 'http://104.225.217.215:8080')
|
OPENWEBUI_URL = os.environ.get('OPENWEBUI_URL', 'http://104.225.217.215:8080')
|
||||||
@@ -30,5 +34,9 @@ class Config:
|
|||||||
CHUNK_SIZE = int(os.environ.get('CHUNK_SIZE', 1000))
|
CHUNK_SIZE = int(os.environ.get('CHUNK_SIZE', 1000))
|
||||||
CHUNK_OVERLAP = int(os.environ.get('CHUNK_OVERLAP', 200))
|
CHUNK_OVERLAP = int(os.environ.get('CHUNK_OVERLAP', 200))
|
||||||
|
|
||||||
|
# AI bot configuration
|
||||||
|
AI_TRIGGERS = os.environ.get('AI_TRIGGERS', '@ai,@bot,@assistant,@chatbot').split(',')
|
||||||
|
AI_RESPOND_TO_ALL = os.environ.get('AI_RESPOND_TO_ALL', 'false').lower() == 'true'
|
||||||
|
|
||||||
|
|
||||||
config = Config()
|
config = Config()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from typing import List, Dict, Any, Optional
|
|||||||
from ai_service.config import config
|
from ai_service.config import config
|
||||||
from ai_service.models.model_service import model_service
|
from ai_service.models.model_service import model_service
|
||||||
from ai_service.models.model_parameters import ModelParameters
|
from ai_service.models.model_parameters import ModelParameters
|
||||||
|
from ai_service.openwebui_channels import openwebui_channels
|
||||||
|
|
||||||
class ChatService:
|
class ChatService:
|
||||||
"""Service for chat functionality."""
|
"""Service for chat functionality."""
|
||||||
@@ -61,17 +62,40 @@ class ChatService:
|
|||||||
# Generate a unique ID for the chat
|
# Generate a unique ID for the chat
|
||||||
chat_id = str(uuid.uuid4())
|
chat_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# Default title if none provided
|
||||||
|
chat_title = title or f"Chat {len(self.chats) + 1}"
|
||||||
|
|
||||||
|
# For team chats, create an OpenWebUI channel
|
||||||
|
openwebui_channel_id = None
|
||||||
|
if is_team_chat:
|
||||||
|
try:
|
||||||
|
# Create a channel in OpenWebUI
|
||||||
|
channel_response = openwebui_channels.create_channel(
|
||||||
|
name=chat_title,
|
||||||
|
description=f"Team chat created by {user_id}",
|
||||||
|
is_private=True # Team chats are private by default
|
||||||
|
)
|
||||||
|
|
||||||
|
if channel_response:
|
||||||
|
openwebui_channel_id = channel_response.get('id')
|
||||||
|
print(f"Created OpenWebUI channel with ID: {openwebui_channel_id}")
|
||||||
|
else:
|
||||||
|
print("Failed to create OpenWebUI channel, continuing with local team chat only")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating OpenWebUI channel: {str(e)}")
|
||||||
|
|
||||||
# Create chat data
|
# Create chat data
|
||||||
self.chats[chat_id] = {
|
self.chats[chat_id] = {
|
||||||
'id': chat_id,
|
'id': chat_id,
|
||||||
'title': title or f"Chat {len(self.chats) + 1}",
|
'title': chat_title,
|
||||||
'user_id': user_id,
|
'user_id': user_id,
|
||||||
'model_id': model_id or config.DEFAULT_MODEL,
|
'model_id': model_id or config.DEFAULT_MODEL,
|
||||||
'is_team_chat': is_team_chat,
|
'is_team_chat': is_team_chat,
|
||||||
'created_at': datetime.utcnow().isoformat(),
|
'created_at': datetime.now().isoformat(),
|
||||||
'updated_at': datetime.utcnow().isoformat(),
|
'updated_at': datetime.now().isoformat(),
|
||||||
'messages': [],
|
'messages': [],
|
||||||
'team_members': [user_id] if is_team_chat else []
|
'team_members': [user_id] if is_team_chat else [],
|
||||||
|
'openwebui_channel_id': openwebui_channel_id
|
||||||
}
|
}
|
||||||
|
|
||||||
# Save chats to file
|
# Save chats to file
|
||||||
@@ -96,20 +120,47 @@ class ChatService:
|
|||||||
if chat_id not in self.chats:
|
if chat_id not in self.chats:
|
||||||
raise ValueError(f"Chat with ID {chat_id} not found")
|
raise ValueError(f"Chat with ID {chat_id} not found")
|
||||||
|
|
||||||
|
chat = self.chats[chat_id]
|
||||||
|
|
||||||
# Create message data
|
# Create message data
|
||||||
message = {
|
message = {
|
||||||
'id': str(uuid.uuid4()),
|
'id': str(uuid.uuid4()),
|
||||||
'content': content,
|
'content': content,
|
||||||
'user_id': user_id if is_user_message else None,
|
'user_id': user_id if is_user_message else None,
|
||||||
'is_user_message': is_user_message,
|
'is_user_message': is_user_message,
|
||||||
'timestamp': datetime.utcnow().isoformat()
|
'timestamp': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add message to chat
|
# Add message to chat
|
||||||
self.chats[chat_id]['messages'].append(message)
|
chat['messages'].append(message)
|
||||||
|
|
||||||
# Update chat timestamp
|
# Update chat timestamp
|
||||||
self.chats[chat_id]['updated_at'] = datetime.utcnow().isoformat()
|
chat['updated_at'] = datetime.now().isoformat()
|
||||||
|
|
||||||
|
# If this is a team chat with an OpenWebUI channel, send the message there too
|
||||||
|
if chat['is_team_chat'] and 'openwebui_channel_id' in chat and chat['openwebui_channel_id']:
|
||||||
|
try:
|
||||||
|
# For AI responses, use a special AI user ID
|
||||||
|
# This ensures the AI's messages are properly distinguished in OpenWebUI
|
||||||
|
sender_id = "ai-assistant" if not is_user_message else user_id
|
||||||
|
|
||||||
|
# Format the message for OpenWebUI
|
||||||
|
if is_user_message:
|
||||||
|
# For user messages, use the regular format
|
||||||
|
formatted_content = content
|
||||||
|
else:
|
||||||
|
# For AI responses, add a special prefix to make it clear it's from the AI
|
||||||
|
# This helps visually distinguish AI responses in the channel
|
||||||
|
formatted_content = f"🤖 {content}"
|
||||||
|
|
||||||
|
# Send message to OpenWebUI channel
|
||||||
|
openwebui_channels.send_message(
|
||||||
|
channel_id=chat['openwebui_channel_id'],
|
||||||
|
message=formatted_content,
|
||||||
|
user_id=sender_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending message to OpenWebUI channel: {str(e)}")
|
||||||
|
|
||||||
# Save chats to file
|
# Save chats to file
|
||||||
self._save_chats()
|
self._save_chats()
|
||||||
@@ -140,7 +191,7 @@ class ChatService:
|
|||||||
"""
|
"""
|
||||||
user_chats = []
|
user_chats = []
|
||||||
|
|
||||||
for chat_id, chat in self.chats.items():
|
for _, chat in self.chats.items():
|
||||||
# Include private chats owned by the user
|
# Include private chats owned by the user
|
||||||
if chat['user_id'] == user_id and not chat['is_team_chat']:
|
if chat['user_id'] == user_id and not chat['is_team_chat']:
|
||||||
user_chats.append(chat)
|
user_chats.append(chat)
|
||||||
@@ -173,6 +224,21 @@ class ChatService:
|
|||||||
if not chat['is_team_chat']:
|
if not chat['is_team_chat']:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Add to OpenWebUI channel if available
|
||||||
|
if 'openwebui_channel_id' in chat and chat['openwebui_channel_id']:
|
||||||
|
try:
|
||||||
|
# Add member to OpenWebUI channel
|
||||||
|
openwebui_success = openwebui_channels.add_member(
|
||||||
|
channel_id=chat['openwebui_channel_id'],
|
||||||
|
user_id=user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if not openwebui_success:
|
||||||
|
print(f"Warning: Failed to add user {user_id} to OpenWebUI channel {chat['openwebui_channel_id']}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error adding member to OpenWebUI channel: {str(e)}")
|
||||||
|
|
||||||
|
# Add to local team members list
|
||||||
if user_id not in chat['team_members']:
|
if user_id not in chat['team_members']:
|
||||||
chat['team_members'].append(user_id)
|
chat['team_members'].append(user_id)
|
||||||
self._save_chats()
|
self._save_chats()
|
||||||
@@ -198,6 +264,21 @@ class ChatService:
|
|||||||
if not chat['is_team_chat']:
|
if not chat['is_team_chat']:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Remove from OpenWebUI channel if available
|
||||||
|
if 'openwebui_channel_id' in chat and chat['openwebui_channel_id']:
|
||||||
|
try:
|
||||||
|
# Remove member from OpenWebUI channel
|
||||||
|
openwebui_success = openwebui_channels.remove_member(
|
||||||
|
channel_id=chat['openwebui_channel_id'],
|
||||||
|
user_id=user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if not openwebui_success:
|
||||||
|
print(f"Warning: Failed to remove user {user_id} from OpenWebUI channel {chat['openwebui_channel_id']}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error removing member from OpenWebUI channel: {str(e)}")
|
||||||
|
|
||||||
|
# Remove from local team members list
|
||||||
if user_id in chat['team_members']:
|
if user_id in chat['team_members']:
|
||||||
chat['team_members'].remove(user_id)
|
chat['team_members'].remove(user_id)
|
||||||
self._save_chats()
|
self._save_chats()
|
||||||
@@ -217,6 +298,22 @@ class ChatService:
|
|||||||
if chat_id not in self.chats:
|
if chat_id not in self.chats:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
chat = self.chats[chat_id]
|
||||||
|
|
||||||
|
# Delete OpenWebUI channel if this is a team chat
|
||||||
|
if chat['is_team_chat'] and 'openwebui_channel_id' in chat and chat['openwebui_channel_id']:
|
||||||
|
try:
|
||||||
|
# Delete the OpenWebUI channel
|
||||||
|
openwebui_success = openwebui_channels.delete_channel(
|
||||||
|
channel_id=chat['openwebui_channel_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
if not openwebui_success:
|
||||||
|
print(f"Warning: Failed to delete OpenWebUI channel {chat['openwebui_channel_id']}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error deleting OpenWebUI channel: {str(e)}")
|
||||||
|
|
||||||
|
# Delete the chat from local storage
|
||||||
del self.chats[chat_id]
|
del self.chats[chat_id]
|
||||||
self._save_chats()
|
self._save_chats()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
"""
|
||||||
|
OpenWebUI channels integration for team chats.
|
||||||
|
|
||||||
|
This module provides functions to interact with OpenWebUI channels API
|
||||||
|
for creating and managing team chats through OpenWebUI's channels feature.
|
||||||
|
It also includes functionality to listen for and respond to messages in OpenWebUI channels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
|
||||||
|
from ai_service.config import config
|
||||||
|
|
||||||
|
class OpenWebUIChannels:
|
||||||
|
"""Class for interacting with OpenWebUI channels."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the OpenWebUI channels integration."""
|
||||||
|
self.openwebui_url = config.OPENWEBUI_URL
|
||||||
|
self.openwebui_api_key = config.OPENWEBUI_API_KEY
|
||||||
|
self.headers = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add API key if available
|
||||||
|
if self.openwebui_api_key:
|
||||||
|
self.headers["Authorization"] = f"Bearer {self.openwebui_api_key}"
|
||||||
|
|
||||||
|
def create_channel(self, name: str, description: str = "", is_private: bool = False) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Create a new channel in OpenWebUI.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the channel.
|
||||||
|
description: Description of the channel.
|
||||||
|
is_private: Whether the channel is private.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Channel data if creation was successful, None otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Prepare channel data
|
||||||
|
channel_data = {
|
||||||
|
"name": name,
|
||||||
|
"description": description,
|
||||||
|
"is_private": is_private
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make API request to create channel
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.openwebui_url}/api/channels",
|
||||||
|
headers=self.headers,
|
||||||
|
json=channel_data,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200 or response.status_code == 201:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Error creating channel: {response.status_code} - {response.text}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating channel: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_channel(self, channel_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get a channel by ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_id: ID of the channel.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Channel data if found, None otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Make API request to get channel
|
||||||
|
response = requests.get(
|
||||||
|
f"{self.openwebui_url}/api/channels/{channel_id}",
|
||||||
|
headers=self.headers,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Error getting channel: {response.status_code} - {response.text}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting channel: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_channels(self) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get all channels.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of channel data.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Make API request to get channels
|
||||||
|
response = requests.get(
|
||||||
|
f"{self.openwebui_url}/api/channels",
|
||||||
|
headers=self.headers,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Error getting channels: {response.status_code} - {response.text}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting channels: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def add_member(self, channel_id: str, user_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Add a user to a channel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_id: ID of the channel.
|
||||||
|
user_id: ID of the user to add.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if addition was successful, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Prepare member data
|
||||||
|
member_data = {
|
||||||
|
"user_id": user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make API request to add member
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.openwebui_url}/api/channels/{channel_id}/members",
|
||||||
|
headers=self.headers,
|
||||||
|
json=member_data,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.status_code == 200 or response.status_code == 201
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error adding member to channel: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def remove_member(self, channel_id: str, user_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Remove a user from a channel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_id: ID of the channel.
|
||||||
|
user_id: ID of the user to remove.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if removal was successful, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Make API request to remove member
|
||||||
|
response = requests.delete(
|
||||||
|
f"{self.openwebui_url}/api/channels/{channel_id}/members/{user_id}",
|
||||||
|
headers=self.headers,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.status_code == 200 or response.status_code == 204
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error removing member from channel: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_channel(self, channel_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Delete a channel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_id: ID of the channel.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if deletion was successful, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Make API request to delete channel
|
||||||
|
response = requests.delete(
|
||||||
|
f"{self.openwebui_url}/api/channels/{channel_id}",
|
||||||
|
headers=self.headers,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.status_code == 200 or response.status_code == 204
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error deleting channel: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def register_webhook(self, webhook_url: str) -> bool:
|
||||||
|
"""
|
||||||
|
Register a webhook to receive channel messages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
webhook_url: URL of the webhook endpoint.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if registration was successful, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Prepare webhook data
|
||||||
|
webhook_data = {
|
||||||
|
"url": webhook_url,
|
||||||
|
"events": ["channel_message"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make API request to register webhook
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.openwebui_url}/api/webhooks",
|
||||||
|
headers=self.headers,
|
||||||
|
json=webhook_data,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200 or response.status_code == 201:
|
||||||
|
print(f"Successfully registered webhook: {webhook_url}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"Error registering webhook: {response.status_code} - {response.text}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error registering webhook: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_message(self, channel_id: str, message: str, user_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Send a message to a channel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_id: ID of the channel.
|
||||||
|
message: Message content.
|
||||||
|
user_id: ID of the user sending the message.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Message data if sending was successful, None otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Prepare message data
|
||||||
|
message_data = {
|
||||||
|
"content": message,
|
||||||
|
"user_id": user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make API request to send message
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.openwebui_url}/api/channels/{channel_id}/messages",
|
||||||
|
headers=self.headers,
|
||||||
|
json=message_data,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200 or response.status_code == 201:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Error sending message to channel: {response.status_code} - {response.text}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending message to channel: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Create a singleton instance
|
||||||
|
openwebui_channels = OpenWebUIChannels()
|
||||||
Reference in New Issue
Block a user