From 3d372dafae6c736402577e5ce9f2823e0d1a3b5b Mon Sep 17 00:00:00 2001 From: "huangzhelong.byte" Date: Sat, 5 Apr 2025 17:34:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=85=A8=E9=83=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=9A=84=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/App.vue | 15 +-- app/src/api/message-bridge.ts | 42 +++++--- app/src/components/main-panel/index.vue | 11 ++- app/src/components/main-panel/panel.ts | 61 +++++++----- .../components/main-panel/resource/index.vue | 5 +- app/src/hook/setting.ts | 42 +++++++- app/src/i18n/ar.json | 11 ++- app/src/i18n/de.json | 11 ++- app/src/i18n/en.json | 11 ++- app/src/i18n/fr.json | 11 ++- app/src/i18n/ja.json | 11 ++- app/src/i18n/ko.json | 11 ++- app/src/i18n/ru.json | 11 ++- app/src/i18n/zh-cn.json | 11 ++- app/src/i18n/zh-tw.json | 11 ++- app/src/views/debug/welcome.vue | 14 +-- app/src/views/setting/api.vue | 29 +++++- app/src/views/setting/general.vue | 3 + app/src/views/setting/llm.ts | 99 +------------------ app/src/views/setting/setting-section.ts | 6 +- test/src/llm.ts | 93 +++++++++++++++++ test/src/util.ts | 19 ++-- 22 files changed, 335 insertions(+), 203 deletions(-) create mode 100644 test/src/llm.ts diff --git a/app/src/App.vue b/app/src/App.vue index fa3b17f..49934d9 100644 --- a/app/src/App.vue +++ b/app/src/App.vue @@ -15,6 +15,7 @@ import { setDefaultCss } from './hook/css'; import { pinkLog } from './views/setting/util'; import { acquireVsCodeApi, useMessageBridge } from './api/message-bridge'; import { connectionArgs, connectionMethods, connectionResult, doConnect } from './views/connect/connection'; +import { loadSetting } from './hook/setting'; const bridge = useMessageBridge(); @@ -25,21 +26,10 @@ bridge.addCommandListener('hello', data => { }, { once: true }); -// 发送消息 -const sendPing = () => { - bridge.postMessage({ - command: 'ping', - data: { timestamp: Date.now() } - }); -}; - - onMounted(() => { // 初始化 css setDefaultCss(); - // 初始化 设置 - document.addEventListener('click', () => { Connection.showPanel = false; }); @@ -58,6 +48,9 @@ onMounted(() => { }, { once: true }); setTimeout(() => { + // 初始化 设置 + loadSetting(); + doConnect(); }, 200); } diff --git a/app/src/api/message-bridge.ts b/app/src/api/message-bridge.ts index b91c875..cb1a5d4 100644 --- a/app/src/api/message-bridge.ts +++ b/app/src/api/message-bridge.ts @@ -13,7 +13,7 @@ export type CommandHandler = (data: any) => void; export const acquireVsCodeApi = (window as any)['acquireVsCodeApi']; interface AddCommandListenerOption { - once: boolean // 只调用一次就销毁 + once: boolean // 只调用一次就销毁 } class MessageBridge { @@ -71,10 +71,10 @@ class MessageBridge { this.isConnected.value = false; }; - this.postMessage = (message) => { + this.postMessage = (message) => { if (this.ws?.readyState === WebSocket.OPEN) { console.log(message); - + this.ws.send(JSON.stringify(message)); } }; @@ -95,27 +95,45 @@ class MessageBridge { public postMessage(message: VSCodeMessage) { throw new Error('PostMessage not initialized'); } - + /** - * @description 注册一个命令的执行器 - * @returns + * @description 注册一个命令的执行器(支持一次性或持久监听) + * @example + * // 基本用法(持久监听) + * const removeListener = bridge.addCommandListener('message', (data) => { + * console.log('收到消息:', data.msg.text); + * }, { once: false }); + * + * // 稍后取消监听 + * removeListener(); + * + * @example + * // 一次性监听(自动移除) + * bridge.addCommandListener('connect', (data) => { + * const { code, msg } = data; + * console.log(`连接结果: ${code === 200 ? '成功' : '失败'}`); + * }, { once: true }); */ - public addCommandListener(command: string, commandHandler: CommandHandler, option: AddCommandListenerOption) { + public addCommandListener( + command: string, + commandHandler: CommandHandler, + option: AddCommandListenerOption + ): () => boolean { if (!this.handlers.has(command)) { this.handlers.set(command, new Set()); } const commandHandlers = this.handlers.get(command)!; - const wrapperCommandHandler = option.once ? (data: any) => { - commandHandler(data); - commandHandlers.delete(wrapperCommandHandler); - } : commandHandler; + const wrapperCommandHandler = option.once ? (data: any) => { + commandHandler(data); + commandHandlers.delete(wrapperCommandHandler); + } : commandHandler; commandHandlers.add(wrapperCommandHandler); return () => commandHandlers.delete(wrapperCommandHandler); } - public destroy() { + public destroy() { this.ws?.close(); this.handlers.clear(); } diff --git a/app/src/components/main-panel/index.vue b/app/src/components/main-panel/index.vue index 53d86df..8801a1c 100644 --- a/app/src/components/main-panel/index.vue +++ b/app/src/components/main-panel/index.vue @@ -12,7 +12,7 @@ > - {{ tab.name }} + {{ tab.name }} - - @@ -83,6 +81,7 @@ defineComponent({ name: 'main-panel' }); } .tabs-container .tab { + white-space: nowrap; margin: 5px; font-size: 13px; width: 120px; @@ -101,6 +100,12 @@ defineComponent({ name: 'main-panel' }); align-items: center; } +.tabs-container .tab .tab-name { + max-width: 70px; + overflow: hidden; + text-overflow: ellipsis; +} + .tabs-container .tab:hover { background-color: var(--input-active-background); } diff --git a/app/src/components/main-panel/panel.ts b/app/src/components/main-panel/panel.ts index f4e7ed5..ba726a3 100644 --- a/app/src/components/main-panel/panel.ts +++ b/app/src/components/main-panel/panel.ts @@ -4,46 +4,61 @@ import Resource from './resource/index.vue'; import Chat from './chat/index.vue'; import Prompt from './prompt/index.vue'; import Tool from './tool/index.vue'; +import I18n from '@/i18n/index'; + +const { t } = I18n.global; interface Tab { name: string; icon: string; type: string; - component: any; - storage: Record; + component: any; + storage: Record; } export const debugModes = [ - Resource, Prompt, Tool, Chat + Resource, Prompt, Tool, Chat ] // TODO: 实现对于 tabs 这个数据的可持久化 -export const tabs = reactive({ +export const tabs = reactive<{ + content: Tab[] + activeIndex: number + activeTab: Tab +}>({ content: [ - { - name: '空白测试 1', - icon: 'icon-blank', - type: 'blank', - component: undefined, - storage: {} - } - ] as Tab[], + createTab('blank', 1) + ], activeIndex: 0, - get activeTab() { - return this.content[this.activeIndex]; - } + get activeTab() { + return this.content[this.activeIndex]; + } }); let tabCounter = 1; -export function addNewTab() { - const newTab = { - name: `空白测试 ${++ tabCounter}`, +function createTab(type: string, index: number): Tab { + let customName: string | null = null; + + return { + get name() { + if (customName !== null) { + return customName; + } + return t('blank-test') + ` ${index}`; + }, + set name(value: string) { + customName = value; // 允许外部修改 name + }, icon: 'icon-blank', - type: 'blank', - component: undefined, - storage: {} + type, + component: undefined, + storage: {}, }; +} + +export function addNewTab() { + const newTab = createTab('blank', ++tabCounter); tabs.content.push(newTab); tabs.activeIndex = tabs.content.length - 1; } @@ -56,9 +71,9 @@ export function setActiveTab(index: number) { export function closeTab(index: number) { if (tabs.content.length <= 1) return; // 至少保留一个标签页 - + tabs.content.splice(index, 1); - + // 调整活动标签索引 if (tabs.activeIndex >= index) { tabs.activeIndex = Math.max(0, tabs.activeIndex - 1); diff --git a/app/src/components/main-panel/resource/index.vue b/app/src/components/main-panel/resource/index.vue index af6cb4a..4656d00 100644 --- a/app/src/components/main-panel/resource/index.vue +++ b/app/src/components/main-panel/resource/index.vue @@ -3,7 +3,7 @@

