实现客户端部分的 prompt 支持
This commit is contained in:
parent
cf16cff7cf
commit
5a2a699a51
@ -52,7 +52,7 @@
|
||||
<footer class="chat-footer" ref="footerRef">
|
||||
<div class="input-area">
|
||||
<div class="input-wrapper">
|
||||
<Setting :tabId="tabId" />
|
||||
<Setting :tabId="tabId" v-model="userInput" />
|
||||
|
||||
<KCuteTextarea
|
||||
v-model="userInput"
|
||||
|
@ -1,37 +1,74 @@
|
||||
<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>
|
||||
<el-tooltip :content="t('prompts')" placement="top">
|
||||
<div class="setting-button" @click="showChoosePrompt = true">
|
||||
<span class="iconfont icon-chat"></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>
|
||||
<el-dialog v-model="showChoosePrompt" :title="t('prompts')" width="400px">
|
||||
|
||||
<div class="prompt-template-container-scrollbar" v-if="!selectPrompt">
|
||||
<PromptTemplates
|
||||
:tab-id="-1"
|
||||
@prompt-selected="prompt => selectPrompt = prompt"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<PromptReader
|
||||
:tab-id="-1"
|
||||
:current-prompt-name="selectPrompt!.name"
|
||||
@prompt-get-response="msg => whenGetPromptResponse(msg)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="showContextLengthDialog = false">{{ t("cancel") }}</el-button>
|
||||
<el-button v-if="selectPrompt" @click="selectPrompt = undefined;">{{ t('return') }}</el-button>
|
||||
<el-button @click="showChoosePrompt = false; selectPrompt = undefined;">{{ t("cancel") }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, inject, ref } from 'vue';
|
||||
import { inject, ref, defineProps, PropType, defineEmits } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ChatStorage } from '../chat';
|
||||
import { PromptsGetResponse, PromptTemplate } from '@/hook/type';
|
||||
|
||||
import PromptTemplates from '../../prompt/prompt-templates.vue';
|
||||
import PromptReader from '../../prompt/prompt-reader.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const tabStorage = inject('tabStorage') as ChatStorage;
|
||||
|
||||
const showContextLengthDialog = ref(false);
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const emits = defineEmits([ 'update:modelValue' ]);
|
||||
|
||||
let selectPrompt = ref<PromptTemplate | undefined>(undefined);
|
||||
|
||||
const showChoosePrompt = ref(false);
|
||||
|
||||
function whenGetPromptResponse(msg: PromptsGetResponse) {
|
||||
try {
|
||||
const content = msg.messages[0].content;
|
||||
|
||||
if (content) {
|
||||
emits('update:modelValue', props.modelValue + content);
|
||||
}
|
||||
|
||||
showChoosePrompt.value = false;
|
||||
|
||||
} catch (error) {
|
||||
ElMessage.error((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
<Model />
|
||||
<SystemPrompt />
|
||||
<ToolUse />
|
||||
<Prompt v-model="val" />
|
||||
<Websearch />
|
||||
<Temperature />
|
||||
<ContextLength />
|
||||
@ -10,7 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, provide } from 'vue';
|
||||
import { defineProps, provide, ref } from 'vue';
|
||||
import { llmManager } from '@/views/setting/llm';
|
||||
import { tabs } from '../../panel';
|
||||
import type { ChatSetting, ChatStorage } from '../chat';
|
||||
@ -18,17 +19,24 @@ import type { ChatSetting, ChatStorage } from '../chat';
|
||||
import Model from './model.vue';
|
||||
import SystemPrompt from './system-prompt.vue';
|
||||
import ToolUse from './tool-use.vue';
|
||||
import Prompt from './prompt.vue';
|
||||
import Websearch from './websearch.vue';
|
||||
import Temperature from './temperature.vue';
|
||||
import ContextLength from './context-length.vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
tabId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const val = ref('');
|
||||
|
||||
const tab = tabs.content[props.tabId];
|
||||
const tabStorage = tab.storage as ChatStorage & { settings: ChatSetting };
|
||||
|
||||
|
@ -32,12 +32,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, defineProps, watch, ref, computed } from 'vue';
|
||||
import { defineComponent, defineProps, defineEmits, watch, ref, computed, reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { tabs } from '../panel';
|
||||
import { parsePromptTemplate, promptsManager, PromptStorage } from './prompts';
|
||||
import { CasualRestAPI, PromptsGetResponse } from '@/hook/type';
|
||||
import { promptsManager, PromptStorage } from './prompts';
|
||||
import { PromptsGetResponse } from '@/hook/type';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp';
|
||||
|
||||
@ -49,11 +49,26 @@ const props = defineProps({
|
||||
tabId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
currentPromptName: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
const tab = tabs.content[props.tabId];
|
||||
const tabStorage = tab.storage as PromptStorage;
|
||||
const emits = defineEmits(['prompt-get-response']);
|
||||
|
||||
let tabStorage: PromptStorage;
|
||||
|
||||
if (props.tabId >= 0) {
|
||||
tabStorage = tabs.content[props.tabId].storage as PromptStorage;
|
||||
} else {
|
||||
tabStorage = reactive({
|
||||
currentPromptName: props.currentPromptName || '',
|
||||
formData: {},
|
||||
lastPromptGetResponse: undefined
|
||||
});
|
||||
}
|
||||
|
||||
if (!tabStorage.formData) {
|
||||
tabStorage.formData = {};
|
||||
@ -108,7 +123,6 @@ const initFormData = () => {
|
||||
newSchemaDataForm[param.name] = tabStorage.formData[param.name];
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
@ -116,24 +130,25 @@ const resetForm = () => {
|
||||
responseData.value = undefined;
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
async function handleSubmit() {
|
||||
const bridge = useMessageBridge();
|
||||
|
||||
bridge.addCommandListener('prompts/get', (data: CasualRestAPI<PromptsGetResponse>) => {
|
||||
tabStorage.lastPromptGetResponse = data.msg;
|
||||
}, { once: true });
|
||||
|
||||
bridge.postMessage({
|
||||
command: 'prompts/get',
|
||||
data: { promptId: currentPrompt.value.name, args: JSON.parse(JSON.stringify(tabStorage.formData)) }
|
||||
const { code, msg } = await bridge.commandRequest('prompts/get', {
|
||||
promptId: currentPrompt.value.name,
|
||||
args: JSON.parse(JSON.stringify(tabStorage.formData))
|
||||
});
|
||||
|
||||
tabStorage.lastPromptGetResponse = msg;
|
||||
|
||||
emits('prompt-get-response', msg);
|
||||
}
|
||||
|
||||
watch(() => tabStorage.currentPromptName, () => {
|
||||
if (props.tabId >= 0) {
|
||||
watch(() => tabStorage.currentPromptName, () => {
|
||||
initFormData();
|
||||
resetForm();
|
||||
}, { immediate: true });
|
||||
|
||||
}, { immediate: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<div class="prompt-template-container">
|
||||
<div
|
||||
class="item"
|
||||
:class="{ 'active': tabStorage.currentPromptName === template.name }"
|
||||
:class="{ 'active': props.tabId >= 0 && tabStorage.currentPromptName === template.name }"
|
||||
v-for="template of promptsManager.templates"
|
||||
:key="template.name"
|
||||
@click="handleClick(template)"
|
||||
@ -28,7 +28,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { CasualRestAPI, PromptTemplate, PromptsListResponse } from '@/hook/type';
|
||||
import { onMounted, onUnmounted, defineProps } from 'vue';
|
||||
import { onMounted, onUnmounted, defineProps, defineEmits, reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { promptsManager, PromptStorage } from './prompts';
|
||||
import { tabs } from '../panel';
|
||||
@ -44,8 +44,20 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const tab = tabs.content[props.tabId];
|
||||
const tabStorage = tab.storage as PromptStorage;
|
||||
const emits = defineEmits([ 'prompt-selected' ]);
|
||||
|
||||
let tabStorage: PromptStorage;
|
||||
|
||||
if (props.tabId >= 0) {
|
||||
const tab = tabs.content[props.tabId];
|
||||
tabStorage = tab.storage as PromptStorage;
|
||||
} else {
|
||||
tabStorage = reactive({
|
||||
currentPromptName: '',
|
||||
formData: {},
|
||||
lastPromptGetResponse: undefined
|
||||
});
|
||||
}
|
||||
|
||||
function reloadPrompts(option: { first: boolean }) {
|
||||
bridge.postMessage({
|
||||
@ -62,9 +74,11 @@ function reloadPrompts(option: { first: boolean }) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick(template: PromptTemplate) {
|
||||
tabStorage.currentPromptName = template.name;
|
||||
function handleClick(prompt: PromptTemplate) {
|
||||
tabStorage.currentPromptName = prompt.name;
|
||||
tabStorage.lastPromptGetResponse = undefined;
|
||||
|
||||
emits('prompt-selected', prompt);
|
||||
}
|
||||
|
||||
let commandCancel: (() => void);
|
||||
|
@ -151,5 +151,6 @@
|
||||
"generate-answer": "جارٍ إنشاء الإجابة",
|
||||
"choose-presetting": "اختر الإعداد المسبق",
|
||||
"cwd": "دليل التنفيذ",
|
||||
"mcp-server-timeout": "أطول وقت لاستدعاء أداة MCP"
|
||||
"mcp-server-timeout": "أطول وقت لاستدعاء أداة MCP",
|
||||
"return": "عودة"
|
||||
}
|
@ -151,5 +151,6 @@
|
||||
"generate-answer": "Antwort wird generiert",
|
||||
"choose-presetting": "Voreinstellung auswählen",
|
||||
"cwd": "Ausführungsverzeichnis",
|
||||
"mcp-server-timeout": "Maximale Aufrufzeit des MCP-Tools"
|
||||
"mcp-server-timeout": "Maximale Aufrufzeit des MCP-Tools",
|
||||
"return": "Zurück"
|
||||
}
|
@ -151,5 +151,6 @@
|
||||
"generate-answer": "Generating answer",
|
||||
"choose-presetting": "Select preset",
|
||||
"cwd": "Execution directory",
|
||||
"mcp-server-timeout": "Maximum call time of MCP tool"
|
||||
"mcp-server-timeout": "Maximum call time of MCP tool",
|
||||
"return": "Back"
|
||||
}
|
@ -151,5 +151,6 @@
|
||||
"generate-answer": "Génération de la réponse",
|
||||
"choose-presetting": "Sélectionner un préréglage",
|
||||
"cwd": "Répertoire d'exécution",
|
||||
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP"
|
||||
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
|
||||
"return": "Retour"
|
||||
}
|
@ -151,5 +151,6 @@
|
||||
"generate-answer": "回答を生成中",
|
||||
"choose-presetting": "プリセットを選択",
|
||||
"cwd": "実行ディレクトリ",
|
||||
"mcp-server-timeout": "MCPツールの最大呼び出し時間"
|
||||
"mcp-server-timeout": "MCPツールの最大呼び出し時間",
|
||||
"return": "戻る"
|
||||
}
|
@ -151,5 +151,6 @@
|
||||
"generate-answer": "답변 생성 중",
|
||||
"choose-presetting": "프리셋 선택",
|
||||
"cwd": "실행 디렉터리",
|
||||
"mcp-server-timeout": "MCP 도구 최대 호출 시간"
|
||||
"mcp-server-timeout": "MCP 도구 최대 호출 시간",
|
||||
"return": "돌아가기"
|
||||
}
|
@ -151,5 +151,6 @@
|
||||
"generate-answer": "Генерация ответа",
|
||||
"choose-presetting": "Выбрать预设",
|
||||
"cwd": "Каталог выполнения",
|
||||
"mcp-server-timeout": "Максимальное время вызова инструмента MCP"
|
||||
"mcp-server-timeout": "Максимальное время вызова инструмента MCP",
|
||||
"return": "Назад"
|
||||
}
|
@ -151,5 +151,6 @@
|
||||
"generate-answer": "正在生成答案",
|
||||
"choose-presetting": "选择预设",
|
||||
"cwd": "执行目录",
|
||||
"mcp-server-timeout": "MCP工具最长调用时间"
|
||||
"mcp-server-timeout": "MCP工具最长调用时间",
|
||||
"return": "返回"
|
||||
}
|
@ -151,5 +151,6 @@
|
||||
"generate-answer": "正在生成答案",
|
||||
"choose-presetting": "選擇預設",
|
||||
"cwd": "執行目錄",
|
||||
"mcp-server-timeout": "MCP工具最長調用時間"
|
||||
"mcp-server-timeout": "MCP工具最長調用時間",
|
||||
"return": "返回"
|
||||
}
|
@ -7,128 +7,9 @@
|
||||
"type": "blank",
|
||||
"componentIndex": 3,
|
||||
"storage": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请问杭州的天气",
|
||||
"extraInfo": {
|
||||
"created": 1745258229256,
|
||||
"serverName": "deepseek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_0_57ad3eab-cd9d-4403-bdf0-31d00b83b98c",
|
||||
"index": 0,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather_by_city_code",
|
||||
"arguments": "{\"city_code\":101210101}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1745258234470,
|
||||
"serverName": "deepseek",
|
||||
"usage": {
|
||||
"prompt_tokens": 570,
|
||||
"completion_tokens": 26,
|
||||
"total_tokens": 596,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 512
|
||||
},
|
||||
"prompt_cache_hit_tokens": 512,
|
||||
"prompt_cache_miss_tokens": 58
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_0_57ad3eab-cd9d-4403-bdf0-31d00b83b98c",
|
||||
"content": "[{\"type\":\"text\",\"text\":\"CityWeather(city_name_en='hangzhou', city_name_cn='杭州', city_code='101210101', temp='21', wd='', ws='', sd='92%', aqi='13', weather='阴')\"}]",
|
||||
"extraInfo": {
|
||||
"created": 1745258234522,
|
||||
"serverName": "deepseek",
|
||||
"usage": {
|
||||
"prompt_tokens": 570,
|
||||
"completion_tokens": 26,
|
||||
"total_tokens": 596,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 512
|
||||
},
|
||||
"prompt_cache_hit_tokens": 512,
|
||||
"prompt_cache_miss_tokens": 58
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "杭州的天气信息如下:\n\n- 城市:杭州\n- 温度:21°C\n- 天气状况:阴\n- 湿度:92%\n- 空气质量指数 (AQI):13(优秀)\n\n天气较为舒适,适合出行!",
|
||||
"extraInfo": {
|
||||
"created": 1745258240571,
|
||||
"serverName": "deepseek",
|
||||
"usage": {
|
||||
"prompt_tokens": 660,
|
||||
"completion_tokens": 52,
|
||||
"total_tokens": 712,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 576
|
||||
},
|
||||
"prompt_cache_hit_tokens": 576,
|
||||
"prompt_cache_miss_tokens": 84
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "再试一次",
|
||||
"extraInfo": {
|
||||
"created": 1745495349299,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_jpm4v1e92v4nq650wlqkhm56",
|
||||
"index": 0,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather_by_city_code",
|
||||
"arguments": "{\"city_code\":101210101}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraInfo": {
|
||||
"created": 1745495350097,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_jpm4v1e92v4nq650wlqkhm56",
|
||||
"content": "[{\"type\":\"text\",\"text\":\"CityWeather(city_name_en='hangzhou', city_name_cn='杭州', city_code='101210101', temp='18.3', wd='', ws='', sd='89%', aqi='57', weather='阴')\"}]",
|
||||
"extraInfo": {
|
||||
"created": 1745495350319,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "杭州的最新天气信息如下:\n\n- 城市:杭州\n- 温度:18.3°C\n- 天气状况:阴\n- 湿度:89%\n- 空气质量指数 (AQI):57(良好)\n\n天气较为凉爽,适合外出活动!",
|
||||
"extraInfo": {
|
||||
"created": 1745495352693,
|
||||
"serverName": "Huoshan DeepSeek"
|
||||
}
|
||||
}
|
||||
],
|
||||
"messages": [],
|
||||
"settings": {
|
||||
"modelIndex": 0,
|
||||
"modelIndex": 15,
|
||||
"enableTools": [
|
||||
{
|
||||
"name": "add",
|
||||
@ -158,29 +39,51 @@
|
||||
],
|
||||
"enableWebSearch": false,
|
||||
"temperature": 0.7,
|
||||
"contextLength": 10,
|
||||
"contextLength": 20,
|
||||
"systemPrompt": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "工具",
|
||||
"icon": "icon-tool",
|
||||
"type": "tool",
|
||||
"componentIndex": 2,
|
||||
"name": "交互测试",
|
||||
"icon": "icon-robot",
|
||||
"type": "blank",
|
||||
"componentIndex": 3,
|
||||
"storage": {
|
||||
"currentToolName": "get_weather_by_city_code",
|
||||
"formData": {
|
||||
"city_code": 0
|
||||
},
|
||||
"lastToolCallResponse": {
|
||||
"content": [
|
||||
"messages": [],
|
||||
"settings": {
|
||||
"modelIndex": 15,
|
||||
"enableTools": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "CityWeather(city_name_en='hangzhou', city_name_cn='杭州', city_code='101210101', temp='20.3', wd='', ws='', sd='89%', aqi='38', weather='阴')"
|
||||
"name": "add",
|
||||
"description": "对两个数字进行实数域的加法",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "multiply",
|
||||
"description": "对两个数字进行实数域的乘法运算",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "is_even",
|
||||
"description": "判断一个整数是否为偶数",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "capitalize",
|
||||
"description": "将字符串首字母大写",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "get_weather_by_city_code",
|
||||
"description": "根据城市天气预报的城市编码 (int),获取指定城市的天气信息",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
"enableWebSearch": false,
|
||||
"temperature": 0.7,
|
||||
"contextLength": 20,
|
||||
"systemPrompt": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user