perf: AI支持自定义模型列表

This commit is contained in:
kuaifan 2025-02-14 00:50:45 +08:00
parent 821df75d4b
commit 8ed9186ff4
5 changed files with 227 additions and 164 deletions

View File

@ -66,24 +66,33 @@ class Setting extends AbstractModel
} }
$array = []; $array = [];
$aiList = ['openai', 'claude', 'deepseek', 'gemini', 'zhipu', 'qianwen', 'wenxin']; $aiList = ['openai', 'claude', 'deepseek', 'gemini', 'zhipu', 'qianwen', 'wenxin'];
$fieldList = ['key', 'model', 'base_url', 'agency', 'temperature', 'system', 'secret']; $fieldList = ['key', 'models', 'model', 'base_url', 'agency', 'temperature', 'system', 'secret'];
foreach ($aiList as $aiName) { foreach ($aiList as $aiName) {
foreach ($fieldList as $fieldName) { foreach ($fieldList as $fieldName) {
$key = $aiName . '_' . $fieldName; $key = $aiName . '_' . $fieldName;
if ($fieldName == 'temperature' && $value[$key]) { $content = $value[$key] ? trim($value[$key]) : '';
$array[$key] = floatval(min(1, max(0, floatval($value[$key]) ?: 0.7))); switch ($fieldName) {
continue; case 'models':
if ($content) {
$content = explode("\n", $content);
$content = array_filter($content);
} }
$array[$key] = $value[$key] ?: match ($key) { if (empty($content)) {
'openai_model' => 'gpt-4o-mini', $content = self::AIDefaultModels($aiName);
'claude_model' => 'claude-3-5-sonnet-latest', }
'deepseek_model' => 'deepseek-chat', $content = implode("\n", $content);
'gemini_model' => 'gemini-1.5-flash', break;
'zhipu_model' => 'glm-4', case 'model':
'qianwen_model' => 'qwen-turbo', $models = Setting::AIModels2Array($array[$key . 's'], true);
'wenxin_model' => 'ernie-4.0-8k', $content = in_array($content, $models) ? $content : ($models[0] ?? '');
default => '', break;
}; case 'temperature':
if ($content) {
$content = floatval(min(1, max(0, floatval($content) ?: 0.7)));
}
break;
}
$array[$key] = $content;
} }
} }
$value = $array; $value = $array;
@ -103,6 +112,111 @@ class Setting extends AbstractModel
return !!$array[$ai . '_key']; return !!$array[$ai . '_key'];
} }
/**
* AI默认模型
* @param string $ai
* @return array
*/
public static function AIDefaultModels($ai = 'openai')
{
return match ($ai) {
'openai' => [
'gpt-4: GPT-4',
'gpt-4-turbo: GPT-4 Turbo',
'gpt-4o: GPT-4o',
'gpt-4o-mini: GPT-4o Mini',
'o1: GPT-o1',
'o1-mini: GPT-o1 Mini',
'o3-mini: GPT-o3 Mini',
'gpt-3.5-turbo: GPT-3.5 Turbo',
'gpt-3.5-turbo-16k: GPT-3.5 Turbo 16K',
'gpt-3.5-turbo-0125: GPT-3.5 Turbo 0125',
'gpt-3.5-turbo-1106: GPT-3.5 Turbo 1106'
],
'claude' => [
'claude-3-5-sonnet-latest: Claude 3.5 Sonnet',
'claude-3-5-sonnet-20241022: Claude 3.5 Sonnet 20241022',
'claude-3-5-haiku-latest: Claude 3.5 Haiku',
'claude-3-5-haiku-20241022: Claude 3.5 Haiku 20241022',
'claude-3-opus-latest: Claude 3 Opus',
'claude-3-opus-20240229: Claude 3 Opus 20240229',
'claude-3-haiku-20240307: Claude 3 Haiku 20240307',
'claude-2.1: Claude 2.1',
'claude-2.0: Claude 2.0'
],
'deepseek' => [
'deepseek-chat: DeepSeek V3',
'deepseek-reasoner: DeepSeek R1'
],
'wenxin' => [
'ernie-4.0-8k: Ernie 4.0 8K',
'ernie-4.0-8k-latest: Ernie 4.0 8K Latest',
'ernie-4.0-turbo-128k: Ernie 4.0 Turbo 128K',
'ernie-4.0-turbo-8k: Ernie 4.0 Turbo 8K',
'ernie-3.5-128k: Ernie 3.5 128K',
'ernie-3.5-8k: Ernie 3.5 8K',
'ernie-speed-128k: Ernie Speed 128K',
'ernie-speed-8k: Ernie Speed 8K',
'ernie-lite-8k: Ernie Lite 8K',
'ernie-tiny-8k: Ernie Tiny 8K'
],
'qianwen' => [
'qwen-max: QWEN Max',
'qwen-max-latest: QWEN Max Latest',
'qwen-turbo: QWEN Turbo',
'qwen-turbo-latest: QWEN Turbo Latest',
'qwen-plus: QWEN Plus',
'qwen-plus-latest: QWEN Plus Latest',
'qwen-long: QWEN Long'
],
'gemini' => [
'gemini-2.0-flash: Gemini 2.0 Flash',
'gemini-2.0-flash-lite-preview-02-05: Gemini 2.0 Flash-Lite Preview',
'gemini-1.5-flash: Gemini 1.5 Flash',
'gemini-1.5-flash-8b: Gemini 1.5 Flash 8B',
'gemini-1.5-pro: Gemini 1.5 Pro',
'gemini-1.0-pro: Gemini 1.0 Pro'
],
'zhipu' => [
'glm-4: GLM-4',
'glm-4-plus: GLM-4 Plus',
'glm-4-air: GLM-4 Air',
'glm-4-airx: GLM-4 AirX',
'glm-4-long: GLM-4 Long',
'glm-4-flash: GLM-4 Flash',
'glm-4v: GLM-4V',
'glm-4v-plus: GLM-4V Plus',
'glm-3-turbo: GLM-3 Turbo'
],
default => [],
};
}
/**
* AI模型转数组
* @param $models
* @param bool $retValue
* @return array
*/
public static function AIModels2Array($models, $retValue = false)
{
$list = is_array($models) ? $models : explode("\n", $models);
$array = [];
foreach ($list as $item) {
list($value, $label) = explode(':', $item . ':');
if ($value) {
$array[] = [
'value' => trim($value),
'label' => trim($label ?: $value)
];
}
}
if ($retValue) {
return array_column($array, 'value');
}
return $array;
}
/** /**
* 验证邮箱地址(过滤忽略地址) * 验证邮箱地址(过滤忽略地址)
* @param $array * @param $array

View File

@ -196,20 +196,23 @@ class UserBot extends AbstractModel
]; ];
} }
$aibotSetting = Base::setting('aibotSetting'); $aibotSetting = Base::setting('aibotSetting');
$aibotModel = $aibotSetting[$match[1] . '_model'];
$aibotModels = Setting::AIModels2Array($aibotSetting[$match[1] . '_models']);
if (empty($aibotModels)) {
return [];
}
return [ return [
[ [
'key' => '~ai-model-select', 'key' => '~ai-model-select',
'label' => Doo::translate('选择模型'), 'label' => Doo::translate('选择模型'),
'config' => [ 'config' => [
'model' => $aibotSetting[$match[1] . '_model'] 'model' => $aibotModel,
'models' => $aibotModels
] ]
], ],
[ [
'key' => '~ai-session-create', 'key' => '~ai-session-create',
'label' => Doo::translate('开启新会话'), 'label' => Doo::translate('开启新会话'),
'config' => [
'model' => $aibotSetting[$match[1] . '_model']
]
], ],
[ [
'key' => '~ai-session-history', 'key' => '~ai-session-history',

View File

@ -720,7 +720,6 @@ import touchclick from "../../../directives/touchclick";
import {languageList} from "../../../language"; import {languageList} from "../../../language";
import {isLocalResourcePath} from "../../../components/Replace/utils"; import {isLocalResourcePath} from "../../../components/Replace/utils";
import emitter from "../../../store/events"; import emitter from "../../../store/events";
import {AIModelLabel, AIModelList} from "../../../store/utils";
export default { export default {
name: "DialogWrapper", name: "DialogWrapper",
@ -1848,9 +1847,16 @@ export default {
if (key === '~ai-model-select') { if (key === '~ai-model-select') {
const model = this.aiModelValue() const model = this.aiModelValue()
if (model) { if (model) {
label = AIModelLabel(this.dialogData.email, model) label = model
} else if (config?.model) { } else if (config?.model) {
label = AIModelLabel(this.dialogData.email, config.model) label = config.model
}
if (config?.models) {
config.models.forEach(({value, label: text}) => {
if (value === label) {
label = text
}
})
} }
} }
return label return label
@ -1918,14 +1924,10 @@ export default {
if (!this.isAiBot) { if (!this.isAiBot) {
return return
} }
const list = AIModelList(this.dialogData.email) const models = item.config?.models
const configModel = item.config?.model
if (configModel && !list.find(({value}) => value === configModel)) {
list.unshift({label: configModel, value: configModel})
}
this.$store.state.menuOperation = { this.$store.state.menuOperation = {
event, event,
list, list: $A.isArray(models) ? models : [],
scrollHide: true, scrollHide: true,
onUpdate: async model => { onUpdate: async model => {
this.dialogAiModel = [ this.dialogAiModel = [

View File

@ -19,20 +19,15 @@
:placeholder="$L(field.placeholder)"/> :placeholder="$L(field.placeholder)"/>
</template> </template>
<template v-else-if="field.type === 'model'"> <template v-else-if="field.type === 'model'">
<Select <Select v-model="formData[field.prop]" transfer>
v-model="formData[field.prop]" <Option v-for="item in modelOption(field.prop)" :value="item.value" :key="item.value">{{ item.label }}</Option>
@on-create="modelCreate($event, field.options)"
filterable
allow-create
transfer>
<Option v-for="item in modelOption(formData[field.prop], field.options)" :value="item.value" :key="item.value">{{ item.label }}</Option>
</Select> </Select>
</template> </template>
<template v-else-if="field.type === 'textarea'"> <template v-else-if="field.type === 'textarea'">
<Input <Input
:maxlength="500" :maxlength="500"
type="textarea" type="textarea"
:autosize="{minRows:2,maxRows:5}" :autosize="{minRows:2,maxRows:6}"
v-model="formData[field.prop]" v-model="formData[field.prop]"
:placeholder="$L(field.placeholder)"/> :placeholder="$L(field.placeholder)"/>
</template> </template>
@ -64,7 +59,6 @@
<script> <script>
import {mapState} from "vuex"; import {mapState} from "vuex";
import {AIModelList} from "../../../../store/utils";
export default { export default {
name: "SystemAibot", name: "SystemAibot",
@ -89,14 +83,20 @@ export default {
tipPrefix: '访问OpenAI网站查看', tipPrefix: '访问OpenAI网站查看',
link: 'https://platform.openai.com/account/api-keys' link: 'https://platform.openai.com/account/api-keys'
}, },
{
label: '模型列表',
prop: 'openai_models',
type: 'textarea',
placeholder: '一行一个模型名称',
tipPrefix: '查看说明',
link: 'https://platform.openai.com/docs/models'
},
{ {
label: '默认模型', label: '默认模型',
prop: 'openai_model', prop: 'openai_model',
type: 'model', type: 'model',
options: AIModelList('openai'), placeholder: '请选择默认模型',
placeholder: '请输入模型名称', tip: '可选数据来自模型列表',
tipPrefix: '查看说明',
link: 'https://platform.openai.com/docs/models'
}, },
{ {
label: 'Base URL', label: 'Base URL',
@ -134,14 +134,20 @@ export default {
placeholder: 'Claude API Key', placeholder: 'Claude API Key',
link: 'https://docs.anthropic.com/en/api/getting-started' link: 'https://docs.anthropic.com/en/api/getting-started'
}, },
{
label: '模型列表',
prop: 'claude_models',
type: 'textarea',
placeholder: '一行一个模型名称',
tipPrefix: '查看说明',
link: 'https://docs.anthropic.com/en/docs/about-claude/models'
},
{ {
label: '默认模型', label: '默认模型',
prop: 'claude_model', prop: 'claude_model',
type: 'model', type: 'model',
options: AIModelList('claude'), placeholder: '请选择默认模型',
placeholder: '请输入模型名称', tip: '可选数据来自模型列表',
tipPrefix: '查看说明',
link: 'https://docs.anthropic.com/en/docs/about-claude/models'
}, },
{ {
label: '使用代理', label: '使用代理',
@ -174,14 +180,20 @@ export default {
tipPrefix: '访问DeepSeek网站查看', tipPrefix: '访问DeepSeek网站查看',
link: 'https://platform.deepseek.com/api_keys' link: 'https://platform.deepseek.com/api_keys'
}, },
{
label: '模型列表',
prop: 'deepseek_models',
type: 'textarea',
placeholder: '一行一个模型名称',
tipPrefix: '查看说明',
link: 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing'
},
{ {
label: '默认模型', label: '默认模型',
prop: 'deepseek_model', prop: 'deepseek_model',
type: 'model', type: 'model',
options: AIModelList('deepseek'), placeholder: '请选择默认模型',
placeholder: '请输入模型名称', tip: '可选数据来自模型列表',
tipPrefix: '查看说明',
link: 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing'
}, },
{ {
label: 'Base URL', label: 'Base URL',
@ -220,14 +232,20 @@ export default {
placeholder: 'Gemini API Key', placeholder: 'Gemini API Key',
link: 'https://makersuite.google.com/app/apikey' link: 'https://makersuite.google.com/app/apikey'
}, },
{
label: '模型列表',
prop: 'gemini_models',
type: 'textarea',
placeholder: '一行一个模型名称',
tipPrefix: '查看说明',
link: 'https://ai.google.dev/models/gemini'
},
{ {
label: '默认模型', label: '默认模型',
prop: 'gemini_model', prop: 'gemini_model',
type: 'model', type: 'model',
options: AIModelList('gemini'), placeholder: '请选择默认模型',
placeholder: '请输入模型名称', tip: '可选数据来自模型列表',
tipPrefix: '查看说明',
link: 'https://ai.google.dev/models/gemini'
}, },
{ {
label: '使用代理', label: '使用代理',
@ -259,14 +277,20 @@ export default {
placeholder: 'Zhipu API Key', placeholder: 'Zhipu API Key',
link: 'https://bigmodel.cn/usercenter/apikeys' link: 'https://bigmodel.cn/usercenter/apikeys'
}, },
{
label: '模型列表',
prop: 'zhipu_models',
type: 'textarea',
placeholder: '一行一个模型名称',
tipPrefix: '查看说明',
link: 'https://open.bigmodel.cn/dev/api'
},
{ {
label: '默认模型', label: '默认模型',
prop: 'zhipu_model', prop: 'zhipu_model',
type: 'model', type: 'model',
options: AIModelList('zhipu'), placeholder: '请选择默认模型',
placeholder: '请输入模型名称', tip: '可选数据来自模型列表',
tipPrefix: '查看说明',
link: 'https://open.bigmodel.cn/dev/api'
}, },
{ {
label: '使用代理', label: '使用代理',
@ -298,14 +322,20 @@ export default {
placeholder: 'Qianwen API Key', placeholder: 'Qianwen API Key',
link: 'https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key' link: 'https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key'
}, },
{
label: '模型列表',
prop: 'qianwen_models',
type: 'textarea',
placeholder: '一行一个模型名称',
tipPrefix: '查看说明',
link: 'https://help.aliyun.com/zh/model-studio/getting-started/models'
},
{ {
label: '默认模型', label: '默认模型',
prop: 'qianwen_model', prop: 'qianwen_model',
type: 'model', type: 'model',
options: AIModelList('qianwen'), placeholder: '请选择默认模型',
placeholder: '请输入模型名称', tip: '可选数据来自模型列表',
tipPrefix: '查看说明',
link: 'https://help.aliyun.com/zh/model-studio/getting-started/models'
}, },
{ {
label: '使用代理', label: '使用代理',
@ -344,14 +374,20 @@ export default {
placeholder: 'Wenxin Secret Key', placeholder: 'Wenxin Secret Key',
link: 'https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application/v1' link: 'https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application/v1'
}, },
{
label: '模型列表',
prop: 'wenxin_models',
type: 'textarea',
placeholder: '一行一个模型名称',
tipPrefix: '查看说明',
link: 'https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Blfmc9dlf'
},
{ {
label: '默认模型', label: '默认模型',
prop: 'wenxin_model', prop: 'wenxin_model',
type: 'model', type: 'model',
options: AIModelList('wenxin'), placeholder: '请选择默认模型',
placeholder: '请输入模型名称', tip: '可选数据来自模型列表',
tipPrefix: '查看说明',
link: 'https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Blfmc9dlf'
}, },
{ {
label: '使用代理', label: '使用代理',
@ -384,14 +420,15 @@ export default {
...mapState(['formOptions']), ...mapState(['formOptions']),
}, },
methods: { methods: {
modelCreate(value, options) { modelOption(prop) {
options.push({value, label: value}); const value = this.formData[prop + 's'];
}, if (value) {
modelOption(value, options) { return value.split('\n').map(item => {
if (value && !options.find(item => item.value === value)) { const [value, label] = `${item}:`.split(':');
options.unshift({value, label: value}); return {value, label: label || value};
}, []).filter(item => item.value);
} }
return options; return []
}, },
submitForm() { submitForm() {
this.$refs.formData.validate((valid) => { this.$refs.formData.validate((valid) => {

View File

@ -162,96 +162,3 @@ export class SSEClient {
} }
} }
} }
const __AIModelData = {
openai: [
{value: 'gpt-4', label: 'GPT-4'},
{value: 'gpt-4-turbo', label: 'GPT-4 Turbo'},
{value: 'gpt-4o', label: 'GPT-4o'},
{value: 'gpt-4o-mini', label: 'GPT-4o Mini'},
{value: 'o1', label: 'GPT-o1'},
{value: 'o1-mini', label: 'GPT-o1 Mini'},
{value: 'o3-mini', label: 'GPT-o3 Mini'},
{value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo'},
{value: 'gpt-3.5-turbo-16k', label: 'GPT-3.5 Turbo 16K'},
{value: 'gpt-3.5-turbo-0125', label: 'GPT-3.5 Turbo 0125'},
{value: 'gpt-3.5-turbo-1106', label: 'GPT-3.5 Turbo 1106'}
],
claude: [
{value: 'claude-3-5-sonnet-latest', label: 'Claude 3.5 Sonnet'},
{value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet 20241022'},
{value: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku'},
{value: 'claude-3-5-haiku-20241022', label: 'Claude 3.5 Haiku 20241022'},
{value: 'claude-3-opus-latest', label: 'Claude 3 Opus'},
{value: 'claude-3-opus-20240229', label: 'Claude 3 Opus 20240229'},
{value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku 20240307'},
{value: 'claude-2.1', label: 'Claude 2.1'},
{value: 'claude-2.0', label: 'Claude 2.0'}
],
deepseek: [
{value: 'deepseek-chat', label: 'DeepSeek V3'},
{value: 'deepseek-reasoner', label: 'DeepSeek R1'}
],
wenxin: [
{value: 'ernie-4.0-8k', label: 'Ernie 4.0 8K'},
{value: 'ernie-4.0-8k-latest', label: 'Ernie 4.0 8K Latest'},
{value: 'ernie-4.0-turbo-128k', label: 'Ernie 4.0 Turbo 128K'},
{value: 'ernie-4.0-turbo-8k', label: 'Ernie 4.0 Turbo 8K'},
{value: 'ernie-3.5-128k', label: 'Ernie 3.5 128K'},
{value: 'ernie-3.5-8k', label: 'Ernie 3.5 8K'},
{value: 'ernie-speed-128k', label: 'Ernie Speed 128K'},
{value: 'ernie-speed-8k', label: 'Ernie Speed 8K'},
{value: 'ernie-lite-8k', label: 'Ernie Lite 8K'},
{value: 'ernie-tiny-8k', label: 'Ernie Tiny 8K'}
],
qianwen: [
{value: 'qwen-max', label: 'QWEN Max'},
{value: 'qwen-max-latest', label: 'QWEN Max Latest'},
{value: 'qwen-turbo', label: 'QWEN Turbo'},
{value: 'qwen-turbo-latest', label: 'QWEN Turbo Latest'},
{value: 'qwen-plus', label: 'QWEN Plus'},
{value: 'qwen-plus-latest', label: 'QWEN Plus Latest'},
{value: 'qwen-long', label: 'QWEN Long'}
],
gemini: [
{value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash'},
{value: 'gemini-2.0-flash-lite-preview-02-05', label: 'Gemini 2.0 Flash-Lite Preview'},
{value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash'},
{value: 'gemini-1.5-flash-8b', label: 'Gemini 1.5 Flash 8B'},
{value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro'},
{value: 'gemini-1.0-pro', label: 'Gemini 1.0 Pro'}
],
zhipu: [
{value: 'glm-4', label: 'GLM-4'},
{value: 'glm-4-plus', label: 'GLM-4 Plus'},
{value: 'glm-4-air', label: 'GLM-4 Air'},
{value: 'glm-4-airx', label: 'GLM-4 AirX'},
{value: 'glm-4-long', label: 'GLM-4 Long'},
{value: 'glm-4-flash', label: 'GLM-4 Flash'},
{value: 'glm-4v', label: 'GLM-4V'},
{value: 'glm-4v-plus', label: 'GLM-4V Plus'},
{value: 'glm-3-turbo', label: 'GLM-3 Turbo'}
]
}
const AIModelList = (email) => {
const emailMap = {
"ai-openai@bot.system": "openai",
"ai-claude@bot.system": "claude",
"ai-deepseek@bot.system": "deepseek",
"ai-wenxin@bot.system": "wenxin",
"ai-qianwen@bot.system": "qianwen",
"ai-gemini@bot.system": "gemini",
"ai-zhipu@bot.system": "zhipu"
};
email = emailMap[email] || email;
return __AIModelData[email] || []
}
const AIModelLabel = (email, model) => {
const item = AIModelList(email).find(item => item.value === model)
return item ? item.label : model
}
export {AIModelList, AIModelLabel}