完成标签页的保存

This commit is contained in:
huangzhelong.byte 2025-04-05 18:18:34 +08:00
parent 3d372dafae
commit b156244763
9 changed files with 309 additions and 11 deletions

View File

@ -16,6 +16,7 @@ import { pinkLog } from './views/setting/util';
import { acquireVsCodeApi, useMessageBridge } from './api/message-bridge'; import { acquireVsCodeApi, useMessageBridge } from './api/message-bridge';
import { connectionArgs, connectionMethods, connectionResult, doConnect } from './views/connect/connection'; import { connectionArgs, connectionMethods, connectionResult, doConnect } from './views/connect/connection';
import { loadSetting } from './hook/setting'; import { loadSetting } from './hook/setting';
import { loadPanels } from './hook/panel';
const bridge = useMessageBridge(); const bridge = useMessageBridge();
@ -47,11 +48,19 @@ onMounted(() => {
connectionResult.logString = msg; connectionResult.logString = msg;
}, { once: true }); }, { once: true });
setTimeout(() => { setTimeout(() => {
// //
loadSetting(); loadSetting();
// tab
loadPanels();
doConnect(); doConnect();
// 200 ws ws
//
}, 200); }, 200);
} }
}); });

View File

@ -24,7 +24,7 @@
<span <span
class="add-button iconfont icon-add" class="add-button iconfont icon-add"
@click="addNewTab" @click="pageAddNewTab"
> >
</span> </span>
</div> </div>
@ -38,9 +38,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { addNewTab, tabs, setActiveTab, closeTab } from './panel'; import { addNewTab, tabs, setActiveTab, closeTab } from './panel';
defineComponent({ name: 'main-panel' }); defineComponent({ name: 'main-panel' });
const route = useRoute();
const router = useRouter();
function pageAddNewTab() {
addNewTab();
// debug debug
if (route.name !== 'debug') {
router.push('/debug');
}
}
</script> </script>
<style> <style>

View File

@ -1,10 +1,12 @@
import { reactive } from 'vue'; import { watch, reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import Resource from './resource/index.vue'; import Resource from './resource/index.vue';
import Chat from './chat/index.vue'; import Chat from './chat/index.vue';
import Prompt from './prompt/index.vue'; import Prompt from './prompt/index.vue';
import Tool from './tool/index.vue'; import Tool from './tool/index.vue';
import I18n from '@/i18n/index'; import I18n from '@/i18n/index';
import { safeSavePanels, savePanels } from '@/hook/panel';
const { t } = I18n.global; const { t } = I18n.global;
@ -13,6 +15,7 @@ interface Tab {
icon: string; icon: string;
type: string; type: string;
component: any; component: any;
componentIndex: number;
storage: Record<string, any>; storage: Record<string, any>;
} }
@ -37,6 +40,17 @@ export const tabs = reactive<{
let tabCounter = 1; let tabCounter = 1;
// 监控 tabs
watch(
() => tabs,
(newValue, oldValue) => {
console.log('state change');
safeSavePanels();
},
{ deep: true }
);
function createTab(type: string, index: number): Tab { function createTab(type: string, index: number): Tab {
let customName: string | null = null; let customName: string | null = null;
@ -52,6 +66,7 @@ function createTab(type: string, index: number): Tab {
}, },
icon: 'icon-blank', icon: 'icon-blank',
type, type,
componentIndex: -1,
component: undefined, component: undefined,
storage: {}, storage: {},
}; };

94
app/src/hook/panel.ts Normal file
View File

