mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-12-17 08:54:19 +08:00
将ai接口移至quickcommand,支持在配置菜单全局配置ai接口
This commit is contained in:
179
src/components/composer/ai/AskAIEditor.vue
Normal file
179
src/components/composer/ai/AskAIEditor.vue
Normal 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>
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
|
||||
113
src/components/popup/AIConfig.vue
Normal file
113
src/components/popup/AIConfig.vue
Normal 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>
|
||||
Reference in New Issue
Block a user