support ai-mook
This commit is contained in:
parent
1ce5b3a60a
commit
34a6001455
@ -58,7 +58,7 @@ export interface EnableToolItem {
|
||||
}
|
||||
|
||||
export interface ChatSetting {
|
||||
modelIndex: number
|
||||
modelIndex?: number
|
||||
systemPrompt: string
|
||||
enableTools: EnableToolItem[]
|
||||
temperature: number
|
||||
|
@ -162,16 +162,15 @@ provide('tabStorage', tabStorage);
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.tools-dialog-container .el-switch__core {
|
||||
.el-switch__core {
|
||||
border: 1px solid var(--main-color) !important;
|
||||
}
|
||||
|
||||
|
||||
.tools-dialog-container .el-switch .el-switch__action {
|
||||
.el-switch .el-switch__action {
|
||||
background-color: var(--main-color);
|
||||
}
|
||||
|
||||
.tools-dialog-container .el-switch.is-checked .el-switch__action {
|
||||
.el-switch.is-checked .el-switch__action {
|
||||
background-color: var(--sidebar);
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ export class TaskLoop {
|
||||
private bridge: MessageBridge;
|
||||
private streamingContent: Ref<string>;
|
||||
private streamingToolCalls: Ref<ToolCall[]>;
|
||||
private aborted = false;
|
||||
|
||||
private currentChatId = '';
|
||||
private onError: (error: IErrorMssage) => void = (msg) => { };
|
||||
@ -318,6 +319,7 @@ export class TaskLoop {
|
||||
});
|
||||
this.streamingContent.value = '';
|
||||
this.streamingToolCalls.value = [];
|
||||
this.aborted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -545,6 +547,7 @@ export class TaskLoop {
|
||||
maxEpochs = 50,
|
||||
verbose = 0
|
||||
} = this.taskOptions || {};
|
||||
this.aborted = false;
|
||||
|
||||
for (let i = 0; i < maxEpochs; ++i) {
|
||||
|
||||
@ -570,6 +573,12 @@ export class TaskLoop {
|
||||
// 发送请求
|
||||
const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter);
|
||||
|
||||
// 如果在调用过程中出发了 abort,则直接中断
|
||||
if (this.aborted) {
|
||||
this.aborted = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果存在需要调度的工具
|
||||
if (this.streamingToolCalls.value.length > 0) {
|
||||
|
||||
@ -597,8 +606,19 @@ export class TaskLoop {
|
||||
|
||||
// ready to call tools
|
||||
toolCall = this.consumeToolCalls(toolCall);
|
||||
|
||||
if (this.aborted) {
|
||||
this.aborted = false;
|
||||
break;
|
||||
}
|
||||
|
||||
let toolCallResult = await handleToolCalls(toolCall);
|
||||
|
||||
if (this.aborted) {
|
||||
this.aborted = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// hook : finish call tools
|
||||
toolCallResult = this.consumeToolCalleds(toolCallResult);
|
||||
|
||||
@ -656,6 +676,11 @@ export class TaskLoop {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.aborted) {
|
||||
this.aborted = false;
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (this.streamingContent.value) {
|
||||
tabStorage.messages.push({
|
||||
role: 'assistant',
|
||||
|
@ -33,9 +33,36 @@
|
||||
<el-button @click="resetForm">
|
||||
{{ t('reset') }}
|
||||
</el-button>
|
||||
<el-button @click="generateMockData">
|
||||
<el-button @click="generateMockData" :loading="mockLoading"
|
||||
:disabled="loading || aiMockLoading || mockLoading">
|
||||
{{ 'mook' }}
|
||||
</el-button>
|
||||
|
||||
|
||||
<el-popover placement="top" width="350" trigger="click" v-model:visible="aiPromptVisible">
|
||||
<template #reference>
|
||||
<el-button :loading="aiMockLoading" :disabled="loading || aiMockLoading || mockLoading">
|
||||
{{ 'ai-mook' }}
|
||||
</el-button>
|
||||
</template>
|
||||
<div style="margin-bottom: 8px; font-weight: bold;">
|
||||
{{ t('edit-ai-mook-prompt') }}
|
||||
</div>
|
||||
<el-input type="textarea" v-model="aiMookPrompt" :rows="2" style="margin-bottom: 8px;" />
|
||||
<div style="display: flex; align-items: center; margin-bottom: 8px;">
|
||||
<el-switch
|
||||
v-model="enableXmlWrapper"
|
||||
style="margin-right: 8px;"
|
||||
/>
|
||||
<span style="opacity: 0.7;">XML</span>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<el-button size="small" @click="aiPromptVisible = false">{{ t('cancel') }}</el-button>
|
||||
<el-button size="small" type="primary" :loading="aiMockLoading" @click="onAIMookConfirm">
|
||||
{{ t('confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@ -44,7 +71,7 @@
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, defineProps, watch, ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
||||
import { tabs } from '../panel';
|
||||
import type { ToolStorage } from './tools';
|
||||
import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp';
|
||||
@ -54,6 +81,10 @@ import { mcpClientAdapter } from '@/views/connect/core';
|
||||
import { JSONSchemaFaker } from 'json-schema-faker';
|
||||
|
||||
defineComponent({ name: 'tool-executor' });
|
||||
const mockLoading = ref(false);
|
||||
const aiMockLoading = ref(false);
|
||||
const aiPromptVisible = ref(false);
|
||||
const enableXmlWrapper = ref(false);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -85,6 +116,7 @@ const currentTool = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const aiMookPrompt = ref(`please call the tool ${currentTool.value?.name || ''} to make some test`);
|
||||
|
||||
const formRules = computed<FormRules>(() => {
|
||||
const rules: FormRules = {};
|
||||
@ -132,27 +164,89 @@ const resetForm = () => {
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
import { TaskLoop } from '@/components/main-panel/chat/core/task-loop';
|
||||
import type { ChatStorage } from '../chat/chat-box/chat';
|
||||
|
||||
const onAIMookConfirm = async () => {
|
||||
aiPromptVisible.value = false;
|
||||
await generateAIMockData(aiMookPrompt.value);
|
||||
};
|
||||
|
||||
const generateAIMockData = async (prompt?: string) => {
|
||||
if (!currentTool.value?.inputSchema) return;
|
||||
aiMockLoading.value = true;
|
||||
try {
|
||||
const loop = new TaskLoop({ maxEpochs: 1 });
|
||||
const usePrompt = prompt || `please call the tool ${currentTool.value.name} to make some test`;
|
||||
const chatStorage = {
|
||||
messages: [],
|
||||
settings: {
|
||||
temperature: 0.6,
|
||||
systemPrompt: '',
|
||||
enableTools: [{
|
||||
name: currentTool.value.name,
|
||||
description: currentTool.value.description,
|
||||
inputSchema: currentTool.value.inputSchema,
|
||||
enabled: true
|
||||
}],
|
||||
enableWebSearch: false,
|
||||
contextLength: 5,
|
||||
enableXmlWrapper: enableXmlWrapper.value,
|
||||
parallelToolCalls: false
|
||||
}
|
||||
} as ChatStorage;
|
||||
|
||||
loop.setMaxEpochs(1);
|
||||
|
||||
let aiMockJson: any = undefined;
|
||||
|
||||
loop.registerOnToolCall(toolCall => {
|
||||
if (toolCall.function?.name === currentTool.value?.name) {
|
||||
try {
|
||||
const toolArgs = JSON.parse(toolCall.function?.arguments || '{}');
|
||||
aiMockJson = toolArgs;
|
||||
} catch (e) {
|
||||
ElMessage.error('AI 生成的 JSON 解析错误');
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('AI 调用了未知的工具');
|
||||
}
|
||||
loop.abort();
|
||||
return toolCall;
|
||||
});
|
||||
|
||||
loop.registerOnError(error => {
|
||||
ElMessage.error(error + '');
|
||||
});
|
||||
|
||||
await loop.start(chatStorage, usePrompt);
|
||||
|
||||
if (aiMockJson && typeof aiMockJson === 'object') {
|
||||
Object.keys(aiMockJson).forEach(key => {
|
||||
tabStorage.formData[key] = aiMockJson[key];
|
||||
});
|
||||
formRef.value?.clearValidate?.();
|
||||
}
|
||||
} finally {
|
||||
aiMockLoading.value = false;
|
||||
}
|
||||
};
|
||||
const generateMockData = async () => {
|
||||
if (!currentTool.value?.inputSchema) return;
|
||||
|
||||
// 注册faker到json-schema-faker
|
||||
JSONSchemaFaker.option({
|
||||
useDefaultValue: true,
|
||||
alwaysFakeOptionals: true
|
||||
});
|
||||
|
||||
// 生成mock数据
|
||||
// TODO: as any ?
|
||||
const mockData = await JSONSchemaFaker.resolve(currentTool.value.inputSchema as any) as any;
|
||||
|
||||
// 将 mock 数据绑定到表单
|
||||
Object.keys(mockData).forEach(key => {
|
||||
tabStorage.formData[key] = mockData[key];
|
||||
console.log(mockData[key]);
|
||||
});
|
||||
|
||||
// 如果需要刷新表单校验,可以加上
|
||||
formRef.value?.clearValidate?.();
|
||||
mockLoading.value = true;
|
||||
try {
|
||||
JSONSchemaFaker.option({
|
||||
useDefaultValue: true,
|
||||
alwaysFakeOptionals: true
|
||||
});
|
||||
const mockData = await JSONSchemaFaker.resolve(currentTool.value.inputSchema as any) as any;
|
||||
Object.keys(mockData).forEach(key => {
|
||||
tabStorage.formData[key] = mockData[key];
|
||||
});
|
||||
formRef.value?.clearValidate?.();
|
||||
} finally {
|
||||
mockLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
async function handleExecute() {
|
||||
@ -167,10 +261,15 @@ async function handleExecute() {
|
||||
}
|
||||
}
|
||||
|
||||
watch(currentTool, (tool) => {
|
||||
aiMookPrompt.value = `please call the tool ${tool?.name || ''} to make some test`;
|
||||
});
|
||||
|
||||
watch(() => tabStorage.currentToolName, () => {
|
||||
initFormData();
|
||||
resetForm();
|
||||
}, { immediate: true });
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -20,8 +20,14 @@
|
||||
|
||||
<div v-else>
|
||||
<!-- 展示原本的信息 -->
|
||||
<template v-if="!showRawJson">
|
||||
{{tabStorage.lastToolCallResponse?.content.map(c => c.text).join('\n')}}
|
||||
<template v-if="!showRawJson && tabStorage.lastToolCallResponse">
|
||||
<div
|
||||
v-for="(c, idx) in tabStorage.lastToolCallResponse!.content"
|
||||
:key="idx"
|
||||
class="tool-call-block"
|
||||
>
|
||||
<pre class="tool-call-text">{{ c.text }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 展示 json -->
|
||||
@ -105,4 +111,21 @@ const showRawJson = ref(false);
|
||||
padding: 5px 9px;
|
||||
border-radius: .5em;
|
||||
}
|
||||
|
||||
.tool-call-block {
|
||||
margin-bottom: 12px;
|
||||
padding: 10px 12px;
|
||||
background: rgba(0,0,0,0.04);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.tool-call-text {
|
||||
font-family: var(--code-font-family, monospace);
|
||||
font-size: 15px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
margin: 0;
|
||||
color: var(--el-text-color-primary, #222);
|
||||
}
|
||||
</style>
|
@ -183,5 +183,6 @@
|
||||
"export": "تصدير",
|
||||
"export-filename": "اسم ملف التصدير",
|
||||
"how-to-use": "كيفية الاستخدام؟",
|
||||
"is-required": "هو حقل مطلوب"
|
||||
"is-required": "هو حقل مطلوب",
|
||||
"edit-ai-mook-prompt": "تحرير إشارات AI Mook"
|
||||
}
|
@ -183,5 +183,6 @@
|
||||
"export": "Exportieren",
|
||||
"export-filename": "Exportdateiname",
|
||||
"how-to-use": "Wie benutzt man?",
|
||||
"is-required": "ist ein Pflichtfeld"
|
||||
"is-required": "ist ein Pflichtfeld",
|
||||
"edit-ai-mook-prompt": "AI Mook-Prompts bearbeiten"
|
||||
}
|
@ -183,5 +183,6 @@
|
||||
"export": "Export",
|
||||
"export-filename": "Export filename",
|
||||
"how-to-use": "How to use?",
|
||||
"is-required": "is a required field"
|
||||
"is-required": "is a required field",
|
||||
"edit-ai-mook-prompt": "Edit AI Mook prompts"
|
||||
}
|
@ -183,5 +183,6 @@
|
||||
"export": "Exporter",
|
||||
"export-filename": "Nom du fichier d'exportation",
|
||||
"how-to-use": "Comment utiliser ?",
|
||||
"is-required": "est un champ obligatoire"
|
||||
"is-required": "est un champ obligatoire",
|
||||
"edit-ai-mook-prompt": "Modifier les invites AI Mook"
|
||||
}
|
@ -183,5 +183,6 @@
|
||||
"export": "エクスポート",
|
||||
"export-filename": "エクスポートファイル名",
|
||||
"how-to-use": "使用方法",
|
||||
"is-required": "は必須フィールドです"
|
||||
"is-required": "は必須フィールドです",
|
||||
"edit-ai-mook-prompt": "AI Mookプロンプトを編集"
|
||||
}
|
@ -183,5 +183,6 @@
|
||||
"export": "내보내기",
|
||||
"export-filename": "내보내기 파일 이름",
|
||||
"how-to-use": "사용 방법?",
|
||||
"is-required": "는 필수 필드입니다"
|
||||
"is-required": "는 필수 필드입니다",
|
||||
"edit-ai-mook-prompt": "AI Mook 프롬프트 편집"
|
||||
}
|
@ -183,5 +183,6 @@
|
||||
"export": "Экспорт",
|
||||
"export-filename": "Имя файла экспорта",
|
||||
"how-to-use": "Как использовать?",
|
||||
"is-required": "является обязательным полем"
|
||||
"is-required": "является обязательным полем",
|
||||
"edit-ai-mook-prompt": "Редактировать подсказки AI Mook"
|
||||
}
|
@ -183,5 +183,6 @@
|
||||
"export": "导出",
|
||||
"export-filename": "导出文件名",
|
||||
"how-to-use": "如何使用?",
|
||||
"is-required": "是必填字段"
|
||||
"is-required": "是必填字段",
|
||||
"edit-ai-mook-prompt": "编辑 AI Mook 提示词"
|
||||
}
|
@ -183,5 +183,6 @@
|
||||
"export": "導出",
|
||||
"export-filename": "導出文件名",
|
||||
"how-to-use": "如何使用?",
|
||||
"is-required": "是必填欄位"
|
||||
"is-required": "是必填欄位",
|
||||
"edit-ai-mook-prompt": "編輯AI Mook提示詞"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user