重构 chat 模块的 setting 面板

This commit is contained in:
锦恢 2025-04-28 15:47:29 +08:00
parent a535690bc6
commit 27e94efa26
25 changed files with 560 additions and 465 deletions

View File

@ -7,6 +7,11 @@ export interface VSCodeMessage {
callbackId?: string; callbackId?: string;
} }
export interface RestFulResponse {
code: number;
msg: any;
}
export type MessageHandler = (message: VSCodeMessage) => void; export type MessageHandler = (message: VSCodeMessage) => void;
export type CommandHandler = (data: any) => void; export type CommandHandler = (data: any) => void;
@ -135,6 +140,25 @@ class MessageBridge {
return () => commandHandlers.delete(wrapperCommandHandler); return () => commandHandlers.delete(wrapperCommandHandler);
} }
/**
* @description do as axios does
* @param command
* @param data
* @returns
*/
public commandRequest(command: string, data?: any) {
return new Promise<RestFulResponse>((resolve, reject) => {
this.addCommandListener(command, (data) => {
resolve(data as RestFulResponse);
}, { once: true });
this.postMessage({
command,
data
});
});
}
public destroy() { public destroy() {
this.ws?.close(); this.ws?.close();
this.handlers.clear(); this.handlers.clear();
@ -151,6 +175,7 @@ export function useMessageBridge() {
return { return {
postMessage: bridge.postMessage.bind(bridge), postMessage: bridge.postMessage.bind(bridge),
addCommandListener: bridge.addCommandListener.bind(bridge), addCommandListener: bridge.addCommandListener.bind(bridge),
commandRequest: bridge.commandRequest.bind(bridge),
isConnected: bridge.isConnected isConnected: bridge.isConnected
}; };
} }

View File

@ -82,7 +82,7 @@ import { TaskLoop } from './task-loop';
import { llmManager, llms } from '@/views/setting/llm'; import { llmManager, llms } from '@/views/setting/llm';
import * as Message from './message'; import * as Message from './message';
import Setting from './setting.vue'; import Setting from './options/setting.vue';
import KCuteTextarea from '@/components/k-cute-textarea/index.vue'; import KCuteTextarea from '@/components/k-cute-textarea/index.vue';
import { provide } from 'vue'; import { provide } from 'vue';

View File

@ -8,7 +8,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineProps } from 'vue'; import { defineProps } from 'vue';
import { markdownToHtml } from '../markdown'; import { markdownToHtml } from '../markdown/markdown';
import MessageMeta from './message-meta.vue'; import MessageMeta from './message-meta.vue';

View File

