mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-06 21:14:09 +08:00
重构AI配置组件以支持多个接口模型
This commit is contained in:
parent
2217685072
commit
1be095fec2
@ -1,47 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="q-my-md">
|
||||
<BorderLabel label="API配置">
|
||||
<ButtonGroup
|
||||
:model-value="argvs.apiConfig.modelType"
|
||||
@update:modelValue="updateArgvs('apiConfig.modelType', $event)"
|
||||
:options="modelTypeOptions"
|
||||
height="26px"
|
||||
class="q-mb-sm"
|
||||
/>
|
||||
<VariableInput
|
||||
:model-value="argvs.apiConfig.apiUrl"
|
||||
label="API地址"
|
||||
:placeholder="
|
||||
argvs.apiConfig.modelType === 'openai'
|
||||
? '例:https://api.openai.com/v1'
|
||||
: '例:http://localhost:11434'
|
||||
"
|
||||
@update:modelValue="updateArgvs('apiConfig.apiUrl', $event)"
|
||||
class="q-mb-sm"
|
||||
/>
|
||||
<div class="row q-gutter-sm">
|
||||
<VariableInput
|
||||
class="col"
|
||||
v-if="argvs.apiConfig.modelType === 'openai'"
|
||||
:model-value="argvs.apiConfig.apiToken"
|
||||
@update:modelValue="updateArgvs('apiConfig.apiToken', $event)"
|
||||
label="API密钥"
|
||||
/>
|
||||
<VariableInput
|
||||
class="col"
|
||||
:model-value="argvs.apiConfig.model"
|
||||
@update:modelValue="updateArgvs('apiConfig.model', $event)"
|
||||
label="模型"
|
||||
:placeholder="
|
||||
argvs.apiConfig.modelType === 'openai'
|
||||
? '例:gpt-4o'
|
||||
: '例:qwen2.5:32b'
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-if="apiOptions.length > 0"
|
||||
:model-value="argvs.apiConfig"
|
||||
@update:model-value="updateArgvs('apiConfig', $event)"
|
||||
:options="apiOptions"
|
||||
map-options
|
||||
emit-value
|
||||
dense
|
||||
options-dense
|
||||
filled
|
||||
label="API模型"
|
||||
class="q-mb-sm"
|
||||
/>
|
||||
<q-field filled dense v-else class="q-mb-sm">
|
||||
<template #control>
|
||||
<div class="flex items-center justify-center full-width text-warning">
|
||||
<q-icon name="warning" class="q-mr-sm" />
|
||||
<div>
|
||||
未配置API模型,配置方法:命令配置界面-右下角菜单按钮-API配置
|
||||
</div>
|
||||
</div>
|
||||
</BorderLabel>
|
||||
</div>
|
||||
</template>
|
||||
</q-field>
|
||||
<ButtonGroup
|
||||
:model-value="argvs.content.presetPrompt"
|
||||
@update:modelValue="updateArgvs('content.presetPrompt', $event)"
|
||||
@ -61,11 +42,11 @@
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import BorderLabel from "components/composer/common/BorderLabel.vue";
|
||||
import ButtonGroup from "components/composer/common/ButtonGroup.vue";
|
||||
import { newVarInputVal } from "js/composer/varInputValManager";
|
||||
import VariableInput from "components/composer/common/VariableInput.vue";
|
||||
import { parseFunction, stringifyArgv } from "js/composer/formatString";
|
||||
import { dbManager } from "js/utools.js";
|
||||
|
||||
export default defineComponent({
|
||||
name: "AskAIEditor",
|
||||
@ -74,7 +55,6 @@ export default defineComponent({
|
||||
},
|
||||
components: {
|
||||
VariableInput,
|
||||
BorderLabel,
|
||||
ButtonGroup,
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
@ -87,6 +67,7 @@ export default defineComponent({
|
||||
},
|
||||
apiConfig: {},
|
||||
},
|
||||
apiOptions: [],
|
||||
presetPromptOptions: [
|
||||
{ label: "自由问答", value: "" },
|
||||
{ label: "翻译", value: "translate" },
|
||||
@ -111,17 +92,9 @@ export default defineComponent({
|
||||
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
|
||||
if (!code) return argvs;
|
||||
try {
|
||||
const variableFormatPaths = [
|
||||
"arg0.prompt",
|
||||
"arg1.apiUrl",
|
||||
"arg1.apiToken",
|
||||
"arg1.model",
|
||||
];
|
||||
const variableFormatPaths = ["arg0.prompt"];
|
||||
const params = parseFunction(code, { variableFormatPaths });
|
||||
return {
|
||||
content: params.argvs[0],
|
||||
apiConfig: params.argvs[1],
|
||||
};
|
||||
return params;
|
||||
} catch (e) {
|
||||
console.error("解析参数失败:", e);
|
||||
}
|
||||
@ -130,7 +103,7 @@ export default defineComponent({
|
||||
generateCode(argvs = this.argvs) {
|
||||
return `${this.modelValue.value}(${stringifyArgv(
|
||||
argvs.content
|
||||
)}, ${stringifyArgv(argvs.apiConfig)})`;
|
||||
)}, ${JSON.stringify(argvs.apiConfig)})`;
|
||||
},
|
||||
getSummary(argvs) {
|
||||
return "问AI:" + argvs.content.prompt;
|
||||
@ -153,14 +126,16 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const aiConfig = this.$root.profile.aiConfig || {};
|
||||
console.log("aiConfig", aiConfig);
|
||||
this.defaultArgvs.apiConfig = {
|
||||
modelType: aiConfig.modelType || "openai",
|
||||
apiUrl: newVarInputVal("str", aiConfig.apiUrl || ""),
|
||||
apiToken: newVarInputVal("str", aiConfig.apiToken || ""),
|
||||
model: newVarInputVal("str", aiConfig.model || ""),
|
||||
};
|
||||
const apiConfigs = dbManager.getStorage("cfg_aiConfigs");
|
||||
this.apiOptions = apiConfigs
|
||||
? apiConfigs.map((config) => {
|
||||
return {
|
||||
label: config.name,
|
||||
value: config,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
this.defaultArgvs.apiConfig = apiConfigs?.[0] || {};
|
||||
|
||||
const argvs = this.modelValue.argvs || this.defaultArgvs;
|
||||
if (!this.modelValue.code) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<q-expansion-item
|
||||
v-model="isExpanded"
|
||||
@update:model-value="$emit('update:is-expanded', $event)"
|
||||
class="command-composer command-config"
|
||||
class="command-config"
|
||||
expand-icon-toggle
|
||||
>
|
||||
<template v-slot:header>
|
||||
|
@ -1,54 +1,122 @@
|
||||
<template>
|
||||
<q-card style="width: 600px" class="q-pa-md">
|
||||
<q-card-section class="text-h5"> API配置 </q-card-section>
|
||||
<q-card-section>
|
||||
<ButtonGroup
|
||||
v-model="modelType"
|
||||
:options="[
|
||||
{ label: 'OPENAI', value: 'openai' },
|
||||
{ label: 'OLLAMA', value: 'ollama' },
|
||||
]"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-gutter-sm column">
|
||||
<q-input outlined dense v-model="apiUrl">
|
||||
<template v-slot:prepend>
|
||||
<q-badge
|
||||
color="primary"
|
||||
text-color="black"
|
||||
label="API地址"
|
||||
class="q-pa-xs"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input outlined dense v-model="apiToken" v-if="modelType === 'openai'">
|
||||
<template v-slot:prepend>
|
||||
<q-badge
|
||||
color="primary"
|
||||
text-color="black"
|
||||
label="API令牌"
|
||||
class="q-pa-xs"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-select
|
||||
outlined
|
||||
dense
|
||||
v-model="model"
|
||||
:options="models"
|
||||
@focus="getModels"
|
||||
<q-card style="width: 800px" class="q-pa-sm">
|
||||
<div class="text-h5 q-my-md q-px-sm">API配置</div>
|
||||
<div>
|
||||
<div class="flex q-mb-md q-px-sm" style="height: 26px">
|
||||
<ButtonGroup
|
||||
v-model="modelToAdd"
|
||||
class="col"
|
||||
:options="[
|
||||
{ label: 'OPENAI', value: 'openai' },
|
||||
{ label: 'OLLAMA', value: 'ollama' },
|
||||
]"
|
||||
height="26px"
|
||||
/>
|
||||
<q-icon
|
||||
name="add_box"
|
||||
@click="addModel"
|
||||
color="primary"
|
||||
size="26px"
|
||||
class="cursor-pointer q-ml-sm"
|
||||
/>
|
||||
</div>
|
||||
<q-scroll-area
|
||||
:style="`height: ${getConfigListHeight()}px;`"
|
||||
class="q-px-sm"
|
||||
:vertical-thumb-style="{
|
||||
width: '2px',
|
||||
}"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-badge
|
||||
color="primary"
|
||||
text-color="black"
|
||||
label="模型名称"
|
||||
class="q-pa-xs"
|
||||
/>
|
||||
</template>
|
||||
</q-select>
|
||||
</q-card-section>
|
||||
<q-card-section class="flex justify-end q-gutter-sm">
|
||||
<div class="config-list">
|
||||
<div
|
||||
v-for="(aiConfig, index) in aiConfigs"
|
||||
:key="index"
|
||||
class="config-item"
|
||||
>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model="aiConfig.name"
|
||||
class="col"
|
||||
placeholder="请输入名称"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-badge
|
||||
color="primary"
|
||||
text-color="black"
|
||||
label="名称"
|
||||
class="q-pa-xs"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
color="grey"
|
||||
name="remove_circle"
|
||||
@click="deleteModel(index)"
|
||||
size="16px"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model="aiConfig.apiUrl"
|
||||
class="col-8"
|
||||
:placeholder="`${aiConfig.modelType} API地址`"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-badge
|
||||
color="primary"
|
||||
text-color="black"
|
||||
label="接口"
|
||||
class="q-pa-xs"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="row q-col-gutter-sm">
|
||||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="aiConfig.model"
|
||||
:options="models"
|
||||
@focus="getModels(aiConfig)"
|
||||
class="col"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-badge
|
||||
color="primary"
|
||||
text-color="black"
|
||||
label="模型"
|
||||
class="q-pa-xs"
|
||||
/>
|
||||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model="aiConfig.apiToken"
|
||||
v-if="aiConfig.modelType === 'openai'"
|
||||
type="password"
|
||||
class="col-8"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-badge
|
||||
color="primary"
|
||||
text-color="black"
|
||||
label="令牌"
|
||||
class="q-pa-xs"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
</div>
|
||||
<div class="flex justify-end q-gutter-sm q-px-sm">
|
||||
<q-btn flat color="grey" label="取消" v-close-popup />
|
||||
<q-btn
|
||||
flat
|
||||
@ -57,12 +125,13 @@
|
||||
v-close-popup
|
||||
@click="saveConfig"
|
||||
/>
|
||||
</q-card-section>
|
||||
</div>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { dbManager } from "js/utools.js";
|
||||
import ButtonGroup from "components/composer/common/ButtonGroup.vue";
|
||||
|
||||
export default defineComponent({
|
||||
@ -72,42 +141,62 @@ export default defineComponent({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modelType: "openai",
|
||||
apiUrl: "",
|
||||
apiToken: "",
|
||||
model: "",
|
||||
modelToAdd: "openai",
|
||||
aiConfigs: [],
|
||||
models: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async getModels() {
|
||||
try {
|
||||
const { success, result } = await window.getModelsFromAiApi({
|
||||
modelType: this.modelType,
|
||||
apiUrl: this.apiUrl,
|
||||
apiToken: this.apiToken,
|
||||
});
|
||||
this.models = success ? result : [];
|
||||
} catch (_) {
|
||||
this.models = [];
|
||||
async getModels(aiConfig) {
|
||||
const { success, result, error } = await window.getModelsFromAiApi(
|
||||
aiConfig
|
||||
);
|
||||
if (!success) {
|
||||
quickcommand.showMessageBox(error, "error");
|
||||
return;
|
||||
}
|
||||
this.models = result;
|
||||
},
|
||||
saveConfig() {
|
||||
this.$root.profile.aiConfig = {
|
||||
modelType: this.modelType,
|
||||
apiUrl: this.apiUrl,
|
||||
apiToken: this.apiToken,
|
||||
model: this.model,
|
||||
};
|
||||
console.log("saveConfig", this.$root.profile.aiConfig);
|
||||
dbManager.setStorage(
|
||||
"cfg_aiConfigs",
|
||||
window.lodashM.cloneDeep(this.aiConfigs)
|
||||
);
|
||||
},
|
||||
deleteModel(index) {
|
||||
this.aiConfigs.splice(index, 1);
|
||||
},
|
||||
addModel() {
|
||||
this.aiConfigs.push({
|
||||
modelType: this.modelToAdd,
|
||||
apiUrl: "",
|
||||
apiToken: "",
|
||||
model: "",
|
||||
name: "",
|
||||
});
|
||||
},
|
||||
getConfigListHeight() {
|
||||
const counts = Math.min(this.aiConfigs.length, 3);
|
||||
return counts * 100 + (counts - 1) * 8;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const aiConfig = this.$root.profile.aiConfig || {};
|
||||
this.modelType = aiConfig.modelType || "openai";
|
||||
this.apiUrl = aiConfig.apiUrl || "";
|
||||
this.apiToken = aiConfig.apiToken || "";
|
||||
this.model = aiConfig.model || "";
|
||||
this.aiConfigs = dbManager.getStorage("cfg_aiConfigs") || [];
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.config-list,
|
||||
.config-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.config-item {
|
||||
border: 1px solid var(--q-primary);
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
|
@ -368,3 +368,71 @@ body.body--dark.glass-effect-menu .q-menu {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* q-field--filled 布局更加紧凑 */
|
||||
/* 输入框高度及字体 */
|
||||
.q-field--filled:not(.q-textarea) .q-field__control,
|
||||
.q-field--filled:not(.q-textarea) .q-field__control>*,
|
||||
.q-field--filled:not(.q-field--labeled):not(.q-textarea) .q-field__native {
|
||||
max-height: 36px !important;
|
||||
min-height: 36px !important;
|
||||
}
|
||||
|
||||
.q-field--filled .q-field__control,
|
||||
.q-field--filled .q-field__control>*,
|
||||
.q-field--filled .q-field__native {
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.q-field--filled.q-select--with-chips .q-field__control .q-chip {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
/* 输入框图标大小 */
|
||||
.q-field--filled .q-field__control .q-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 输入框标签字体大小,占位时的位置 */
|
||||
.q-field--filled .q-field__label {
|
||||
font-size: 11px;
|
||||
top: 11px;
|
||||
}
|
||||
|
||||
/* 输入框标签悬浮的位置 */
|
||||
.q-field--filled.q-field--dense.q-field--float .q-field__label {
|
||||
transform: translateY(-50%) scale(0.75);
|
||||
}
|
||||
|
||||
/* 去除filled输入框边框 */
|
||||
.q-field--filled .q-field__control:before {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 去除filled输入框下划线 */
|
||||
.q-field--filled .q-field__control:after {
|
||||
height: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 输入框背景颜色及内边距 */
|
||||
.q-field--filled .q-field__control {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
/* 输入框聚焦时的背景颜色 */
|
||||
.q-field--filled.q-field--highlighted .q-field__control {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
/* 暗黑模式下的输入框背景颜色 */
|
||||
.body--dark .q-field--filled .q-field__control {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
/* 暗黑模式下输入框聚焦时的背景颜色 */
|
||||
.body--dark .q-field--filled.q-field--highlighted .q-field__control {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
@ -9,74 +9,6 @@
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 布局更加紧凑 */
|
||||
/* 输入框高度及字体 */
|
||||
.command-composer .q-field--filled:not(.q-textarea) .q-field__control,
|
||||
.command-composer .q-field--filled:not(.q-textarea) .q-field__control>*,
|
||||
.command-composer .q-field--filled:not(.q-field--labeled):not(.q-textarea) .q-field__native {
|
||||
max-height: 36px !important;
|
||||
min-height: 36px !important;
|
||||
}
|
||||
|
||||
.command-composer .q-field--filled .q-field__control,
|
||||
.command-composer .q-field--filled .q-field__control>*,
|
||||
.command-composer .q-field--filled .q-field__native {
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.command-composer .q-field--filled.q-select--with-chips .q-field__control .q-chip {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
/* 输入框图标大小 */
|
||||
.command-composer .q-field--filled .q-field__control .q-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 输入框标签字体大小,占位时的位置 */
|
||||
.command-composer .q-field--filled .q-field__label {
|
||||
font-size: 11px;
|
||||
top: 11px;
|
||||
}
|
||||
|
||||
/* 输入框标签悬浮的位置 */
|
||||
.command-composer .q-field--filled.q-field--dense.q-field--float .q-field__label {
|
||||
transform: translateY(-50%) scale(0.75);
|
||||
}
|
||||
|
||||
/* 去除filled输入框边框 */
|
||||
.command-composer .q-field--filled .q-field__control:before {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 去除filled输入框下划线 */
|
||||
.command-composer .q-field--filled .q-field__control:after {
|
||||
height: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 输入框背景颜色及内边距 */
|
||||
.command-composer .q-field--filled .q-field__control {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
/* 输入框聚焦时的背景颜色 */
|
||||
.command-composer .q-field--filled.q-field--highlighted .q-field__control {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
/* 暗黑模式下的输入框背景颜色 */
|
||||
.body--dark .command-composer .q-field--filled .q-field__control {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
/* 暗黑模式下输入框聚焦时的背景颜色 */
|
||||
.body--dark .command-composer .q-field--filled.q-field--highlighted .q-field__control {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
/* checkbox/toggle大小及字体 */
|
||||
.command-composer .q-checkbox__label,
|
||||
.command-composer .q-toggle__label {
|
||||
|
Loading…
x
Reference in New Issue
Block a user