修复关闭标签页 key 重排序错误的问题

This commit is contained in:
锦恢 2025-04-28 14:57:46 +08:00
parent 4f9900a64c
commit a535690bc6
12 changed files with 131 additions and 41 deletions

View File

@ -14,6 +14,7 @@
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"markdown-it-katex": "^2.0.3", "markdown-it-katex": "^2.0.3",
"openai": "^4.93.0", "openai": "^4.93.0",
"uuid": "^11.1.0",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-i18n": "^11.1.0", "vue-i18n": "^11.1.0",
"vue-router": "^4.0.3" "vue-router": "^4.0.3"
@ -11820,6 +11821,16 @@
"websocket-driver": "^0.7.4" "websocket-driver": "^0.7.4"
} }
}, },
"node_modules/sockjs/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
@ -12970,13 +12981,16 @@
} }
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "8.3.2", "version": "11.1.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"dev": true, "funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"uuid": "dist/bin/uuid" "uuid": "dist/esm/bin/uuid"
} }
}, },
"node_modules/v8-compile-cache": { "node_modules/v8-compile-cache": {

View File

@ -14,6 +14,7 @@
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"markdown-it-katex": "^2.0.3", "markdown-it-katex": "^2.0.3",
"openai": "^4.93.0", "openai": "^4.93.0",
"uuid": "^11.1.0",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-i18n": "^11.1.0", "vue-i18n": "^11.1.0",
"vue-router": "^4.0.3" "vue-router": "^4.0.3"

View File

@ -22,7 +22,7 @@
--vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); --vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6);
--vscode-progressBar-background: #0e70c0; --vscode-progressBar-background: #0e70c0;
--vscode-editor-background: #ffffff; --vscode-editor-background: #ffffff;
--vscode-editor-foreground: #000000; --vscode-editor-foreground: #3d3d3d;
--vscode-editorStickyScroll-background: #ffffff; --vscode-editorStickyScroll-background: #ffffff;
--vscode-editorStickyScrollHover-background: #f0f0f0; --vscode-editorStickyScrollHover-background: #f0f0f0;
--vscode-editorStickyScroll-shadow: #dddddd; --vscode-editorStickyScroll-shadow: #dddddd;

View File

@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">
<link rel="icon" href="<%= BASE_URL %>favicon.svg"> <link rel="icon" href="<%= BASE_URL %>favicon.svg">
<link rel="stylesheet" href="default-dark.css"> <link rel="stylesheet" href="default-light.css">
<link rel="stylesheet" href="vscode.css"> <link rel="stylesheet" href="vscode.css">
<link rel="stylesheet" href="mcp.css"> <link rel="stylesheet" href="mcp.css">
<link rel="stylesheet" href="iconfont.css"> <link rel="stylesheet" href="iconfont.css">

View File