- 资源模块 + {{ t("resources") + t("module") }}

resources/templates/list

@@ -26,10 +26,13 @@ \ No newline at end of file diff --git a/app/src/views/setting/general.vue b/app/src/views/setting/general.vue index 2998805..8cbe093 100644 --- a/app/src/views/setting/general.vue +++ b/app/src/views/setting/general.vue @@ -21,6 +21,7 @@ import { defineComponent, ref } from 'vue'; import { languageSetting } from './language'; import { useI18n } from 'vue-i18n'; +import { saveSetting } from '@/hook/setting'; defineComponent({ name: 'appearance' }); @@ -34,6 +35,8 @@ function onlanguagechange(code: string) { currentLanguage.value = option.text; } // languageDialogShow.value = true; + + saveSetting(); } diff --git a/app/src/views/setting/llm.ts b/app/src/views/setting/llm.ts index d18b23f..ba12cb9 100644 --- a/app/src/views/setting/llm.ts +++ b/app/src/views/setting/llm.ts @@ -1,104 +1,13 @@ import { reactive } from 'vue'; +import { pinkLog } from './util'; +import { saveSetting } from '@/hook/setting'; -export const llms = reactive([ - { - id: 'deepseek', - name: 'DeepSeek', - baseUrl: 'https://api.deepseek.com/v1', - models: ['deepseek-chat', 'deepseek-coder', 'deepseek-math'], - icon: '/images/deepseek.com.ico', - provider: 'DeepSeek', - isOpenAICompatible: true, - description: '深度求索推出的大模型,擅长中文和代码', - website: 'https://www.deepseek.com', - userToken: '', - userModel: 'deepseek-chat' - }, - { - id: 'openai', - name: 'OpenAI', - baseUrl: 'https://api.openai.com/v1', - models: ['gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'], - icon: '/images/openai.com.ico', - provider: 'OpenAI', - isOpenAICompatible: true, - description: 'OpenAI官方API', - website: 'https://openai.com', - userToken: '', - userModel: 'gpt-4-turbo' - }, - { - id: 'mistral', - name: 'Mistral', - baseUrl: 'https://api.mistral.ai/v1', - models: ['mistral-tiny', 'mistral-small', 'mistral-medium'], - icon: '/images/mistral.ai.ico', - provider: 'Mistral AI', - isOpenAICompatible: true, - description: '欧洲开源大模型代表', - website: 'https://mistral.ai', - userToken: '', - userModel: 'mistral-tiny' - }, - { - id: 'ollama', - name: 'Ollama (Local)', - baseUrl: 'http://localhost:11434/v1', - models: ['llama2', 'mistral', 'codellama'], - icon: '/images/ollama.png', - provider: 'Ollama', - isOpenAICompatible: true, - description: '本地运行的大模型', - website: 'https://ollama.com', - userToken: '', - userModel: 'llama2' - }, - { - id: 'groq', - name: 'Groq', - baseUrl: 'https://api.groq.com/openai/v1', - models: ['mixtral-8x7b-32768', 'llama2-70b-4096'], - icon: '/images/grok.com.png', - provider: 'Groq', - isOpenAICompatible: true, - description: '超高速推理API', - website: 'https://groq.com', - userToken: '', - userModel: 'mixtral-8x7b-32768' - }, - { - id: 'perplexity', - name: 'Perplexity', - baseUrl: 'https://api.perplexity.ai/v1', - models: ['pplx-7b-online', 'pplx-70b-online'], - icon: '/images/perplexity.ai.ico', - provider: 'Perplexity AI', - isOpenAICompatible: true, - description: '联网搜索增强的大模型', - website: 'https://www.perplexity.ai', - userToken: '', - userModel: 'pplx-7b-online' - }, - { - id: 'kimi', - name: 'Kimi Chat', - baseUrl: 'https://api.moonshot.cn/v1', - models: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'], - icon: '/images/kimichat.cn.png', - provider: '月之暗面 (Moonshot AI)', - isOpenAICompatible: true, - description: '支持超长上下文的中文大模型,上下文窗口高达128K', - website: 'https://kimi.moonshot.cn', - userToken: '', - userModel: 'moonshot-v1-8k' - } -]); +export const llms = reactive([]); export const llmManager = reactive({ currentModelIndex: 0, }); export function onmodelchange() { - console.log(); - + pinkLog('切换模型到:' + llms[llmManager.currentModelIndex].id); } \ No newline at end of file diff --git a/app/src/views/setting/setting-section.ts b/app/src/views/setting/setting-section.ts index f806fe9..e8a238b 100644 --- a/app/src/views/setting/setting-section.ts +++ b/app/src/views/setting/setting-section.ts @@ -1,4 +1,4 @@ -import { reactive } from 'vue'; +import { reactive, computed } from 'vue'; import I18n from '@/i18n/index'; @@ -13,11 +13,11 @@ export const settingSections = reactive({ }, { value: 'general', - label: t('general-setting') + label: computed(() => t('general-setting')) }, { value: 'appearance', - label: t('appearance-setting') + label: computed(() => t('appearance-setting')) } ] }); diff --git a/test/src/llm.ts b/test/src/llm.ts new file mode 100644 index 0000000..a7f8885 --- /dev/null +++ b/test/src/llm.ts @@ -0,0 +1,93 @@ +export const llms = [ + { + id: 'deepseek', + name: 'DeepSeek', + baseUrl: 'https://api.deepseek.com/v1', + models: ['deepseek-chat', 'deepseek-coder', 'deepseek-math'], + icon: '/images/deepseek.com.ico', + provider: 'DeepSeek', + isOpenAICompatible: true, + description: '深度求索推出的大模型,擅长中文和代码', + website: 'https://www.deepseek.com', + userToken: '', + userModel: 'deepseek-chat' + }, + { + id: 'openai', + name: 'OpenAI', + baseUrl: 'https://api.openai.com/v1', + models: ['gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'], + icon: '/images/openai.com.ico', + provider: 'OpenAI', + isOpenAICompatible: true, + description: 'OpenAI官方API', + website: 'https://openai.com', + userToken: '', + userModel: 'gpt-4-turbo' + }, + { + id: 'mistral', + name: 'Mistral', + baseUrl: 'https://api.mistral.ai/v1', + models: ['mistral-tiny', 'mistral-small', 'mistral-medium'], + icon: '/images/mistral.ai.ico', + provider: 'Mistral AI', + isOpenAICompatible: true, + description: '欧洲开源大模型代表', + website: 'https://mistral.ai', + userToken: '', + userModel: 'mistral-tiny' + }, + { + id: 'ollama', + name: 'Ollama (Local)', + baseUrl: 'http://localhost:11434/v1', + models: ['llama2', 'mistral', 'codellama'], + icon: '/images/ollama.png', + provider: 'Ollama', + isOpenAICompatible: true, + description: '本地运行的大模型', + website: 'https://ollama.com', + userToken: '', + userModel: 'llama2' + }, + { + id: 'groq', + name: 'Groq', + baseUrl: 'https://api.groq.com/openai/v1', + models: ['mixtral-8x7b-32768', 'llama2-70b-4096'], + icon: '/images/grok.com.png', + provider: 'Groq', + isOpenAICompatible: true, + description: '超高速推理API', + website: 'https://groq.com', + userToken: '', + userModel: 'mixtral-8x7b-32768' + }, + { + id: 'perplexity', + name: 'Perplexity', + baseUrl: 'https://api.perplexity.ai/v1', + models: ['pplx-7b-online', 'pplx-70b-online'], + icon: '/images/perplexity.ai.ico', + provider: 'Perplexity AI', + isOpenAICompatible: true, + description: '联网搜索增强的大模型', + website: 'https://www.perplexity.ai', + userToken: '', + userModel: 'pplx-7b-online' + }, + { + id: 'kimi', + name: 'Kimi Chat', + baseUrl: 'https://api.moonshot.cn/v1', + models: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'], + icon: '/images/kimichat.cn.png', + provider: '月之暗面 (Moonshot AI)', + isOpenAICompatible: true, + description: '支持超长上下文的中文大模型,上下文窗口高达128K', + website: 'https://kimi.moonshot.cn', + userToken: '', + userModel: 'moonshot-v1-8k' + } +]; \ No newline at end of file diff --git a/test/src/util.ts b/test/src/util.ts index 05ec8d7..7ec1f92 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; +import { llms } from './llm'; function getConfigurationPath() { // 如果是 vscode 插件下,则修改为 ~/.openmcp/config.json @@ -16,17 +17,23 @@ function getConfigurationPath() { return 'config.json'; } +function getDefaultLanguage() { + if (process.env.VSCODE_PID) { + // TODO: 获取 vscode 内部的语言 + + } + return 'zh'; +} + interface IConfig { - MODEL_BASE_URL: string; - MODEL_NAME: string; - API_TOKEN: string; + MODEL_INDEX: number; [key: string]: any; } const DEFAULT_CONFIG: IConfig = { - MODEL_BASE_URL: '', - MODEL_NAME: '', - API_TOKEN: '' + MODEL_INDEX: 0, + LLM_INFO: llms, + LANG: getDefaultLanguage() }; function createConfig(): IConfig {