@ -0,0 +1,94 @@
import { useMessageBridge } from "@/api/message-bridge";
import { llmManager, llms } from "@/views/setting/llm";
import { pinkLog } from "@/views/setting/util";
import I18n from '@/i18n/index';
import { debugModes, tabs } from "@/components/main-panel/panel";
import { markRaw } from "vue";
interface SaveTabItem {
name: string;
icon: string;
type: string;
componentIndex: number;
storage: Record<string, any>;
}
interface SaveTab {
tabs: SaveTabItem[]
currentIndex: number
}
export function loadPanels() {
const bridge = useMessageBridge();
bridge.addCommandListener('panel/load', data => {
const persistTab = data.msg as SaveTab;
if (persistTab.tabs.length === 0) {
// 空的,直接返回不需要管
return;
}
tabs.activeIndex = 0;
tabs.content = [];
for (const tab of persistTab.tabs || []) {
tabs.content.push({
name: tab.name,
icon: tab.icon,
type: tab.type,
componentIndex: tab.componentIndex,
component: markRaw(debugModes[tab.componentIndex]),
storage: tab.storage
});
}
tabs.activeIndex = persistTab.currentIndex;
}, { once: true });
bridge.postMessage({
command: 'panel/load'
});
}
let debounceHandler: number;
export function safeSavePanels() {
clearTimeout(debounceHandler);
debounceHandler = setTimeout(() => {
savePanels();
}, 200);
}
export function savePanels(saveHandler?: () => void) {
const bridge = useMessageBridge();
const saveTabs: SaveTab = {
currentIndex: tabs.activeIndex,
tabs: []
};
for (const tab of tabs.content) {
saveTabs.tabs.push({
name: tab.name,
icon: tab.icon,
type: tab.type,
componentIndex: tab.componentIndex,
storage: JSON.parse(JSON.stringify(tab.storage))
});
}
bridge.addCommandListener('panel/save', data => {
const saveStatusCode = data.code;
pinkLog('配置保存状态:' + saveStatusCode);
if (saveHandler) {
saveHandler();
}
}, { once: true });
bridge.postMessage({
command: 'panel/save',
data: saveTabs
});
}

View File

