完成标签页的保存
This commit is contained in:
parent
3d372dafae
commit
b156244763
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
|
@ -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
94
app/src/hook/panel.ts
Normal 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
|
||||||
|
});
|
||||||
|
}
|
@ -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 就不会再被进行赋值操作了
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
69
test/src/controller/panel.ts
Normal file
69
test/src/controller/panel.ts
Normal 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}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
try {
|
||||||
currentConfig = loadConfig();
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading existing config:', error);
|
console.error('Error saving config file:', error);
|
||||||
}
|
throw error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const newConfig = { ...currentConfig, ...config };
|
|
||||||
|
export function loadTabSaveConfig(): SaveTab {
|
||||||
try {
|
const tabSavePath = getTabSavePath();
|
||||||
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2), 'utf-8');
|
|
||||||
|
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
23
test/tabs.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user