@ -29,8 +29,8 @@ bridge.addCommandListener('hello', data => {
function initDebug() { function initDebug() {
// connectionArgs.commandString = 'node /Users/bytedance/projects/mcp/servers/src/puppeteer/dist/index.js'; connectionArgs.commandString = 'node /Users/bytedance/projects/mcp/servers/src/puppeteer/dist/index.js';
connectionArgs.commandString = 'node C:/Users/K/code/servers/src/puppeteer/dist/index.js'; // connectionArgs.commandString = 'node C:/Users/K/code/servers/src/puppeteer/dist/index.js';
// connectionArgs.commandString = 'uv run mcp run bing-picture.py'; // connectionArgs.commandString = 'uv run mcp run bing-picture.py';
connectionArgs.cwd = '../servers'; connectionArgs.cwd = '../servers';
connectionMethods.current = 'STDIO'; connectionMethods.current = 'STDIO';
@ -107,4 +107,8 @@ onMounted(() => {
max-width: 300px; max-width: 300px;
} }
.icon-chat:before {
font-weight: 1000;
}
</style> </style>

View File

@ -5,25 +5,32 @@
</div> </div>
<div v-else-if="props.item.type === 'image'" class="tool-image"> <div v-else-if="props.item.type === 'image'" class="tool-image">
<div class="media-item"> <div class="media-item" @click="showFullImage">
<img :src="thumbnail" alt="screenshot"/> <img :src="thumbnail" alt="screenshot" />
<span class="float-container"> <span class="float-container">
<span class="iconfont icon-image"></span>
<!-- 后处理结束后显示的部分 -->
<span class="iconfont icon-image" v-if="finishProcess"></span>
<!-- 后处理时显示的部分 -->
<el-progress v-else
class="progress"
:percentage="progress"
:stroke-width="5"
type="circle"
:width="80"
color="var(--main-color)"
>
<template #default="{ percentage }">
<div class="progress-label">
<span class="percentage-value">{{ percentage }}%</span>
<span class="percentage-label">{{ progressText }}</span>
</div>
</template>
</el-progress>
</span> </span>
</div> </div>
<span v-if="!finishProcess">
<el-progress
class="progress"
:percentage="progress"
:stroke-width="3"
>
<template #default="{ percentage }">
<span class="percentage-label">{{ progressText }}</span>
<span class="percentage-value">{{ percentage }}%</span>
</template>
</el-progress>
</span>
</div> </div>
<div v-else class="tool-other">{{ JSON.stringify(props.item) }}</div> <div v-else class="tool-other">{{ JSON.stringify(props.item) }}</div>
@ -62,7 +69,7 @@ if (ocr) {
const { id, progress: p = 1.0, status = 'finish' } = data; const { id, progress: p = 1.0, status = 'finish' } = data;
if (id === workerId) { if (id === workerId) {
progressText.value = status; progressText.value = status;
progress.value = Math.min(Math.ceil(Math.max(p * 100 ,0)), 100); progress.value = Math.min(Math.ceil(Math.max(p * 100, 0)), 100);
} }
}, { once: false }); }, { once: false });
@ -88,13 +95,48 @@ if (props.item.data) {
console.log(props.item.data); console.log(props.item.data);
getBlobUrlByFilename(props.item.data).then(url => { getBlobUrlByFilename(props.item.data).then(url => {
console.log(url); console.log(url);
if (url) { if (url) {
thumbnail.value = url; thumbnail.value = url;
} }
}); });
} }
const showFullImage = () => {
const img = new Image();
img.src = thumbnail.value;
img.onload = () => {
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
overlay.style.zIndex = '9999';
overlay.style.display = 'flex';
overlay.style.justifyContent = 'center';
overlay.style.alignItems = 'center';
overlay.onclick = () => document.body.removeChild(overlay);
const imgContainer = document.createElement('div');
imgContainer.style.maxWidth = '90vw';
imgContainer.style.maxHeight = '90vh';
imgContainer.style.overflow = 'auto';
const fullImg = new Image();
fullImg.src = thumbnail.value;
fullImg.style.width = 'auto';
fullImg.style.height = 'auto';
fullImg.style.maxWidth = '100%';
fullImg.style.maxHeight = '100%';
imgContainer.appendChild(fullImg);
overlay.appendChild(imgContainer);
document.body.appendChild(overlay);
};
};
</script> </script>
<style> <style>
@ -106,10 +148,6 @@ if (props.item.data) {
margin-top: 10px; margin-top: 10px;
} }
.percentage-label {
margin-right: 10px;
}
.tool-image .media-item { .tool-image .media-item {
position: relative; position: relative;
width: 100px; width: 100px;
@ -130,7 +168,7 @@ if (props.item.data) {
overflow: hidden; overflow: hidden;
} }
.tool-image .media-item > img { .tool-image .media-item>img {
position: absolute; position: absolute;
top: 50%; top: 50%;
height: 100%; height: 100%;
@ -145,7 +183,7 @@ if (props.item.data) {
top: 0; top: 0;
width: 100px; width: 100px;
height: 100px; height: 100px;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.6);
opacity: 1; opacity: 1;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -153,6 +191,25 @@ if (props.item.data) {
transition: var(--animation-3s); transition: var(--animation-3s);
} }
.progress-label {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.percentage-label {
max-width: 50px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.percentage-value {
font-size: 14px;
font-weight: bold;
}
.tool-image .media-item .float-container .iconfont { .tool-image .media-item .float-container .iconfont {
color: var(--background); color: var(--background);
} }
@ -161,4 +218,11 @@ if (props.item.data) {
opacity: 1; opacity: 1;
} }
.media-item {
cursor: pointer;
}
.media-item:hover {
opacity: 0.9;
}
</style> </style>

View File

@ -45,7 +45,7 @@
反馈 反馈
</el-button> </el-button>
</span> </span>
<span style="width: 200px;" class="tools-dialog-container" v-if="isValid"> <span style="width: 200px;" class="tools-dialog-container" v-if="currentMessageLevel === 'info'">
<el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON" <el-switch v-model="props.message.showJson!.value" inline-prompt active-text="JSON"
inactive-text="Text" style="margin-left: 10px; width: 200px;" inactive-text="Text" style="margin-left: 10px; width: 200px;"
:inactive-action-style="'backgroundColor: var(--sidebar)'" /> :inactive-action-style="'backgroundColor: var(--sidebar)'" />

View File

@ -312,7 +312,7 @@ export class TaskLoop {
const toolCallResult = await this.handleToolCalls(this.streamingToolCalls.value); const toolCallResult = await this.handleToolCalls(this.streamingToolCalls.value);
console.log(toolCallResult); console.log('toolCallResult', toolCallResult);
if (toolCallResult.state === MessageState.ParseJsonError) { if (toolCallResult.state === MessageState.ParseJsonError) {
// 如果是因为解析 JSON 错误,则重新开始 // 如果是因为解析 JSON 错误,则重新开始

View File

@ -6,7 +6,7 @@
<span <span
class="tab" class="tab"
v-for="(tab, index) of tabs.content" v-for="(tab, index) of tabs.content"
:key="index" :key="tab.id"
:class="{ 'active-tab': tabs.activeIndex === index }" :class="{ 'active-tab': tabs.activeIndex === index }"
@click="setActiveTab(index)" @click="setActiveTab(index)"
> >
@ -56,7 +56,7 @@ function pageAddNewTab() {
} }
} }
function setActiveTab(index: number) { function setActiveTab(index: number) {
if (index >= 0 && index < tabs.content.length) { if (index >= 0 && index < tabs.content.length) {
tabs.activeIndex = index; tabs.activeIndex = index;
// debug // debug

View File

@ -1,5 +1,5 @@
import { watch, reactive } from 'vue'; import { watch, reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { v4 as uuidv4 } from 'uuid';
import Resource from './resource/index.vue'; import Resource from './resource/index.vue';
import Chat from './chat/index.vue'; import Chat from './chat/index.vue';
@ -11,6 +11,7 @@ import { safeSavePanels, savePanels } from '@/hook/panel';
const { t } = I18n.global; const { t } = I18n.global;
interface Tab { interface Tab {
id: string;
name: string; name: string;
icon: string; icon: string;
type: string; type: string;
@ -52,6 +53,7 @@ watch(
export function createTab(type: string, index: number): Tab { export function createTab(type: string, index: number): Tab {
let customName: string | null = null; let customName: string | null = null;
const id = uuidv4();
return { return {
get name() { get name() {
@ -65,6 +67,7 @@ export function createTab(type: string, index: number): Tab {
}, },
icon: 'icon-blank', icon: 'icon-blank',
type, type,
id,
componentIndex: -1, componentIndex: -1,
component: undefined, component: undefined,
storage: {}, storage: {},
@ -83,6 +86,8 @@ export function closeTab(index: number) {
tabs.content.splice(index, 1); tabs.content.splice(index, 1);
console.log(tabs.content);
// 调整活动标签索引 // 调整活动标签索引
if (tabs.activeIndex >= index) { if (tabs.activeIndex >= index) {
tabs.activeIndex = Math.max(0, tabs.activeIndex - 1); tabs.activeIndex = Math.max(0, tabs.activeIndex - 1);

View File

@ -2,6 +2,7 @@ import { useMessageBridge } from "@/api/message-bridge";
import { pinkLog } from "@/views/setting/util"; import { pinkLog } from "@/views/setting/util";
import { debugModes, tabs } from "@/components/main-panel/panel"; import { debugModes, tabs } from "@/components/main-panel/panel";
import { markRaw, ref, nextTick } from "vue"; import { markRaw, ref, nextTick } from "vue";
import { v4 as uuidv4 } from 'uuid';
interface SaveTabItem { interface SaveTabItem {
name: string; name: string;
@ -48,6 +49,7 @@ export function loadPanels() {
const component = tab.componentIndex >= 0? markRaw(debugModes[tab.componentIndex]) : undefined; const component = tab.componentIndex >= 0? markRaw(debugModes[tab.componentIndex]) : undefined;
tabs.content.push({ tabs.content.push({
id: uuidv4(),
name: tab.name, name: tab.name,
icon: tab.icon, icon: tab.icon,
type: tab.type, type: tab.type,
@ -57,7 +59,7 @@ export function loadPanels() {
}); });
} }
tabs.activeIndex = persistTab.currentIndex; tabs.activeIndex = persistTab.currentIndex;
} }
panelLoaded.value = true; panelLoaded.value = true;
@ -76,7 +78,7 @@ export function safeSavePanels() {
clearTimeout(debounceHandler); clearTimeout(debounceHandler);
debounceHandler = setTimeout(() => { debounceHandler = setTimeout(() => {
savePanels(); savePanels();
}, 1000); }, 100);
} }
export function savePanels(saveHandler?: () => void) { export function savePanels(saveHandler?: () => void) {

View File

@ -8,7 +8,7 @@
<component <component
v-show="tab === tabs.content[tabs.activeIndex]" v-show="tab === tabs.content[tabs.activeIndex]"
v-for="(tab, index) of tabs.content" v-for="(tab, index) of tabs.content"
:key="index" :key="tab.id"
:is="tab.component" :is="tab.component"
:tab-id="index" :tab-id="index"
/> />