将ai接口移至quickcommand,支持在配置菜单全局配置ai接口

This commit is contained in:
fofolee
2025-02-18 00:59:28 +08:00
parent 3eca3b448e
commit 9e00a08253
13 changed files with 393 additions and 257 deletions

View File

@@ -0,0 +1,179 @@
<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'
"
/>
</div>
</BorderLabel>
</div>
<ButtonGroup
:model-value="argvs.content.presetPrompt"
@update:modelValue="updateArgvs('content.presetPrompt', $event)"
:options="presetPromptOptions"
height="26px"
class="q-mb-sm"
/>
<VariableInput
:model-value="argvs.content.prompt"
@update:modelValue="updateArgvs('content.prompt', $event)"
label="提示词"
type="textarea"
autogrow
/>
</div>
</template>
<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";
export default defineComponent({
name: "AskAIEditor",
props: {
modelValue: Object,
},
components: {
VariableInput,
BorderLabel,
ButtonGroup,
},
emits: ["update:modelValue"],
data() {
return {
defaultArgvs: {
content: {
prompt: newVarInputVal("str"),
presetPrompt: "",
},
apiConfig: {},
},
presetPromptOptions: [
{ label: "自由问答", value: "" },
{ label: "翻译", value: "translate" },
{ label: "总结", value: "summarize" },
{ label: "执行shell命令", value: "shell" },
],
modelTypeOptions: [
{ label: "OpenAI", value: "openai" },
{ label: "Ollama", value: "ollama" },
],
};
},
computed: {
argvs() {
return (
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
);
},
},
methods: {
parseCodeToArgvs(code) {
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
if (!code) return argvs;
try {
const variableFormatPaths = [
"arg0.prompt",
"arg1.apiUrl",
"arg1.apiToken",
"arg1.model",
];
const params = parseFunction(code, { variableFormatPaths });
return {
content: params.argvs[0],
apiConfig: params.argvs[1],
};
} catch (e) {
console.error("解析参数失败:", e);
}
return argvs;
},
generateCode(argvs = this.argvs) {
return `${this.modelValue.value}(${stringifyArgv(
argvs.content
)}, ${stringifyArgv(argvs.apiConfig)})`;
},
getSummary(argvs) {
return "问AI" + argvs.content.prompt;
},
updateArgvs(keyPath, newValue) {
const newArgvs = { ...this.argvs };
const keys = keyPath.split(".");
const lastKey = keys.pop();
const target = keys.reduce((obj, key) => obj[key], newArgvs);
target[lastKey] = newValue;
this.updateModelValue(newArgvs);
},
updateModelValue(argvs) {
this.$emit("update:modelValue", {
...this.modelValue,
summary: this.getSummary(argvs),
argvs,
code: this.generateCode(argvs),
});
},
},
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 argvs = this.modelValue.argvs || this.defaultArgvs;
if (!this.modelValue.code) {
this.updateModelValue(argvs);
}
},
});
</script>
<style scoped>
.return-label {
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@@ -51,6 +51,14 @@
<PersonalizeMenu />
</q-item>
<!-- AI配置 -->
<q-item clickable v-close-popup @click="showAIConfig = true">
<q-item-section side>
<q-icon name="keyboard_arrow_left" />
</q-item-section>
<q-item-section>AI配置</q-item-section>
</q-item>
<!-- 收藏 -->
<q-item
v-if="activatedQuickPanels.includes(currentTag)"
@@ -99,6 +107,10 @@
<q-dialog v-model="showUserData">
<UserData :showInsertBtn="false" />
</q-dialog>
<q-dialog v-model="showAIConfig">
<AIConfig />
</q-dialog>
</div>
</template>
@@ -110,6 +122,7 @@ import CommandManageMenu from "components/menu/CommandManageMenu.vue";
import UtilityFeaturesMenu from "components/menu/UtilityFeaturesMenu.vue";
import EnvConfigMenu from "components/menu/EnvConfigMenu.vue";
import PersonalizeMenu from "components/menu/PersonalizeMenu.vue";
import AIConfig from "components/popup/AIConfig.vue";
import UserData from "components/popup/UserData.vue";
import { utoolsFull } from "js/utools.js";
import { useCommandManager } from "js/commandManager";
@@ -125,6 +138,7 @@ export default {
EnvConfigMenu,
PersonalizeMenu,
UserData,
AIConfig,
},
data() {
return {
@@ -132,6 +146,7 @@ export default {
showAbout: false,
showPanelConf: false,
showUserData: false,
showAIConfig: false,
utools: utoolsFull,
};
},

View File

@@ -0,0 +1,113 @@
<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"
>
<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">
<q-btn flat color="grey" label="取消" v-close-popup />
<q-btn
flat
color="primary"
label="保存"
v-close-popup
@click="saveConfig"
/>
</q-card-section>
</q-card>
</template>
<script>
import { defineComponent } from "vue";
import ButtonGroup from "components/composer/common/ButtonGroup.vue";
export default defineComponent({
name: "AIConfig",
components: {
ButtonGroup,
},
data() {
return {
modelType: "openai",
apiUrl: "",
apiToken: "",
model: "",
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 = [];
}
},
saveConfig() {
this.$root.profile.aiConfig = {
modelType: this.modelType,
apiUrl: this.apiUrl,
apiToken: this.apiToken,
model: this.model,
};
console.log("saveConfig", this.$root.profile.aiConfig);
},
},
mounted() {
const aiConfig = this.$root.profile.aiConfig || {};
this.modelType = aiConfig.modelType || "openai";
this.apiUrl = aiConfig.apiUrl || "";
this.apiToken = aiConfig.apiToken || "";
this.model = aiConfig.model || "";
},
});
</script>