From a65356113e0aee6012aacaf868f2ac5acca8e6a7 Mon Sep 17 00:00:00 2001 From: ViperEkura <3081035982@qq.com> Date: Mon, 13 Apr 2026 17:11:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/App.vue | 9 +- dashboard/src/components/ProcessBlock.vue | 52 ++ dashboard/src/style.css | 109 ++++- dashboard/src/utils/api.js | 2 +- .../src/views/ConversationDetailView.vue | 41 +- dashboard/src/views/ConversationsView.vue | 156 +++--- dashboard/src/views/HomeView.vue | 173 +++++-- dashboard/src/views/SettingsView.vue | 450 ++++++++++++------ dashboard/src/views/ToolsView.vue | 126 ++--- luxx/routes/messages.py | 5 +- luxx/services/chat.py | 32 +- luxx/services/llm_client.py | 3 + 12 files changed, 829 insertions(+), 329 deletions(-) diff --git a/dashboard/src/App.vue b/dashboard/src/App.vue index baf22da..f94b65e 100644 --- a/dashboard/src/App.vue +++ b/dashboard/src/App.vue @@ -18,7 +18,14 @@ const sidebarCollapsed = ref(false) diff --git a/dashboard/src/views/HomeView.vue b/dashboard/src/views/HomeView.vue index 8059505..592dc4b 100644 --- a/dashboard/src/views/HomeView.vue +++ b/dashboard/src/views/HomeView.vue @@ -1,22 +1,29 @@ @@ -24,44 +31,124 @@ import { ref, onMounted } from 'vue' import { conversationsAPI, toolsAPI } from '../utils/api.js' -const stats = ref({ conversations: 0, tools: 0, messages: 0, models: 1 }) +const stats = ref({ conversations: 0, tools: 0, totalTokens: 0 }) + +const formatTokens = (n) => { + if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M' + if (n >= 1000) return (n / 1000).toFixed(1) + 'K' + return n.toString() +} onMounted(async () => { try { const [convs, tools] = await Promise.allSettled([ - conversationsAPI.list({ page: 1, page_size: 1 }), + conversationsAPI.list({ page: 1, page_size: 100 }), toolsAPI.list() ]) - if (convs.status === 'fulfilled' && convs.value.success) stats.value.conversations = convs.value.data?.total || 0 - if (tools.status === 'fulfilled' && tools.value.success) { - const t = tools.value.data?.tools || tools.value.data || [] - stats.value.tools = Array.isArray(t) ? t.length : 0 + if (convs.status === 'fulfilled' && convs.value.success) { + stats.value.conversations = convs.value.data?.total || 0 + const items = convs.value.data?.items || [] + stats.value.totalTokens = items.reduce((sum, c) => sum + (c.token_count || 0), 0) + } + if (tools.status === 'fulfilled' && tools.value.success) { + const data = tools.value.data?.categorized || {} + let total = 0 + Object.values(data).forEach(arr => { + if (Array.isArray(arr)) total += arr.length + }) + stats.value.tools = total } - stats.value.messages = stats.value.conversations * 5 } catch (e) { console.error(e) } }) diff --git a/dashboard/src/views/SettingsView.vue b/dashboard/src/views/SettingsView.vue index 25dd9c9..8a68eda 100644 --- a/dashboard/src/views/SettingsView.vue +++ b/dashboard/src/views/SettingsView.vue @@ -1,19 +1,182 @@ diff --git a/dashboard/src/views/ToolsView.vue b/dashboard/src/views/ToolsView.vue index 6af87ba..628d635 100644 --- a/dashboard/src/views/ToolsView.vue +++ b/dashboard/src/views/ToolsView.vue @@ -1,35 +1,46 @@ @@ -41,7 +52,6 @@ import { toolsAPI } from '../utils/api.js' const list = ref([]) const loading = ref(true) const error = ref('') -const detail = ref(null) const fetchData = async () => { loading.value = true @@ -55,7 +65,7 @@ const fetchData = async () => { if (Array.isArray(data[cat])) { all.push(...data[cat].map(t => { const func = t.function ? t.function : t - return { name: func.name, description: func.description, parameters: func.parameters, category: cat, enabled: true } + return { name: func.name, description: func.description, parameters: func.parameters, category: cat, enabled: t.enabled !== false } })) } }) @@ -65,44 +75,34 @@ const fetchData = async () => { finally { loading.value = false } } -const showDetail = (tool) => { detail.value = tool } +const toggleEnabled = async (tool) => { + tool.enabled = !tool.enabled +} onMounted(fetchData) diff --git a/luxx/routes/messages.py b/luxx/routes/messages.py index 3048f17..44ed900 100644 --- a/luxx/routes/messages.py +++ b/luxx/routes/messages.py @@ -1,5 +1,6 @@ """Message routes""" import json +from typing import List, Optional from fastapi import APIRouter, Depends, Response from fastapi.responses import StreamingResponse from pydantic import BaseModel @@ -20,6 +21,7 @@ class MessageCreate(BaseModel): """Create message model""" conversation_id: str content: str + thinking_enabled: bool = False class MessageResponse(BaseModel): @@ -113,7 +115,6 @@ def send_message( @router.post("/stream") async def stream_message( data: MessageCreate, - tools_enabled: bool = True, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): @@ -141,7 +142,7 @@ async def stream_message( async for sse_str in chat_service.stream_response( conversation=conversation, user_message=data.content, - tools_enabled=tools_enabled + thinking_enabled=data.thinking_enabled ): # Chat service returns raw SSE strings (including done event) yield sse_str diff --git a/luxx/services/chat.py b/luxx/services/chat.py index 9eb1a2c..cf304d2 100644 --- a/luxx/services/chat.py +++ b/luxx/services/chat.py @@ -1,7 +1,7 @@ """Chat service module""" import json import uuid -from typing import List, Dict, Any, AsyncGenerator +from typing import List, Dict, Any, AsyncGenerator, Optional from luxx.models import Conversation, Message from luxx.tools.executor import ToolExecutor @@ -97,7 +97,7 @@ class ChatService: self, conversation: Conversation, user_message: str, - tools_enabled: bool = True + thinking_enabled: bool = False ) -> AsyncGenerator[Dict[str, str], None]: """ Streaming response generator @@ -112,7 +112,8 @@ class ChatService: "content": json.dumps({"text": user_message, "attachments": []}) }) - tools = registry.list_all() if tools_enabled else None + # Get all available tools + tools = registry.list_all() llm, provider_max_tokens = get_llm_client(conversation) model = conversation.model or llm.default_model or "gpt-4" @@ -150,7 +151,8 @@ class ChatService: messages=messages, tools=tools, temperature=conversation.temperature, - max_tokens=max_tokens or 8192 + max_tokens=max_tokens or 8192, + thinking_enabled=thinking_enabled or conversation.thinking_enabled ): # Parse SSE line # Format: "event: xxx\ndata: {...}\n\n" @@ -179,11 +181,31 @@ class ChatService: try: chunk = json.loads(data_str) except json.JSONDecodeError: - continue + yield _sse_event("error", {"content": f"Failed to parse response: {data_str}"}) + return + + # Check for error in response + if "error" in chunk: + error_msg = chunk["error"].get("message", str(chunk["error"])) + yield _sse_event("error", {"content": f"API Error: {error_msg}"}) + return # Get delta choices = chunk.get("choices", []) if not choices: + # Check if there's any content in the response + if chunk.get("content") or chunk.get("message"): + content = chunk.get("content") or chunk.get("message", {}).get("content", "") + if content: + yield _sse_event("process_step", { + "step": { + "id": f"step-{step_index}", + "index": step_index, + "type": "text", + "content": content + } + }) + step_index += 1 continue delta = choices[0].get("delta", {}) diff --git a/luxx/services/llm_client.py b/luxx/services/llm_client.py index 257f0a9..5dcd6de 100644 --- a/luxx/services/llm_client.py +++ b/luxx/services/llm_client.py @@ -78,6 +78,9 @@ class LLMClient: if "max_tokens" in kwargs: body["max_tokens"] = kwargs["max_tokens"] + if "thinking_enabled" in kwargs and kwargs["thinking_enabled"]: + body["thinking_enabled"] = True + if tools: body["tools"] = tools