From 73a5b05a5d6075636f3ef2a4622cecaa2a2511ed Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Fri, 2 May 2025 23:39:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B2=A1=E6=9C=89=E5=A1=AB?= =?UTF-8?q?=E5=86=99=20token=20=E6=8F=90=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- renderer/src/App.vue | 10 ++++ .../src/components/main-panel/chat/chat.ts | 54 +++++++++++++++---- .../components/main-panel/chat/task-loop.ts | 20 ++++++- service/src/llm/llm.service.ts | 5 +- src/sidebar/installed.service.ts | 22 ++++---- 5 files changed, 89 insertions(+), 22 deletions(-) diff --git a/renderer/src/App.vue b/renderer/src/App.vue index 0f855ca..3ea6abc 100644 --- a/renderer/src/App.vue +++ b/renderer/src/App.vue @@ -24,6 +24,8 @@ import { getPlatform } from './api/platform'; import Tour from '@/components/guide/tour.vue'; import { userHasReadGuide } from './components/guide/tour'; +import { ElLoading } from 'element-plus'; + const bridge = useMessageBridge(); // 监听所有消息 @@ -36,6 +38,13 @@ const route = useRoute(); const router = useRouter(); onMounted(async () => { + const loading = ElLoading.service({ + fullscreen: true, + lock: true, + text: 'Loading', + background: 'rgba(0, 0, 0, 0.7)' + }); + // 初始化 css setDefaultCss(); @@ -78,6 +87,7 @@ onMounted(async () => { // loading panels await loadPanels(); + loading.close(); }); diff --git a/renderer/src/components/main-panel/chat/chat.ts b/renderer/src/components/main-panel/chat/chat.ts index 9a0e6bb..8e5348c 100644 --- a/renderer/src/components/main-panel/chat/chat.ts +++ b/renderer/src/components/main-panel/chat/chat.ts @@ -1,18 +1,41 @@ -import { ToolItem } from "@/hook/type"; -import { ref } from "vue"; +import { ToolCallContent, ToolItem } from "@/hook/type"; +import { Ref, ref } from "vue"; import type { OpenAI } from 'openai'; type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk; -export interface IExtraInfo { - created: number, - serverName: string, - usage?: ChatCompletionChunk['usage']; - [key: string]: any +export enum MessageState { + ServerError = 'server internal error', + ReceiveChunkError = 'receive chunk error', + Timeout = 'timeout', + MaxEpochs = 'max epochs', + Unknown = 'unknown error', + Abort = 'abort', + ToolCall = 'tool call failed', + None = 'none', + Success = 'success', + ParseJsonError = 'parse json error' } -export interface ChatMessage { - role: 'user' | 'assistant' | 'system' | 'tool'; +export interface IExtraInfo { + created: number, + state: MessageState, + serverName: string, + usage?: ChatCompletionChunk['usage']; + [key: string]: any; +} + +export interface ToolMessage { + role: 'tool'; + content: ToolCallContent[]; + tool_call_id?: string + name?: string // 工具名称,当 role 为 tool + tool_calls?: ToolCall[], + extraInfo: IExtraInfo +} + +export interface TextMessage { + role: 'user' | 'assistant' | 'system'; content: string; tool_call_id?: string name?: string // 工具名称,当 role 为 tool @@ -20,6 +43,8 @@ export interface ChatMessage { extraInfo: IExtraInfo } +export type ChatMessage = ToolMessage | TextMessage; + // 新增状态和工具数据 interface EnableToolItem { name: string; @@ -53,6 +78,15 @@ export interface ToolCall { export const allTools = ref([]); +export interface IRenderMessage { + role: 'user' | 'assistant/content' | 'assistant/tool_calls' | 'tool'; + content: string; + toolResult?: ToolCallContent[]; + tool_calls?: ToolCall[]; + showJson?: Ref; + extraInfo: IExtraInfo; +} + export function getToolSchema(enableTools: EnableToolItem[]) { const toolsSchema = []; for (let i = 0; i < enableTools.length; i++) { @@ -70,4 +104,4 @@ export function getToolSchema(enableTools: EnableToolItem[]) { } } return toolsSchema; -} \ No newline at end of file +} diff --git a/renderer/src/components/main-panel/chat/task-loop.ts b/renderer/src/components/main-panel/chat/task-loop.ts index 9d84bc2..048e2da 100644 --- a/renderer/src/components/main-panel/chat/task-loop.ts +++ b/renderer/src/components/main-panel/chat/task-loop.ts @@ -6,6 +6,7 @@ import type { OpenAI } from 'openai'; import { callTool } from "../tool/tools"; import { llmManager, llms } from "@/views/setting/llm"; import { pinkLog, redLog } from "@/views/setting/util"; +import { ElMessage } from "element-plus"; export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk; export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string }; @@ -200,9 +201,19 @@ export class TaskLoop { }); } - public makeChatData(tabStorage: ChatStorage): ChatCompletionCreateParamsBase { + public makeChatData(tabStorage: ChatStorage): ChatCompletionCreateParamsBase | undefined { const baseURL = llms[llmManager.currentModelIndex].baseUrl; - const apiKey = llms[llmManager.currentModelIndex].userToken; + const apiKey = llms[llmManager.currentModelIndex].userToken || ''; + + if (apiKey.trim() === '') { + + if (tabStorage.messages.length > 0 && tabStorage.messages[tabStorage.messages.length - 1].role === 'user') { + tabStorage.messages.pop(); + ElMessage.error('请先设置 API Key'); + } + return undefined; + } + const model = llms[llmManager.currentModelIndex].userModel; const temperature = tabStorage.settings.temperature; const tools = getToolSchema(tabStorage.settings.enableTools); @@ -289,6 +300,11 @@ export class TaskLoop { // 构造 chatData const chatData = this.makeChatData(tabStorage); + if (!chatData) { + this.onDone(); + break; + } + this.currentChatId = chatData.id!; // 发送请求 diff --git a/service/src/llm/llm.service.ts b/service/src/llm/llm.service.ts index a793ad0..11e85cf 100644 --- a/service/src/llm/llm.service.ts +++ b/service/src/llm/llm.service.ts @@ -24,7 +24,7 @@ export async function streamingChatCompletion( } await postProcessMessages(messages); - + const stream = await client.chat.completions.create({ model, messages, @@ -57,6 +57,9 @@ export async function streamingChatCompletion( }); break; } + + console.log(chunk); + if (chunk.choices) { const chunkResult = { diff --git a/src/sidebar/installed.service.ts b/src/sidebar/installed.service.ts index 272a7a3..e16f9ff 100644 --- a/src/sidebar/installed.service.ts +++ b/src/sidebar/installed.service.ts @@ -1,5 +1,5 @@ import { getConnectionConfig, IConnectionItem, panels, saveConnectionConfig, getFirstValidPathFromCommand } from "../global"; - +import { exec, spawn } from 'node:child_process'; import * as vscode from 'vscode'; export async function deleteInstalledConnection(item: IConnectionItem) { @@ -35,16 +35,18 @@ export async function deleteInstalledConnection(item: IConnectionItem) { } } -export async function validateAndGetCommandPath(command: string, cwd?: string): Promise { - const { exec } = require('child_process'); - const { promisify } = require('util'); - const execAsync = promisify(exec); - +export async function validateAndGetCommandPath(commandString: string, cwd?: string): Promise { try { - const { stdout } = await execAsync(`which ${command.split(' ')[0]}`, { cwd }); - return stdout.trim(); + const commands = commandString.split(' '); + const command = commands[0]; + const args = commands.slice(1); + const process = spawn(command, args || [], { shell: true, cwd }); + process.disconnect(); + + return ''; } catch (error) { - throw new Error(`无法找到命令: ${command.split(' ')[0]}`); + console.log(error); + throw new Error(`无法找到命令: ${commandString.split(' ')[0]}`); } } @@ -89,6 +91,8 @@ export async function acquireInstalledConnection(): Promise