mirror of
https://github.com/kuaifan/dootask.git
synced 2026-04-23 10:18:41 +00:00
refactor(ai): 简化AI模块逻辑
This commit is contained in:
parent
a2acd6f6e4
commit
9e65500748
@ -199,7 +199,6 @@ class Setting extends AbstractModel
|
|||||||
$key = trim((string)($setting[$vendor . '_key'] ?? ''));
|
$key = trim((string)($setting[$vendor . '_key'] ?? ''));
|
||||||
return match ($vendor) {
|
return match ($vendor) {
|
||||||
'ollama' => $key !== '' || !empty($setting['ollama_base_url']),
|
'ollama' => $key !== '' || !empty($setting['ollama_base_url']),
|
||||||
'wenxin' => $key !== '' && !empty($setting['wenxin_secret']),
|
|
||||||
default => $key !== '',
|
default => $key !== '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -204,12 +204,6 @@ class AI
|
|||||||
}
|
}
|
||||||
|
|
||||||
$apiKey = Base::val($setting, $modelType . '_key');
|
$apiKey = Base::val($setting, $modelType . '_key');
|
||||||
if ($modelType === 'wenxin') {
|
|
||||||
$wenxinSecret = Base::val($setting, 'wenxin_secret');
|
|
||||||
if ($wenxinSecret) {
|
|
||||||
$apiKey = trim(($apiKey ?: '') . ':' . $wenxinSecret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($modelType === 'ollama' && empty($apiKey)) {
|
if ($modelType === 'ollama' && empty($apiKey)) {
|
||||||
$apiKey = Base::strRandom(6);
|
$apiKey = Base::strRandom(6);
|
||||||
}
|
}
|
||||||
@ -768,14 +762,6 @@ class AI
|
|||||||
}
|
}
|
||||||
$model = trim((string)($setting[$vendor . '_model'] ?? ''));
|
$model = trim((string)($setting[$vendor . '_model'] ?? ''));
|
||||||
break;
|
break;
|
||||||
case 'wenxin':
|
|
||||||
$secret = trim((string)($setting['wenxin_secret'] ?? ''));
|
|
||||||
if ($key === '' || $secret === '' || $baseUrl === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$key = $key . ':' . $secret;
|
|
||||||
$model = trim((string)($setting[$vendor . '_model'] ?? ''));
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
if ($key === '' || $baseUrl === '') {
|
if ($key === '' || $baseUrl === '') {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -490,10 +490,6 @@ class BotReceiveMsgTask extends AbstractTask
|
|||||||
if ($dialog->session_id) {
|
if ($dialog->session_id) {
|
||||||
$extras['context_key'] = 'session_' . $dialog->session_id;
|
$extras['context_key'] = 'session_' . $dialog->session_id;
|
||||||
}
|
}
|
||||||
// 设置文心一言的API密钥
|
|
||||||
if ($type === 'wenxin') {
|
|
||||||
$extras['api_key'] .= ':' . $setting['wenxin_secret'];
|
|
||||||
}
|
|
||||||
// 群聊清理上下文(群聊不使用上下文)
|
// 群聊清理上下文(群聊不使用上下文)
|
||||||
if ($dialog->type === 'group') {
|
if ($dialog->type === 'group') {
|
||||||
$extras['before_clear'] = 1;
|
$extras['before_clear'] = 1;
|
||||||
|
|||||||
@ -1,232 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="setting-component-item">
|
|
||||||
<Form
|
|
||||||
ref="formData"
|
|
||||||
:model="formData"
|
|
||||||
:rules="ruleData"
|
|
||||||
v-bind="formOptions"
|
|
||||||
@submit.native.prevent>
|
|
||||||
<template v-for="field in fields">
|
|
||||||
<FormItem :label="$L(field.label)" :prop="field.prop">
|
|
||||||
<template v-if="field.type === 'password'">
|
|
||||||
<Input
|
|
||||||
:maxlength="field.maxlength || 255"
|
|
||||||
v-model="formData[field.prop]"
|
|
||||||
type="password"
|
|
||||||
:placeholder="$L(field.placeholder)"
|
|
||||||
:show-word-limit="typeof field.showWordLimit === 'undefined' ? 0.9 : field.showWordLimit"/>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="field.type === 'model'">
|
|
||||||
<Select v-model="formData[field.prop]" transfer>
|
|
||||||
<Option v-for="(item, key) in modelOption(field.prop)" :value="item.value" :key="key">{{ item.label }}</Option>
|
|
||||||
</Select>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="field.type === 'textarea'">
|
|
||||||
<Input
|
|
||||||
:maxlength="field.maxlength || 500"
|
|
||||||
type="textarea"
|
|
||||||
:autosize="{minRows:2,maxRows:6}"
|
|
||||||
v-model="formData[field.prop]"
|
|
||||||
:placeholder="$L(field.placeholder)"
|
|
||||||
:show-word-limit="typeof field.showWordLimit === 'undefined' ? 0.9 : field.showWordLimit"/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<Input
|
|
||||||
:maxlength="field.maxlength || 255"
|
|
||||||
v-model="formData[field.prop]"
|
|
||||||
:placeholder="$L(field.placeholder)"
|
|
||||||
:show-word-limit="typeof field.showWordLimit === 'undefined' ? 0.9 : field.showWordLimit"/>
|
|
||||||
</template>
|
|
||||||
<div v-if="field.link || field.tip" class="form-tip">
|
|
||||||
<template v-if="field.link">
|
|
||||||
{{$L(field.tipPrefix || '获取方式')}} <a :href="field.link" target="_blank">{{ field.link }}</a>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="field.tip">
|
|
||||||
{{$L(field.tip)}}
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-if="field.functions" class="form-tip" style="margin-top:-5px">
|
|
||||||
<a href="javascript:void(0)" @click="functionClick(field.prop)">{{ $L(field.functions) }}</a>
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
</template>
|
|
||||||
</Form>
|
|
||||||
<div class="setting-footer">
|
|
||||||
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{ $L('提交') }}</Button>
|
|
||||||
<Button :loading="loadIng > 0" @click="resetForm">{{ $L('重置') }}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {mapState} from "vuex";
|
|
||||||
import {AIModelNames, AISystemConfig} from "../../../../utils/ai";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "SystemAibot",
|
|
||||||
props: {
|
|
||||||
type: {
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loadIng: 0,
|
|
||||||
formData: {},
|
|
||||||
ruleData: {},
|
|
||||||
aiConfig: AISystemConfig,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.formData = $A.getStorageJson(`systemAibot.${this.type}`);
|
|
||||||
this.systemSetting();
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(['formOptions']),
|
|
||||||
|
|
||||||
fields({aiConfig, type}) {
|
|
||||||
// 如果type不存在于aiList中,返回空数组
|
|
||||||
if (!aiConfig.aiList?.[type]) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取基础fields和当前type的extraFields
|
|
||||||
const baseFields = JSON.parse(JSON.stringify(aiConfig.fields));
|
|
||||||
const { extraFields = [] } = aiConfig.aiList[type];
|
|
||||||
|
|
||||||
// 处理每个field,添加type前缀
|
|
||||||
const prefixedFields = baseFields.map(field => ({
|
|
||||||
...field,
|
|
||||||
prop: `${type}_${field.prop}`
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 处理extraFields
|
|
||||||
extraFields.forEach(extraField => {
|
|
||||||
const newField = {
|
|
||||||
...extraField,
|
|
||||||
prop: `${type}_${extraField.prop}`
|
|
||||||
};
|
|
||||||
|
|
||||||
// 查找是否已存在相同prop的field
|
|
||||||
const existingIndex = prefixedFields.findIndex(f =>
|
|
||||||
f.prop === newField.prop
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingIndex !== -1) {
|
|
||||||
// 如果存在,则覆盖原有的field
|
|
||||||
prefixedFields[existingIndex] = {
|
|
||||||
...prefixedFields[existingIndex],
|
|
||||||
...newField
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// 如果不存在,需要插入
|
|
||||||
if (extraField.after) {
|
|
||||||
// 如果指定了after,找到对应位置插入
|
|
||||||
const afterIndex = prefixedFields.findIndex(f =>
|
|
||||||
f.prop === `${type}_${extraField.after}`
|
|
||||||
);
|
|
||||||
if (afterIndex !== -1) {
|
|
||||||
prefixedFields.splice(afterIndex + 1, 0, newField);
|
|
||||||
} else {
|
|
||||||
prefixedFields.push(newField);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 没有指定after,直接添加到末尾
|
|
||||||
prefixedFields.push(newField);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理sort参数并排序
|
|
||||||
let sort = 9999999;
|
|
||||||
prefixedFields.forEach(field => {
|
|
||||||
if (typeof field.sort === 'undefined') {
|
|
||||||
field.sort = ++sort;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 按sort字段正序排序
|
|
||||||
prefixedFields.sort((a, b) => a.sort - b.sort);
|
|
||||||
|
|
||||||
// 返回处理后的字段
|
|
||||||
return prefixedFields;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
modelOption(prop) {
|
|
||||||
const value = this.formData[prop + 's'];
|
|
||||||
if (value) {
|
|
||||||
return AIModelNames(value)
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
|
|
||||||
submitForm() {
|
|
||||||
this.$refs.formData.validate((valid) => {
|
|
||||||
if (valid) {
|
|
||||||
this.systemSetting(true);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
resetForm() {
|
|
||||||
this.formData = $A.cloneJSON(this.formDatum_bak);
|
|
||||||
},
|
|
||||||
|
|
||||||
systemSetting(save) {
|
|
||||||
const props = this.fields.map(item => item.prop);
|
|
||||||
const data = Object.fromEntries(Object.entries(this.formData).filter(([key]) => props.includes(key)));
|
|
||||||
this.loadIng++;
|
|
||||||
this.$store.dispatch("call", {
|
|
||||||
url: 'system/setting/aibot',
|
|
||||||
data: Object.assign(save ? data : {}, {
|
|
||||||
type: save ? 'save' : 'get',
|
|
||||||
filter: this.type,
|
|
||||||
}),
|
|
||||||
method: save ? 'post' : 'get',
|
|
||||||
}).then(({data}) => {
|
|
||||||
if (save) {
|
|
||||||
$A.messageSuccess('修改成功');
|
|
||||||
}
|
|
||||||
this.$emit('on-update-setting', data);
|
|
||||||
this.formData = data;
|
|
||||||
this.formDatum_bak = $A.cloneJSON(this.formData);
|
|
||||||
$A.setStorage(`systemAibot.${this.type}`, data);
|
|
||||||
}).catch(({msg}) => {
|
|
||||||
if (save) {
|
|
||||||
$A.modalError(msg);
|
|
||||||
}
|
|
||||||
}).finally(_ => {
|
|
||||||
this.loadIng--;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
functionClick(prop) {
|
|
||||||
if (prop === `${this.type}_models`) {
|
|
||||||
const data = {};
|
|
||||||
if (this.type === 'ollama') {
|
|
||||||
if (!this.formData[`${this.type}_base_url`]) {
|
|
||||||
$A.modalError('请先填写 Base URL');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
data.base_url = this.formData[`${this.type}_base_url`];
|
|
||||||
data.key = this.formData[`${this.type}_key`];
|
|
||||||
data.agency = this.formData[`${this.type}_agency`];
|
|
||||||
}
|
|
||||||
this.$store.dispatch("call", {
|
|
||||||
url: 'system/setting/aibot_defmodels?type=' + this.type,
|
|
||||||
data,
|
|
||||||
spinner: 600,
|
|
||||||
}).then(({data}) => {
|
|
||||||
this.formData[prop] = data.models.join('\n');
|
|
||||||
$A.messageSuccess('获取成功');
|
|
||||||
}).catch(({msg}) => {
|
|
||||||
$A.modalError(msg || '获取失败');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$A.messageError('未知操作');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
196
resources/assets/js/utils/ai.js
vendored
196
resources/assets/js/utils/ai.js
vendored
@ -15,201 +15,6 @@ const AIBotMap = {
|
|||||||
wenxin: "文心一言",
|
wenxin: "文心一言",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* AI 系统配置表单与平台配置
|
|
||||||
*/
|
|
||||||
const AISystemConfig = {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: "API Key",
|
|
||||||
prop: "key",
|
|
||||||
type: "password"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "模型列表",
|
|
||||||
prop: "models",
|
|
||||||
type: "textarea",
|
|
||||||
maxlength: 1000,
|
|
||||||
showWordLimit: 0.9,
|
|
||||||
placeholder: "一行一个模型名称",
|
|
||||||
functions: "使用默认模型列表"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "默认模型",
|
|
||||||
prop: "model",
|
|
||||||
type: "model",
|
|
||||||
placeholder: "请选择默认模型",
|
|
||||||
tip: "可选数据来自模型列表"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Base URL",
|
|
||||||
prop: "base_url",
|
|
||||||
placeholder: "Enter base URL...",
|
|
||||||
tip: "API请求的基础URL路径,如果没有请留空"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "使用代理",
|
|
||||||
prop: "agency",
|
|
||||||
placeholder: '支持 http 或 socks 代理',
|
|
||||||
tip: "例如:http://proxy.com 或 socks5://proxy.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Temperature",
|
|
||||||
prop: "temperature",
|
|
||||||
placeholder: "模型温度,低则保守,高则多样",
|
|
||||||
tip: "例如:0.7,范围:0-1,默认:0.7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "默认提示词",
|
|
||||||
prop: "system",
|
|
||||||
type: "textarea",
|
|
||||||
maxlength: 20000,
|
|
||||||
showWordLimit: 0.9,
|
|
||||||
placeholder: "请输入默认提示词",
|
|
||||||
tip: "例如:你是一个人开发的AI助手"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
aiList: {
|
|
||||||
openai: {
|
|
||||||
extraFields: [
|
|
||||||
{
|
|
||||||
prop: "key",
|
|
||||||
placeholder: "OpenAI API Key",
|
|
||||||
link: "https://platform.openai.com/account/api-keys"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "models",
|
|
||||||
link: "https://platform.openai.com/docs/models",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
claude: {
|
|
||||||
extraFields: [
|
|
||||||
{
|
|
||||||
prop: "key",
|
|
||||||
placeholder: "Claude API Key",
|
|
||||||
link: "https://docs.anthropic.com/en/api/getting-started"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "models",
|
|
||||||
link: "https://docs.anthropic.com/en/docs/about-claude/models"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
deepseek: {
|
|
||||||
extraFields: [
|
|
||||||
{
|
|
||||||
prop: "key",
|
|
||||||
placeholder: "DeepSeek API Key",
|
|
||||||
link: "https://platform.deepseek.com/api_keys"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "models",
|
|
||||||
link: "https://api-docs.deepseek.com/zh-cn/quick_start/pricing"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
gemini: {
|
|
||||||
extraFields: [
|
|
||||||
{
|
|
||||||
prop: "key",
|
|
||||||
placeholder: "Gemini API Key",
|
|
||||||
link: "https://makersuite.google.com/app/apikey"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "models",
|
|
||||||
link: "https://ai.google.dev/models/gemini"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "agency",
|
|
||||||
placeholder: "仅支持 http 代理",
|
|
||||||
tip: "例如:http://proxy.com"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
grok: {
|
|
||||||
extraFields: [
|
|
||||||
{
|
|
||||||
prop: "key",
|
|
||||||
placeholder: "Grok API Key",
|
|
||||||
link: "https://docs.x.ai/docs/tutorial"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "models",
|
|
||||||
link: "https://docs.x.ai/docs/models"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
ollama: {
|
|
||||||
extraFields: [
|
|
||||||
{
|
|
||||||
prop: "key",
|
|
||||||
placeholder: "Ollama API Key",
|
|
||||||
tip: "如果没有请留空",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "models",
|
|
||||||
link: "https://ollama.com/models",
|
|
||||||
functions: "获取本地模型列表",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "base_url",
|
|
||||||
placeholder: "Enter base URL...",
|
|
||||||
tip: "API请求的URL路径",
|
|
||||||
sort: 1,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
zhipu: {
|
|
||||||
extraFields: [
|
|
||||||
{
|
|
||||||
prop: "key",
|
|
||||||
placeholder: "Zhipu API Key",
|
|
||||||
link: "https://bigmodel.cn/usercenter/apikeys"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "models",
|
|
||||||
link: "https://open.bigmodel.cn/dev/api"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
qianwen: {
|
|
||||||
extraFields: [
|
|
||||||
{
|
|
||||||
prop: "key",
|
|
||||||
placeholder: "Qianwen API Key",
|
|
||||||
link: "https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "models",
|
|
||||||
link: "https://help.aliyun.com/zh/model-studio/getting-started/models"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
wenxin: {
|
|
||||||
extraFields: [
|
|
||||||
{
|
|
||||||
prop: "key",
|
|
||||||
placeholder: "Wenxin API Key",
|
|
||||||
link: "https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application/v1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "secret",
|
|
||||||
placeholder: "Wenxin Secret Key",
|
|
||||||
link: "https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application/v1",
|
|
||||||
type: "password",
|
|
||||||
label: "Secret Key",
|
|
||||||
after: "key"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "models",
|
|
||||||
link: "https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Blfmc9dlf"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 即时消息生成系统提示词
|
* 即时消息生成系统提示词
|
||||||
*/
|
*/
|
||||||
@ -417,7 +222,6 @@ const AINormalizeJsonContent = (content) => {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
AIBotMap,
|
AIBotMap,
|
||||||
AISystemConfig,
|
|
||||||
MESSAGE_AI_SYSTEM_PROMPT,
|
MESSAGE_AI_SYSTEM_PROMPT,
|
||||||
TASK_AI_SYSTEM_PROMPT,
|
TASK_AI_SYSTEM_PROMPT,
|
||||||
PROJECT_AI_SYSTEM_PROMPT,
|
PROJECT_AI_SYSTEM_PROMPT,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user