410 lines
10 KiB
Vue
410 lines
10 KiB
Vue
<template>
|
|
<div class="chat-settings">
|
|
<el-tooltip content="选择模型" placement="top">
|
|
<div class="setting-button" @click="showModelDialog = true">
|
|
<span class="iconfont icon-model">
|
|
{{ currentServerName }}/{{ currentModelName }}
|
|
</span>
|
|
</div>
|
|
</el-tooltip>
|
|
|
|
<el-tooltip content="系统提示词" placement="top">
|
|
<div class="setting-button" :class="{ 'active': hasSystemPrompt }" size="small"
|
|
@click="showSystemPromptDialog = true">
|
|
<span class="iconfont icon-robot"></span>
|
|
</div>
|
|
</el-tooltip>
|
|
|
|
<el-tooltip content="工具使用" placement="top">
|
|
<div class="setting-button" :class="{ 'active': toolActive }" size="small"
|
|
@click="toggleTools">
|
|
<span class="iconfont icon-tool"></span>
|
|
</div>
|
|
</el-tooltip>
|
|
|
|
<el-tooltip content="网络搜索" 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="温度参数" 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="上下文长度" 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="选择模型" 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">取消</el-button>
|
|
<el-button type="primary" @click="confirmModelChange">确认</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- System Prompt对话框 -->
|
|
<el-dialog v-model="showSystemPromptDialog" title="系统提示词" width="600px">
|
|
<el-input v-model="tabStorage.settings.systemPrompt" type="textarea" :rows="8"
|
|
placeholder="输入系统提示词(例如:你是一个专业的前端开发助手,用中文回答)" clearable />
|
|
<template #footer>
|
|
<el-button @click="showSystemPromptDialog = false">关闭</el-button>
|
|
<el-button type="primary" @click="showSystemPromptDialog = false">保存</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 温度参数滑块 -->
|
|
<el-dialog v-model="showTemperatureSlider" title="设置温度参数" 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>精确(0)</span>
|
|
<span>平衡(1)</span>
|
|
<span>创意(2)</span>
|
|
</div>
|
|
</div>
|
|
<template #footer>
|
|
<el-button @click="showTemperatureSlider = false">关闭</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 上下文长度设置 - 改为滑块形式 -->
|
|
<el-dialog v-model="showContextLengthDialog" title="设置上下文长度" width="400px">
|
|
<div class="slider-container">
|
|
<el-slider v-model="tabStorage.settings.contextLength" :min="0" :max="99" :step="1" />
|
|
<div class="slider-tips">
|
|
<span>0: 无上下文</span>
|
|
<span>10: 默认</span>
|
|
<span>99: 最大</span>
|
|
</div>
|
|
</div>
|
|
<template #footer>
|
|
<el-button @click="showContextLengthDialog = false">关闭</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">关闭</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';
|
|
|
|
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 selectedModelIndex = ref(llmManager.currentModelIndex);
|
|
|
|
const availableModels = computed(() => {
|
|
return llms[llmManager.currentModelIndex].models;
|
|
});
|
|
|
|
const hasSystemPrompt = computed(() => {
|
|
return !!tabStorage.settings.systemPrompt?.trim();
|
|
});
|
|
|
|
|
|
const showToolsDialog = ref(false);
|
|
|
|
const toolActive = computed(() => {
|
|
const availableTools = tabStorage.settings.enableTools.filter(tool => tool.enabled);
|
|
return availableTools.length > 0;
|
|
});
|
|
|
|
// 修改 toggleTools 方法
|
|
const toggleTools = () => {
|
|
showToolsDialog.value = true;
|
|
};
|
|
|
|
|
|
const toggleWebSearch = () => {
|
|
tabStorage.settings.enableWebSearch = !tabStorage.settings.enableWebSearch;
|
|
};
|
|
|
|
const confirmModelChange = () => {
|
|
llmManager.currentModelIndex = selectedModelIndex.value;
|
|
showModelDialog.value = false;
|
|
};
|
|
|
|
const onRadioGroupChange = () => {
|
|
const currentModel = llms[llmManager.currentModelIndex].models[selectedModelIndex.value];
|
|
llms[llmManager.currentModelIndex].userModel = currentModel;
|
|
};
|
|
|
|
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);
|
|
}
|
|
</style> |