@ -59,6 +59,7 @@ function chooseDebugMode(index: number) {
if (connectionResult.success) { if (connectionResult.success) {
const activeTab = tabs.activeTab; const activeTab = tabs.activeTab;
activeTab.component = markRaw(debugModes[index]); activeTab.component = markRaw(debugModes[index]);
activeTab.componentIndex = index;
activeTab.icon = debugOptions[index].icon; activeTab.icon = debugOptions[index].icon;
// activeTab tab name // activeTab tab name

View File

@ -2,6 +2,7 @@
import { VSCodeWebViewLike } from '../adapter'; import { VSCodeWebViewLike } from '../adapter';
import { connect, MCPClient, type MCPOptions } from './connect'; import { connect, MCPClient, type MCPOptions } from './connect';
import { callTool, getPrompt, listPrompts, listResources, listResourceTemplates, listTools, readResource } from './handler'; import { callTool, getPrompt, listPrompts, listResources, listResourceTemplates, listTools, readResource } from './handler';
import { panelLoadHandler, panelSaveHandler } from './panel';
import { settingLoadHandler, settingSaveHandler } from './setting'; import { settingLoadHandler, settingSaveHandler } from './setting';
import { ping } from './util'; import { ping } from './util';
@ -78,6 +79,14 @@ export function messageController(command: string, data: any, webview: VSCodeWeb
settingLoadHandler(client, webview); settingLoadHandler(client, webview);
break; break;
case 'panel/save':
panelSaveHandler(client, data, webview);
break;
case 'panel/load':
panelLoadHandler(client, webview);
break;
default: default:
break; break;
} }

View File

@ -0,0 +1,69 @@
import { VSCodeWebViewLike } from '../adapter';
import { loadConfig, loadTabSaveConfig, saveConfig, saveTabSaveConfig } from '../util';
import { MCPClient } from './connect';
export async function panelSaveHandler(client: MCPClient | undefined, data: any, webview: VSCodeWebViewLike) {
if (!client) {
const connectResult = {
code: 501,
msg: 'mcp client 尚未连接'
};
webview.postMessage({ command: 'ping', data: connectResult });
return;
}
try {
// 保存配置
saveTabSaveConfig(data);
webview.postMessage({
command: 'panel/save',
data: {
code: 200,
msg: 'Settings saved successfully'
}
});
} catch (error) {
webview.postMessage({
command: 'panel/save',
data: {
code: 500,
msg: `Failed to save settings: ${(error as Error).message}`
}
});
}
}
export async function panelLoadHandler(client: MCPClient | undefined, webview: VSCodeWebViewLike) {
if (!client) {
const connectResult = {
code: 501,
msg: 'mcp client 尚未连接'
};
webview.postMessage({ command: 'ping', data: connectResult });
return;
}
try {
// 加载配置
const config = loadTabSaveConfig();
webview.postMessage({
command: 'panel/load',
data: {
code: 200,
msg: config // 直接返回配置对象
}
});
} catch (error) {
webview.postMessage({
command: 'panel/load',
data: {
code: 500,
msg: `Failed to load settings: ${(error as Error).message}`
}
});
}
}

View File

@ -17,6 +17,21 @@ function getConfigurationPath() {
return 'config.json'; return 'config.json';
} }
function getTabSavePath() {
// 如果是 vscode 插件下,则修改为 ~/.vscode/openmcp.json
if (process.env.VSCODE_PID) {
// 在 VSCode 插件环境下
// const homeDir = os.homedir();
// const configDir = path.join(homeDir, '.openmcp');
// if (!fs.existsSync(configDir)) {
// fs.mkdirSync(configDir, { recursive: true });
// }
// return path.join(configDir, 'tabs.json');
return 'tabs.json';
}
return 'tabs.json';
}
function getDefaultLanguage() { function getDefaultLanguage() {
if (process.env.VSCODE_PID) { if (process.env.VSCODE_PID) {
// TODO: 获取 vscode 内部的语言 // TODO: 获取 vscode 内部的语言
@ -36,6 +51,24 @@ const DEFAULT_CONFIG: IConfig = {
LANG: getDefaultLanguage() LANG: getDefaultLanguage()
}; };
interface SaveTabItem {
name: string;
icon: string;
type: string;
componentIndex: number;
storage: Record<string, any>;
}
interface SaveTab {
tabs: SaveTabItem[]
currentIndex: number
}
const DEFAULT_TABS: SaveTab = {
tabs: [],
currentIndex: -1
}
function createConfig(): IConfig { function createConfig(): IConfig {
const configPath = getConfigurationPath(); const configPath = getConfigurationPath();
const configDir = path.dirname(configPath); const configDir = path.dirname(configPath);
@ -50,6 +83,20 @@ function createConfig(): IConfig {
return DEFAULT_CONFIG; return DEFAULT_CONFIG;
} }
function createSaveTabConfig(): SaveTab {
const configPath = getTabSavePath();
const configDir = path.dirname(configPath);
// 确保配置目录存在
if (configDir && !fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
// 写入默认配置
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_TABS, null, 2), 'utf-8');
return DEFAULT_TABS;
}
export function loadConfig(): IConfig { export function loadConfig(): IConfig {
const configPath = getConfigurationPath(); const configPath = getConfigurationPath();
@ -66,22 +113,39 @@ export function loadConfig(): IConfig {
} }
} }
export function saveConfig(config: Partial<IConfig>, merge: boolean = true): void { export function saveConfig(config: Partial<IConfig>): void {
const configPath = getConfigurationPath(); const configPath = getConfigurationPath();
let currentConfig: IConfig = DEFAULT_CONFIG; let currentConfig: IConfig = DEFAULT_CONFIG;
if (merge && fs.existsSync(configPath)) {
try {
currentConfig = loadConfig();
} catch (error) {
console.error('Error loading existing config:', error);
}
}
const newConfig = { ...currentConfig, ...config };
try { try {
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2), 'utf-8'); fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
} catch (error) {
console.error('Error saving config file:', error);
throw error;
}
}
export function loadTabSaveConfig(): SaveTab {
const tabSavePath = getTabSavePath();
if (!fs.existsSync(tabSavePath)) {
return createSaveTabConfig();
}
try {
const configData = fs.readFileSync(tabSavePath, 'utf-8');
return JSON.parse(configData) as SaveTab;
} catch (error) {
console.error('Error loading config file, creating new one:', error);
return createSaveTabConfig();
}
}
export function saveTabSaveConfig(config: Partial<IConfig>): void {
const tabSavePath = getTabSavePath();
try {
fs.writeFileSync(tabSavePath, JSON.stringify(config, null, 2), 'utf-8');
} catch (error) { } catch (error) {
console.error('Error saving config file:', error); console.error('Error saving config file:', error);
throw error; throw error;

23
test/tabs.json Normal file
View File

@ -0,0 +1,23 @@
{
"currentIndex": 1,
"tabs": [
{
"name": "资源",
"icon": "icon-file",
"type": "blank",
"componentIndex": 0,
"storage": {
"currentResourceName": "greeting"
}
},
{
"name": "提词",
"icon": "icon-chat",
"type": "blank",
"componentIndex": 1,
"storage": {
"currentPromptName": "translate"
}
}
]
}