@ -19,7 +19,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineProps } from 'vue'; import { defineProps } from 'vue';
import { markdownToHtml } from '../markdown'; import { markdownToHtml } from '../markdown/markdown';
const props = defineProps({ const props = defineProps({
streamingContent: { streamingContent: {

View File

@ -96,7 +96,7 @@
import { defineProps, ref, watch, PropType, computed, defineEmits } from 'vue'; import { defineProps, ref, watch, PropType, computed, defineEmits } from 'vue';
import MessageMeta from './message-meta.vue'; import MessageMeta from './message-meta.vue';
import { markdownToHtml } from '../markdown'; import { markdownToHtml } from '../markdown/markdown';
import { createTest } from '@/views/setting/llm'; import { createTest } from '@/views/setting/llm';
import { IRenderMessage, MessageState } from '../chat'; import { IRenderMessage, MessageState } from '../chat';
import { ToolCallContent } from '@/hook/type'; import { ToolCallContent } from '@/hook/type';

View File

@ -0,0 +1,42 @@
<template>
<el-tooltip :content="t('context-length')" placement="top">
<div class="setting-button" @click="showContextLengthDialog = true">
<span class="iconfont icon-length"></span>
<span class="value-badge">{{ tabStorage.settings.contextLength }}</span>
</div>
</el-tooltip>
<!-- 上下文长度设置 - 改为滑块形式 -->
<el-dialog v-model="showContextLengthDialog" :title="t('context-length') + ' ' + tabStorage.settings.contextLength"
width="400px">
<div class="slider-container">
<el-slider v-model="tabStorage.settings.contextLength" :min="1" :max="99" :step="1" />
<div class="slider-tips">
<span> 1: {{ t('single-dialog') }}</span>
<span> >1: {{ t('multi-dialog') }}</span>
</div>
</div>
<template #footer>
<el-button @click="showContextLengthDialog = false">{{ t("cancel") }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { ChatStorage } from '../chat';
const { t } = useI18n();
const tabStorage = inject('tabStorage') as ChatStorage;
const showContextLengthDialog = ref(false);
</script>
<style>
.icon-length {
font-size: 16px;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<el-tooltip :content="t('choose-model')" placement="top">
<div class="setting-button" @click="showModelDialog = true">
<span class="iconfont icon-model">
{{ currentServerName }}/{{ currentModelName }}
</span>
</div>
</el-tooltip>
<!-- 模型选择对话框 -->
<el-dialog v-model="showModelDialog" :title="t('choose-model')" width="400px">
<el-radio-group v-model="selectedModelIndex" @change="onRadioGroupChange">
<el-radio v-for="(model, index) in availableModels" :key="index" :value="index">
{{ model }}
</el-radio>
</el-radio-group>
<template #footer>
<el-button @click="showModelDialog = false">{{ t("cancel") }}</el-button>
<el-button type="primary" @click="confirmModelChange">{{ t("confirm") }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { saveSetting } from '@/hook/setting';
import { llmManager, llms } from '@/views/setting/llm';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const showModelDialog = ref(false);
const currentModel = llms[llmManager.currentModelIndex].userModel;
const selectedModelIndex = ref(llms[llmManager.currentModelIndex].models.indexOf(currentModel));
const currentServerName = computed(() => {
const currentLlm = llms[llmManager.currentModelIndex];
if (currentLlm) {
return currentLlm.name;
}
return '';
});
const currentModelName = computed(() => {
const currentLlm = llms[llmManager.currentModelIndex];
if (currentLlm) {
return currentLlm.models[selectedModelIndex.value];
}
return '';
});
const availableModels = computed(() => {
return llms[llmManager.currentModelIndex].models;
});
const confirmModelChange = () => {
showModelDialog.value = false;
};
const onRadioGroupChange = () => {
const currentModel = llms[llmManager.currentModelIndex].models[selectedModelIndex.value];
llms[llmManager.currentModelIndex].userModel = currentModel;
saveSetting();
};
</script>
<style></style>

View File

@ -0,0 +1,201 @@
<template>
<div class="chat-settings">
<Model />
<SystemPrompt />
<ToolUse />
<Websearch />
<Temperature />
<ContextLength />
</div>
</template>
<script setup lang="ts">
import { defineProps, provide } from 'vue';
import { llmManager } from '@/views/setting/llm';
import { tabs } from '../../panel';
import type { ChatSetting, ChatStorage } from '../chat';
import Model from './model.vue';
import SystemPrompt from './system-prompt.vue';
import ToolUse from './tool-use.vue';
import Websearch from './websearch.vue';
import Temperature from './temperature.vue';
import ContextLength from './context-length.vue';
const props = defineProps({
tabId: {
type: Number,
required: true
}
});
const tab = tabs.content[props.tabId];
const tabStorage = tab.storage as ChatStorage & { settings: ChatSetting };
if (!tabStorage.settings) {
tabStorage.settings = {
modelIndex: llmManager.currentModelIndex,
enableTools: [],
enableWebSearch: false,
temperature: 0.7,
contextLength: 10,
systemPrompt: ''
} as ChatSetting;
}
provide('tabStorage', tabStorage);
</script>
<style>
.chat-settings {
display: flex;
gap: 2px;
padding: 8px 0;
background-color: var(--sidebar);
width: fit-content;
border-radius: 99%;
bottom: 0px;
z-index: 10;
position: absolute;
}
.setting-button {
padding: 5px 8px;
margin-right: 3px;
border-radius: .5em;
font-size: 12px;
position: relative;
user-select: none;
-webkit-user-drag: none;
display: flex;
align-items: center;
cursor: pointer;
transition: var(--animation-3s);
}
.setting-button.active {
background-color: var(--el-color-primary);
color: var(--el-text-color-primary);
transition: var(--animation-3s);
}
.setting-button.active:hover {
background-color: var(--el-color-primary);
transition: var(--animation-3s);
}
.setting-button:hover {
background-color: var(--background);
transition: var(--animation-3s);
}
.value-badge {
font-size: 10px;
padding: 1px 4px;
border-radius: 4px;
}
.slider-container {
padding: 0 10px;
}
.slider-tips {
display: flex;
justify-content: space-between;
margin-top: 10px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
/* 新增工具相关样式 */
.tools-container {
padding: 10px;
}
.tool-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid var(--el-border-color-light);
}
.tool-info {
flex: 1;
margin-right: 20px;
}
.tool-name {
font-weight: 500;
margin-bottom: 4px;
}
.tool-description {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.tools-dialog-container .el-switch__core {
border: 1px solid var(--main-color) !important;
}
.tools-dialog-container .el-switch .el-switch__action {
background-color: var(--main-color);
}
.tools-dialog-container .el-switch.is-checked .el-switch__action {
background-color: var(--sidebar);
}
/* 新增工具对话框样式 */
.tools-dialog-container {
display: flex;
gap: 16px;
}
.tools-list {
flex: 1;
border-right: 1px solid var(--el-border-color);
padding-right: 16px;
}
.schema-viewer {
flex: 1;
}
.schema-viewer pre {
margin: 0;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
background-color: var(--el-bg-color-overlay);
}
.schema-viewer .openmcp-code-block {
border: none;
}
.schema-viewer code {
font-family: var(--code-font-family);
font-size: 12px;
color: var(--el-text-color-primary);
}
.badge-outer {
position: relative;
}
.badge-inner {
position: absolute;
color: var(--foreground);
background-color: var(--main-color);
border-radius: 50%;
padding: 2px 6px;
font-size: 10px;
top: -16px;
right: -18px;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.2);
}
</style>

View File

@ -0,0 +1,19 @@
import { useMessageBridge } from "@/api/message-bridge";
import { ref } from "vue";
interface SystemPrompt {
name: string;
content: string;
}
export const systemPrompt = ref<SystemPrompt>({
name: '默认',
content: '你是一个AI助手, 你可以回答任何问题。'
});
export function saveSystemPrompts() {
const bridge = useMessageBridge();
return new Promise(resolve => {
})
}

View File

@ -0,0 +1,38 @@
<template>
<el-tooltip :content="t('system-prompt')" placement="top">
<div class="setting-button" :class="{ 'active': hasSystemPrompt }" size="small"
@click="showSystemPromptDialog = true">
<span class="iconfont icon-prompt"></span>
</div>
</el-tooltip>
<!-- System Prompt对话框 -->
<el-dialog v-model="showSystemPromptDialog" :title="t('system-prompt')" width="600px">
<el-input v-model="tabStorage.settings.systemPrompt" type="textarea" :rows="8"
:placeholder="t('system-prompt.placeholder')" clearable />
<template #footer>
<el-button @click="showSystemPromptDialog = false">{{ t("cancel") }}</el-button>
<el-button type="primary" @click="showSystemPromptDialog = false">{{ t("save") }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, inject } from 'vue';
import { useI18n } from 'vue-i18n';
import { ChatStorage } from '../chat';
const { t } = useI18n();
const tabStorage = inject('tabStorage') as ChatStorage;
const showSystemPromptDialog = ref(false);
const hasSystemPrompt = computed(() => {
return !!tabStorage.settings.systemPrompt?.trim();
});
</script>
<style></style>

View File

@ -0,0 +1,42 @@
<template>
<el-tooltip :content="t('temperature-parameter')" placement="top">
<div class="setting-button" @click="showTemperatureSlider = true">
<span class="iconfont icon-temperature"></span>
<span class="value-badge">{{ tabStorage.settings.temperature.toFixed(1) }}</span>
</div>
</el-tooltip>
<!-- 温度参数滑块 -->
<el-dialog v-model="showTemperatureSlider" :title="t('temperature-parameter')" width="400px">
<div class="slider-container">
<el-slider v-model="tabStorage.settings.temperature" :min="0" :max="2" :step="0.1" />
<div class="slider-tips">
<span> {{ t('precise') }}(0)</span>
<span>{{ t('moderate') }}(1)</span>
<span>{{ t('creative') }}(2)</span>
</div>
</div>
<template #footer>
<el-button @click="showTemperatureSlider = false">{{ t("cancel") }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { ChatStorage } from '../chat';
const { t } = useI18n();
const tabStorage = inject('tabStorage') as ChatStorage;
const showTemperatureSlider = ref(false);
</script>
<style>
.icon-temperature {
font-size: 18px;
}
</style>

View File

@ -0,0 +1,85 @@
<template>
<el-tooltip :content="t('tool-use')" placement="top">
<div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small" @click="toggleTools">
<span class="iconfont icon-tool badge-outer">
<span class="badge-inner">
{{ availableToolsNum }}
</span>
</span>
</div>
</el-tooltip>
<el-dialog v-model="showToolsDialog" title="工具管理" width="800px">
<div class="tools-dialog-container">
<el-scrollbar height="400px" class="tools-list">
<div v-for="(tool, index) in tabStorage.settings.enableTools" :key="index" class="tool-item">
<div class="tool-info">
<div class="tool-name">{{ tool.name }}</div>
<div v-if="tool.description" class="tool-description">{{ tool.description }}</div>
</div>
<el-switch v-model="tool.enabled" />
</div>
</el-scrollbar>
<el-scrollbar height="400px" class="schema-viewer">
<div v-html="activeToolsSchemaHTML"></div>
</el-scrollbar>
</div>
<template #footer>
<el-button type="primary" @click="enableAllTools">激活所有工具</el-button>
<el-button type="danger" @click="disableAllTools">禁用所有工具</el-button>
<el-button type="primary" @click="showToolsDialog = false">{{ t("cancel") }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, inject } from 'vue';
import { useI18n } from 'vue-i18n';
import { ChatStorage, getToolSchema } from '../chat';
import { markdownToHtml } from '../markdown/markdown';
const { t } = useI18n();
const tabStorage = inject('tabStorage') as ChatStorage;
const showToolsDialog = ref(false);
const availableToolsNum = computed(() => {
return tabStorage.settings.enableTools.filter(tool => tool.enabled).length;
});
// toggleTools
const toggleTools = () => {
showToolsDialog.value = true;
};
const activeToolsSchemaHTML = computed(() => {
const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
const jsonString = JSON.stringify(toolsSchema, null, 2);
return markdownToHtml(
"```json\n" + jsonString + "\n```"
);
});
// -
const enableAllTools = () => {
tabStorage.settings.enableTools.forEach(tool => {
tool.enabled = true;
});
};
// -
const disableAllTools = () => {
tabStorage.settings.enableTools.forEach(tool => {
tool.enabled = false;
});
};
</script>
<style></style>

View File

@ -0,0 +1,26 @@
<template>
<el-tooltip :content="t('websearch')" placement="top">
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small"
@click="toggleWebSearch">
<span class="iconfont icon-web"></span>
</div>
</el-tooltip>
</template>
<script setup lang="ts">
import { inject } from 'vue';
import { useI18n } from 'vue-i18n';
import { ChatStorage } from '../chat';
const { t } = useI18n();
const tabStorage = inject('tabStorage') as ChatStorage;
const toggleWebSearch = () => {
tabStorage.settings.enableWebSearch = !tabStorage.settings.enableWebSearch;
};
</script>
<style>
</style>

View File

@ -1,438 +0,0 @@
<template>
<div class="chat-settings">
<el-tooltip :content="t('choose-model')" placement="top">
<div class="setting-button" @click="showModelDialog = true">
<span class="iconfont icon-model">
{{ currentServerName }}/{{ currentModelName }}
</span>
</div>
</el-tooltip>
<el-tooltip :content="t('system-prompt')" placement="top">
<div class="setting-button" :class="{ 'active': hasSystemPrompt }" size="small"
@click="showSystemPromptDialog = true">
<span class="iconfont icon-prompt"></span>
</div>
</el-tooltip>
<el-tooltip :content="t('tool-use')" placement="top">
<div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small"
@click="toggleTools">
<span class="iconfont icon-tool badge-outer">
<span class="badge-inner">
{{ availableToolsNum }}
</span>
</span>
</div>
</el-tooltip>
<el-tooltip :content="t('websearch')" placement="top">
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small"
@click="toggleWebSearch">
<span class="iconfont icon-web"></span>
</div>
</el-tooltip>
<el-tooltip :content="t('temperature-parameter')" placement="top">
<div class="setting-button" @click="showTemperatureSlider = true">
<span class="iconfont icon-temperature"></span>
<span class="value-badge">{{ tabStorage.settings.temperature.toFixed(1) }}</span>
</div>
</el-tooltip>
<el-tooltip :content="t('context-length')" placement="top">
<div class="setting-button" @click="showContextLengthDialog = true">
<span class="iconfont icon-length"></span>
<span class="value-badge">{{ tabStorage.settings.contextLength }}</span>
</div>
</el-tooltip>
<!-- 模型选择对话框 -->
<el-dialog v-model="showModelDialog" :title="t('choose-model')" width="400px">
<el-radio-group v-model="selectedModelIndex" @change="onRadioGroupChange">
<el-radio v-for="(model, index) in availableModels" :key="index" :label="index">
{{ model }}
</el-radio>
</el-radio-group>
<template #footer>
<el-button @click="showModelDialog = false">{{ t("cancel") }}</el-button>
<el-button type="primary" @click="confirmModelChange">{{ t("confirm") }}</el-button>
</template>
</el-dialog>
<!-- System Prompt对话框 -->
<el-dialog v-model="showSystemPromptDialog" :title="t('system-prompt')" width="600px">
<el-input v-model="tabStorage.settings.systemPrompt" type="textarea" :rows="8"
:placeholder="t('system-prompt.placeholder')"
clearable
/>
<template #footer>
<el-button @click="showSystemPromptDialog = false">{{ t("cancel") }}</el-button>
<el-button type="primary" @click="showSystemPromptDialog = false">{{ t("save") }}</el-button>
</template>
</el-dialog>
<!-- 温度参数滑块 -->
<el-dialog v-model="showTemperatureSlider" :title="t('temperature-parameter')" width="400px">
<div class="slider-container">
<el-slider v-model="tabStorage.settings.temperature" :min="0" :max="2" :step="0.1" />
<div class="slider-tips">
<span> {{ t('precise') }}(0)</span>
<span>{{ t('moderate') }}(1)</span>
<span>{{ t('creative') }}(2)</span>
</div>
</div>
<template #footer>
<el-button @click="showTemperatureSlider = false">{{ t("cancel") }}</el-button>
</template>
</el-dialog>
<!-- 上下文长度设置 - 改为滑块形式 -->
<el-dialog v-model="showContextLengthDialog" :title="t('context-length') + ' ' + tabStorage.settings.contextLength" width="400px">
<div class="slider-container">
<el-slider v-model="tabStorage.settings.contextLength" :min="1" :max="99" :step="1" />
<div class="slider-tips">
<span> 1: {{ t('single-dialog') }}</span>
<span> >1: {{ t('multi-dialog') }}</span>
</div>
</div>
<template #footer>
<el-button @click="showContextLengthDialog = false">{{ t("cancel") }}</el-button>
</template>
</el-dialog>
<!-- 修改后的工具使用对话框 -->
<el-dialog v-model="showToolsDialog" title="工具管理" width="800px">
<div class="tools-dialog-container">
<el-scrollbar height="400px" class="tools-list">
<div v-for="(tool, index) in tabStorage.settings.enableTools" :key="index" class="tool-item">
<div class="tool-info">
<div class="tool-name">{{ tool.name }}</div>
<div v-if="tool.description" class="tool-description">{{ tool.description }}</div>
</div>
<el-switch v-model="tool.enabled"/>
</div>
</el-scrollbar>
<el-scrollbar height="400px" class="schema-viewer">
<div v-html="activeToolsSchemaHTML"></div>
</el-scrollbar>
</div>
<template #footer>
<el-button type="primary" @click="enableAllTools">激活所有工具</el-button>
<el-button type="danger" @click="disableAllTools">禁用所有工具</el-button>
<el-button type="primary" @click="showToolsDialog = false">{{ t("cancel") }}</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, computed, defineProps, onMounted, onUnmounted } from 'vue';
import { llmManager, llms } from '@/views/setting/llm';
import { tabs } from '../panel';
import { allTools, ChatSetting, ChatStorage, getToolSchema } from './chat';
import { useMessageBridge } from '@/api/message-bridge';
import { CasualRestAPI, ToolItem, ToolsListResponse } from '@/hook/type';
import { markdownToHtml } from './markdown';
import { saveSetting } from '@/hook/setting';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
tabId: {
type: Number,
required: true
}
});
const showModelDialog = ref(false);
const showTemperatureSlider = ref(false);
const showContextLengthDialog = ref(false);
const showSystemPromptDialog = ref(false);
const currentServerName = computed(() => {
const currentLlm = llms[llmManager.currentModelIndex];
if (currentLlm) {
return currentLlm.name;
}
return '';
});
const currentModelName = computed(() => {
const currentLlm = llms[llmManager.currentModelIndex];
if (currentLlm) {
return currentLlm.models[selectedModelIndex.value];
}
return '';
});
const tab = tabs.content[props.tabId];
const tabStorage = tab.storage as ChatStorage & { settings: ChatSetting };
if (!tabStorage.settings) {
tabStorage.settings = {
modelIndex: llmManager.currentModelIndex,
enableTools: [],
enableWebSearch: false,
temperature: 0.7,
contextLength: 10,
systemPrompt: ''
} as ChatSetting;
}
// 使
const currentModel = llms[llmManager.currentModelIndex].userModel;
const selectedModelIndex = ref(llms[llmManager.currentModelIndex].models.indexOf(currentModel));
const availableModels = computed(() => {
return llms[llmManager.currentModelIndex].models;
});
const hasSystemPrompt = computed(() => {
return !!tabStorage.settings.systemPrompt?.trim();
});
const showToolsDialog = ref(false);
const availableToolsNum = computed(() => {
return tabStorage.settings.enableTools.filter(tool => tool.enabled).length;
});
// toggleTools
const toggleTools = () => {
showToolsDialog.value = true;
};
const toggleWebSearch = () => {
tabStorage.settings.enableWebSearch = !tabStorage.settings.enableWebSearch;
};
const confirmModelChange = () => {
showModelDialog.value = false;
};
const onRadioGroupChange = () => {
const currentModel = llms[llmManager.currentModelIndex].models[selectedModelIndex.value];
llms[llmManager.currentModelIndex].userModel = currentModel;
saveSetting();
};
const bridge = useMessageBridge();
let commandCancel: (() => void);
onMounted(() => {
commandCancel = bridge.addCommandListener('tools/list', (data: CasualRestAPI<ToolsListResponse>) => {
allTools.value = data.msg.tools || [];
tabStorage.settings.enableTools = [];
for (const tool of allTools.value) {
tabStorage.settings.enableTools.push({
name: tool.name,
description: tool.description,
enabled: true
});
}
}, { once: false });
bridge.postMessage({
command: 'tools/list'
});
});
onUnmounted(() => {
if (commandCancel) {
commandCancel();
}
})
// JSON Schema
const activeToolsSchemaHTML = computed(() => {
const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
const jsonString = JSON.stringify(toolsSchema, null, 2);
return markdownToHtml(
"```json\n" + jsonString + "\n```"
);
});
// -
const enableAllTools = () => {
tabStorage.settings.enableTools.forEach(tool => {
tool.enabled = true;
});
};
// -
const disableAllTools = () => {
tabStorage.settings.enableTools.forEach(tool => {
tool.enabled = false;
});
};
</script>
<style>
.chat-settings {
display: flex;
gap: 2px;
padding: 8px 0;
background-color: var(--sidebar);
width: fit-content;
border-radius: 99%;
bottom: 0px;
z-index: 10;
position: absolute;
}
.setting-button {
padding: 5px 8px;
margin-right: 3px;
border-radius: .5em;
font-size: 12px;
position: relative;
user-select: none;
-webkit-user-drag: none;
display: flex;
align-items: center;
cursor: pointer;
transition: var(--animation-3s);
}
.setting-button.active {
background-color: var(--el-color-primary);
color: var(--el-text-color-primary);
transition: var(--animation-3s);
}
.setting-button.active:hover {
background-color: var(--el-color-primary);
transition: var(--animation-3s);
}
.setting-button:hover {
background-color: var(--background);
transition: var(--animation-3s);
}
.value-badge {
font-size: 10px;
padding: 1px 4px;
border-radius: 4px;
}
.slider-container {
padding: 0 10px;
}
.icon-temperature {
font-size: 18px;
}
.icon-length {
font-size: 16px;
}
.slider-tips {
display: flex;
justify-content: space-between;
margin-top: 10px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
/* 新增工具相关样式 */
.tools-container {
padding: 10px;
}
.tool-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid var(--el-border-color-light);
}
.tool-info {
flex: 1;
margin-right: 20px;
}
.tool-name {
font-weight: 500;
margin-bottom: 4px;
}
.tool-description {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.tools-dialog-container .el-switch__core {
border: 1px solid var(--main-color) !important;
}
.tools-dialog-container .el-switch .el-switch__action {
background-color: var(--main-color);
}
.tools-dialog-container .el-switch.is-checked .el-switch__action {
background-color: var(--sidebar);
}
/* 新增工具对话框样式 */
.tools-dialog-container {
display: flex;
gap: 16px;
}
.tools-list {
flex: 1;
border-right: 1px solid var(--el-border-color);
padding-right: 16px;
}
.schema-viewer {
flex: 1;
}
.schema-viewer pre {
margin: 0;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
background-color: var(--el-bg-color-overlay);
}
.schema-viewer .openmcp-code-block {
border: none;
}
.schema-viewer code {
font-family: var(--code-font-family);
font-size: 12px;
color: var(--el-text-color-primary);
}
.badge-outer {
position: relative;
}
.badge-inner {
position: absolute;
color: var(--foreground);
background-color: var(--main-color);
border-radius: 50%;
padding: 2px 6px;
font-size: 10px;
top: -16px;
right: -18px;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.2);
}
</style>

View File

@ -75,7 +75,7 @@ onMounted(() => {
const targetResource = resourcesManager.templates.find(template => template.name === tabStorage.currentResourceName); const targetResource = resourcesManager.templates.find(template => template.name === tabStorage.currentResourceName);
if (targetResource === undefined) { if (targetResource === undefined) {
tabStorage.currentResourceName = resourcesManager.templates[0].name; tabStorage.currentResourceName = resourcesManager.templates[0]?.name;
tabStorage.lastResourceReadResponse = undefined; tabStorage.lastResourceReadResponse = undefined;
} }
}, { once: false }); }, { once: false });

View File

@ -40,7 +40,7 @@ import { defineComponent, defineProps, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { tabs } from '../panel'; import { tabs } from '../panel';
import { ToolStorage } from './tools'; import { ToolStorage } from './tools';
import { markdownToHtml } from '../chat/markdown'; import { markdownToHtml } from '../chat/markdown/markdown';
defineComponent({ name: 'tool-logger' }); defineComponent({ name: 'tool-logger' });
const { t } = useI18n(); const { t } = useI18n();

View File

@ -23,25 +23,6 @@ export function getCurrentTime() {
return timeStr; return timeStr;
} }
export function getBase64StringByFilename(filename: string) {
const bridge = useMessageBridge();
return new Promise<string>(resolve => {
bridge.addCommandListener('ocr/get-ocr-image', data => {
const { code, msg = {} } = data;
resolve(msg.base64String);
}, { once: true});
bridge.postMessage({
command: 'ocr/get-ocr-image',
data: {
filename
}
});
});
}
const blobUrlCache = new Map<string, string>(); const blobUrlCache = new Map<string, string>();
export async function getBlobUrlByFilename(filename: string) { export async function getBlobUrlByFilename(filename: string) {
@ -50,7 +31,13 @@ export async function getBlobUrlByFilename(filename: string) {
return blobUrlCache.get(filename); return blobUrlCache.get(filename);
} }
const base64String = await getBase64StringByFilename(filename); const bridge = useMessageBridge();
const res = await bridge.commandRequest('ocr/get-ocr-image', { filename });
if (res?.code !== 200) {
return '';
}
const base64String = res?.msg?.base64String;
if (!base64String) { if (!base64String) {
return ''; return '';

View File

@ -4,7 +4,7 @@ import router from "./router";
import i18n from './i18n'; import i18n from './i18n';
import ElementPlus from 'element-plus'; import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css'; import 'element-plus/dist/index.css';
import "@/components/main-panel/chat/prism/prism.css"; import "@/components/main-panel/chat/markdown/prism/prism.css";
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
createApp(App) createApp(App)

View File

@ -72,6 +72,7 @@ export class ClientController {
@Controller('resources/templates/list') @Controller('resources/templates/list')
async listResourceTemplates(client: RequestClientType, data: any, webview: PostMessageble) { async listResourceTemplates(client: RequestClientType, data: any, webview: PostMessageble) {
if (!client) { if (!client) {
return { return {
code: 501, code: 501,