Browse Source

add ai engine

master
godo 7 months ago
parent
commit
312d4c2179
  1. 16
      frontend/components.d.ts
  2. 218
      frontend/src/components/ai/AiSettingApi.vue
  3. 1
      frontend/src/components/ai/AiSettingDef.vue
  4. 58
      frontend/src/components/ai/DownAddbox.vue
  5. 25
      frontend/src/components/ai/aimodel.vue
  6. 2
      frontend/src/components/ai/aisetting.vue
  7. 5
      frontend/src/components/localchat/AiChatInfo.vue
  8. 2
      frontend/src/components/localchat/AiChatMain.vue
  9. 2
      frontend/src/i18n/lang/en.json
  10. 2
      frontend/src/i18n/lang/zh.json
  11. 7
      frontend/src/stores/aichat.ts
  12. 2
      frontend/src/stores/db.ts
  13. 12
      frontend/src/stores/model.ts
  14. 75
      frontend/src/stores/modelconfig.ts
  15. 60
      frontend/src/system/aiconfig.ts
  16. 23
      frontend/src/system/config.ts
  17. 24
      frontend/src/util/goutil.ts
  18. 3
      godo/ai/server/chat.go
  19. 8
      godo/ai/server/down.go
  20. 29
      godo/ai/server/llms.go
  21. 12
      godo/libs/dir.go

16
frontend/components.d.ts

@ -63,17 +63,29 @@ declare module 'vue' {
DownModelInfo: typeof import('./src/components/ai/DownModelInfo.vue')['default'] DownModelInfo: typeof import('./src/components/ai/DownModelInfo.vue')['default']
EditFileName: typeof import('./src/components/builtin/EditFileName.vue')['default'] EditFileName: typeof import('./src/components/builtin/EditFileName.vue')['default']
EditType: typeof import('./src/components/builtin/EditType.vue')['default'] EditType: typeof import('./src/components/builtin/EditType.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel'] ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem'] ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress'] ElProgress: typeof import('element-plus/es')['ElProgress']
@ -81,8 +93,12 @@ declare module 'vue' {
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider'] ElSlider: typeof import('element-plus/es')['ElSlider']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElText: typeof import('element-plus/es')['ElText']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
Error: typeof import('./src/components/taskbar/Error.vue')['default'] Error: typeof import('./src/components/taskbar/Error.vue')['default']
FileIcon: typeof import('./src/components/builtin/FileIcon.vue')['default'] FileIcon: typeof import('./src/components/builtin/FileIcon.vue')['default']
FileIconImg: typeof import('./src/components/builtin/FileIconImg.vue')['default'] FileIconImg: typeof import('./src/components/builtin/FileIconImg.vue')['default']

218
frontend/src/components/ai/AiSettingApi.vue

@ -5,55 +5,78 @@ import {
getSystemConfig, getSystemConfig,
setSystemConfig, setSystemConfig,
} from "@/system/config"; } from "@/system/config";
import { OpenDirDialog } from "@/util/goutil"; import { OpenDirDialog, checkUrl } from "@/util/goutil";
const config: any = ref({}); const activeNames = ref([])
const hoverTxt = { const hoverTxt = {
dataDir: t('aisetting.tips_dataDir'), dataDir: t('aisetting.tips_dataDir'),
apiUrl: t('aisetting.tips_apiUrl'), apiUrl: t('aisetting.tips_apiUrl'),
}; };
const formData: any = ref({
dataDir: "",
aiUrl: "",
ollamaUrl: "",
ollamaDir: "",
openaiUrl: "",
openaiSecret: "",
giteeSecret: "",
cloudflareUserId: "",
cloudflareSecret: "",
deepseekSecret: "",
bigmodelSecret: "",
volcesSecret:"",
alibabaSecret: "",
groqSecret: "",
mistralSecret: "",
anthropicSecret: "",
llamafamilySecret: "",
siliconflowSecret: ""
})
onMounted(() => { onMounted(() => {
config.value = getSystemConfig(); const config = getSystemConfig();
for (const key in config) {
if (formData.value.hasOwnProperty(key)) {
formData.value[key] = config[key];
}
}
}); });
async function changeDir() { async function changeDir(name: any) {
const path: any = await OpenDirDialog(); const path: any = await OpenDirDialog();
//console.log(path) //console.log(path)
config.value.dataDir = path; formData.value[name] = path;
} }
const saveConfig = async () => { const saveConfig = async () => {
try { const config = getSystemConfig();
await fetch(config.value.apiUrl, { const saveData = toRaw(formData.value)
method: "GET",
mode: "no-cors",
});
} catch (error) {
notifyError(t('common.urlError'));
return;
}
let postData: any = [] let postData: any = []
if (config.value.dataDir.trim() != "") { for (const key in saveData) {
saveData[key] = saveData[key].trim()
config[key] = saveData[key];
if (saveData[key] != "") {
postData.push({ postData.push({
name: "aiDir", name: key,
value: config.value.dataDir.trim(), value: saveData[key],
}) })
} }
if (config.value.ollamaUrl.trim() != "") {
postData.push({
name: "ollamaUrl",
value: config.value.ollamaUrl.trim(),
})
} }
if (config.value.openaiUrl.trim() != "") { if (saveData.aiUrl != "") {
postData.push({ if (!await checkUrl(saveData.aiUrl)) {
name: "openaiUrl", notifyError("ai服务端地址有误");
value: config.value.openaiUrl.trim(), return;
}) }
} }
if (saveData.ollamaUrl != "") {
if (!await checkUrl(saveData.ollamaUrl)) {
notifyError("ai服务端地址有误");
return;
}
}
if (postData.length > 0) { if (postData.length > 0) {
const postDatas = { const postDatas = {
method: "POST", method: "POST",
body: JSON.stringify(postData), body: JSON.stringify(postData),
}; };
const res: any = await fetch(config.value.apiUrl + "/system/setting", postDatas); const res: any = await fetch(config.apiUrl + "/system/setting", postDatas);
//console.log(res) //console.log(res)
if (!res || !res.ok) { if (!res || !res.ok) {
notifyError(t('common.saveError')); notifyError(t('common.saveError'));
@ -66,17 +89,19 @@ const saveConfig = async () => {
} }
} }
setSystemConfig(config.value); setSystemConfig(config);
notifySuccess(t('common.saveSuccess')); notifySuccess(t('common.saveSuccess'));
}; };
</script> </script>
<template> <template>
<el-scrollbar class="scrollbarSettingHeight"> <el-scrollbar class="scrollbarSettingHeight">
<el-form label-width="150px" style="padding: 0 30px 50px 0"> <el-form label-width="150px" style="padding: 0 30px 50px 0">
<el-collapse v-model="activeNames">
<el-collapse-item title="系统配置" name="system">
<el-form-item :label="t('aisetting.dataDir')"> <el-form-item :label="t('aisetting.dataDir')">
<div class="slider-container"> <div class="slider-container">
<el-input v-model="config.dataDir" :placeholder="t('aisetting.localDirHolder')" prefix-icon="Menu" <el-input v-model="formData.dataDir" placeholder="系统模型下载本地地址" prefix-icon="Menu"
@click="changeDir()" clearable></el-input> @click="changeDir('dataDir')" clearable></el-input>
<el-popover placement="left" :width="400" trigger="click"> <el-popover placement="left" :width="400" trigger="click">
<template #reference> <template #reference>
<el-icon :size="22"> <el-icon :size="22">
@ -89,10 +114,9 @@ const saveConfig = async () => {
</el-popover> </el-popover>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="t('aisetting.serverUrl')"> <el-form-item label="AI服务端地址">
<div class="slider-container"> <div class="slider-container">
<el-input v-model="config.aiUrl" :placeholder="t('aisetting.serverUrl')" prefix-icon="Notification" <el-input v-model="formData.aiUrl" placeholder="AI服务端地址" prefix-icon="Notification" clearable></el-input>
clearable></el-input>
<el-popover placement="left" :width="400" trigger="click"> <el-popover placement="left" :width="400" trigger="click">
<template #reference> <template #reference>
<el-icon :size="22"> <el-icon :size="22">
@ -105,20 +129,138 @@ const saveConfig = async () => {
</el-popover> </el-popover>
</div> </div>
</el-form-item> </el-form-item>
</el-collapse-item>
<el-collapse-item title="Ollama配置" name="ollama">
<el-form-item :label="t('aisetting.ollamaUrl')"> <el-form-item :label="t('aisetting.ollamaUrl')">
<div class="slider-container"> <div class="slider-container">
<el-input v-model="config.ollamaUrl" :placeholder="t('aisetting.ollamaUrl')" prefix-icon="Notification" <el-input v-model="formData.ollamaUrl" placeholder="ollama模型访问地址,设置后ollama需重启,为空则使用http://localhost:11434"
prefix-icon="Notification" clearable></el-input>
</div>
</el-form-item>
<el-form-item label="模型存储地址">
<div class="slider-container">
<el-input v-model="formData.ollamaDir" placeholder="ollama模型存储地址,设置后ollama需重启,为空则使用默认地址"
prefix-icon="Menu" @click="changeDir('ollamaDir')" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="OpenAI配置" name="openai">
<el-form-item label="OpenAI地址">
<div class="slider-container">
<el-input v-model="formData.openaiUrl" placeholder="OpenAI地址,可设置为代理地址" prefix-icon="Notification"
clearable></el-input> clearable></el-input>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="OpenAI URL"> <el-form-item label="OpenAI私钥">
<div class="slider-container">
<el-input v-model="formData.openaiSecret" placeholder="OpenAI私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="GiteeAI配置" name="gitee">
<el-form-item label="GiteeAI私钥">
<div class="slider-container"> <div class="slider-container">
<el-input v-model="config.openaiUrl" placeholder="OpenAI URL" prefix-icon="Notification" <el-input v-model="formData.giteeSecret" placeholder="GiteeAI私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="Cloudflare Workers AI配置" name="cloudflare">
<el-form-item label="Cloudflare用户ID">
<div class="slider-container">
<el-input v-model="formData.cloudflareUserId" placeholder="Cloudflare用户ID" prefix-icon="Notification"
clearable></el-input> clearable></el-input>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="Cloudflare私钥">
<div class="slider-container">
<el-input v-model="formData.cloudflareSecret" placeholder="Cloudflare私钥" prefix-icon="Lock"
clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="DeepSeek配置" name="deepseek">
<el-form-item label="DeepSeek私钥">
<div class="slider-container">
<el-input v-model="formData.deepseekSecret" placeholder="DeepSeek私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="智谱清言语BigModel配置" name="bigmodel">
<el-form-item label="智谱清言语私钥">
<div class="slider-container">
<el-input v-model="formData.bigmodelSecret" placeholder="智谱清言语私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="火山方舟配置" name="volces">
<el-form-item label="火山方舟私钥">
<div class="slider-container">
<el-input v-model="formData.volcesSecret" placeholder="火山方舟私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="阿里通义DashScope配置" name="alibaba">
<el-form-item label="阿里通义私钥">
<div class="slider-container">
<el-input v-model="formData.alibabaSecret" placeholder="阿里通义DashScope私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="Groq配置" name="groq">
<el-form-item label="Groq私钥">
<div class="slider-container">
<el-input v-model="formData.groqSecret" placeholder="Groq私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="Mistral配置" name="mistral">
<el-form-item label="mistral私钥">
<div class="slider-container">
<el-input v-model="formData.mistralSecret" placeholder="mistral私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="Anthropic配置" name="anthropic">
<el-form-item label="anthropic私钥">
<div class="slider-container">
<el-input v-model="formData.anthropicSecret" placeholder="anthropic私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="llama.family配置" name="llamafamily">
<el-form-item label="llamafamily私钥">
<div class="slider-container">
<el-input v-model="formData.llamafamilySecret" placeholder="llamafamily私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
<el-collapse-item title="硅基流动配置" name="siliconflow">
<el-form-item label="硅基流动私钥">
<div class="slider-container">
<el-input v-model="formData.siliconflowSecret" placeholder="硅基流动siliconflow私钥" prefix-icon="Lock" clearable></el-input>
</div>
</el-form-item>
</el-collapse-item>
</el-collapse>
<el-form-item> <el-form-item style="margin-top: 10px;">
<el-button @click="saveConfig" type="info" plain> <el-button @click="saveConfig" type="info" plain>
{{ t("common.confim") }} {{ t("common.confim") }}
</el-button> </el-button>

1
frontend/src/components/ai/AiSettingDef.vue

@ -6,7 +6,6 @@ const currentsModel: any = ref({});
onMounted(async () => { onMounted(async () => {
await modelStore.getModelList(); await modelStore.getModelList();
updateCurrentsModel(); updateCurrentsModel();
}); });
function updateCurrentsModel() { function updateCurrentsModel() {
modelStore.cateList.forEach((item:any) => { modelStore.cateList.forEach((item:any) => {

58
frontend/src/components/ai/DownAddbox.vue

@ -20,8 +20,18 @@ const formInit = {
quant: "q4_K_M", quant: "q4_K_M",
pb: "", pb: "",
}, },
type: "", type: "local",
}; };
const engineType = [
{
name: "local",
label: "本地引擎",
},
{
name: "net",
label: "网络引擎",
},
]
const formData = ref(formInit); const formData = ref(formInit);
const fromSource = computed(() => { const fromSource = computed(() => {
if (formData.value.info.engine == "ollama") { if (formData.value.info.engine == "ollama") {
@ -59,6 +69,15 @@ function setFrom(val: string) {
formData.value.info.from = "network" formData.value.info.from = "network"
} }
} }
function setType(val: string) {
if (val == "local") {
formData.value.info.from = "ollama"
formData.value.info.engine = "ollama"
} else {
formData.value.info.from = "network"
formData.value.info.engine = "openai"
}
}
const emit = defineEmits(["closeFn", "saveFn"]); const emit = defineEmits(["closeFn", "saveFn"]);
const localModels: any = ref([]); const localModels: any = ref([]);
async function getLocalModel() { async function getLocalModel() {
@ -99,13 +118,11 @@ function setLocalInfo() {
urls.push(url + item); urls.push(url + item);
}); });
//formData.value.info.url = urls; //formData.value.info.url = urls;
modelData.info.url = urls; modelData.info.url = urls.join("\n");
modelData.info.engine = "ollama";
modelData.info.from = "local";
modelData.info.model = formData.value.info.model;
formData.value.info = modelData.info; formData.value.info = modelData.info;
//formData.value.file_name = modelData.file_name;
//formData.value.engine = modelData.engine;
// if (modelData.engine == "ollama") {
// formData.value.type = "local";
// }
} }
async function download() { async function download() {
const saveData: any = toRaw(formData.value); const saveData: any = toRaw(formData.value);
@ -115,12 +132,17 @@ async function download() {
notifyError(t('model.selectLabel')); notifyError(t('model.selectLabel'));
return; return;
} }
if(saveData.info.model == ""){
if (saveData.info.from == "ollama") {
if (saveData.info.model == "") {
notifyError(t('model.labelNameEmpty')); notifyError(t('model.labelNameEmpty'));
return; return;
} }
if(saveData.type == "net"){
saveData.info.url = [];
saveData.info.context_length = 1024;
emit("saveFn", saveData);
return;
}
if (saveData.info.from == "ollama") {
if (saveData.info.model.indexOf(":") === -1) { if (saveData.info.model.indexOf(":") === -1) {
saveData.info.model = saveData.info.model + ":latest"; saveData.info.model = saveData.info.model + ":latest";
} }
@ -204,12 +226,20 @@ async function download() {
<el-option v-for="(item, key) in modelStore.labelList" :key="key" :label="item.name" :value="item.id" /> <el-option v-for="(item, key) in modelStore.labelList" :key="key" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="引擎类型">
<el-select v-model="formData.type" placeholder="选择引擎类型" @change="setType">
<el-option v-for="item, key in engineType" :key="key" :label="item.label" :value="item.name" />
</el-select>
</el-form-item>
<el-form-item :label="t('model.selectEngine')"> <el-form-item :label="t('model.selectEngine')">
<el-select v-model="formData.info.engine" :placeholder="t('model.selectEngine')" @change="setFrom"> <el-select v-model="formData.info.engine" v-if="formData.type == 'local'" :placeholder="t('model.selectEngine')" @change="setFrom">
<el-option v-for="item, key in modelStore.modelEngines" :key="key" :label="item.name" :value="item.name" /> <el-option v-for="item, key in modelStore.modelEngines" :key="key" :label="item.name" :value="item.name" />
</el-select> </el-select>
<el-select v-model="formData.info.engine" v-else :placeholder="t('model.selectEngine')" @change="setFrom">
<el-option v-for="item, key in modelStore.netEngines" :key="key" :label="item.name" :value="item.cpp" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('model.selectSource')"> <el-form-item :label="t('model.selectSource')" v-if="formData.type == 'local'">
<el-select v-model="formData.info.from"> <el-select v-model="formData.info.from">
<el-option v-for="(item, key) in fromSource" :key="key" :label="item.label" :value="item.value" /> <el-option v-for="(item, key) in fromSource" :key="key" :label="item.label" :value="item.value" />
</el-select> </el-select>
@ -219,7 +249,7 @@ async function download() {
:placeholder="t('model.enterModelName')"></el-input> :placeholder="t('model.enterModelName')"></el-input>
</el-form-item> </el-form-item>
<template v-if="formData.info.from === 'local'"> <template v-if="formData.info.from === 'local' && formData.type == 'local'">
<el-form-item :label="t('model.oppositeIpAddress')"> <el-form-item :label="t('model.oppositeIpAddress')">
<el-input v-model="formData.ip" prefix-icon="Key" clearable placeholder="192.168.1.66" <el-input v-model="formData.ip" prefix-icon="Key" clearable placeholder="192.168.1.66"
@blur="getLocalModel"></el-input> @blur="getLocalModel"></el-input>
@ -230,7 +260,7 @@ async function download() {
</el-select> </el-select>
</el-form-item> </el-form-item>
</template> </template>
<template v-if="formData.info.from === 'network'"> <template v-if="formData.info.from === 'network' && formData.type == 'local'">
<el-form-item :label="t('model.modelUrl')"> <el-form-item :label="t('model.modelUrl')">
<el-input type="textarea" :row="3" v-model="formData.info.url" <el-input type="textarea" :row="3" v-model="formData.info.url"
:placeholder="t('model.enterModelUrl')"></el-input> :placeholder="t('model.enterModelUrl')"></el-input>

25
frontend/src/components/ai/aimodel.vue

@ -38,7 +38,7 @@ async function downLabel(modelData: any, labelData: any) {
labelData = toRaw(labelData); labelData = toRaw(labelData);
modelData = toRaw(modelData); modelData = toRaw(modelData);
//console.log(modelData, labelData) //console.log(modelData, labelData)
if(modelData.model){ if (modelData.model) {
modelData.info.model = modelData.model modelData.info.model = modelData.model
} }
const saveData = { const saveData = {
@ -62,6 +62,13 @@ async function saveBox(modelData: any) {
notifyError(t('model.chooseLabel')); notifyError(t('model.chooseLabel'));
return; return;
} }
if (modelData.type == "net") {
const engine = modelData.info.engine;
if (config[engine + "Secret"] == "") {
notifyError(engine + " secret is empty");
return;
}
}
//console.log(modelData) //console.log(modelData)
downLabel(modelData, labelData); downLabel(modelData, labelData);
} }
@ -77,7 +84,8 @@ async function download(saveData: any) {
notifyError(t('model.labelDown')); notifyError(t('model.labelDown'));
return; return;
} }
//console.log(saveData);
const downUrl = config.aiUrl + "/ai/download"; const downUrl = config.aiUrl + "/ai/download";
try { try {
@ -91,11 +99,22 @@ async function download(saveData: any) {
notifyError(errorData.message); notifyError(errorData.message);
return; return;
} }
if (saveData.type == "net") {
//console.log(saveData)
const res = await completion.json();
if(res.code == -1){
notifyError(res.message);
return
}
await modelStore.checkModelList(saveData)
notifySuccess(t('model.labelDown'));
} else {
saveData.status = "loading"; saveData.status = "loading";
saveData.progress = 0; saveData.progress = 0;
modelStore.addDownload(saveData); modelStore.addDownload(saveData);
await handleDown(saveData, completion); await handleDown(saveData, completion);
}
} catch (error: any) { } catch (error: any) {
notifyError(error.message); notifyError(error.message);
} }

2
frontend/src/components/ai/aisetting.vue

@ -27,7 +27,7 @@ const activeName = ref("system");
} }
.scrollbarSettingHeight { .scrollbarSettingHeight {
height: 85vh; height: 75vh;
padding-bottom: 30px; padding-bottom: 30px;
} }

5
frontend/src/components/localchat/AiChatInfo.vue

@ -36,7 +36,10 @@ const changeInfo = async () => {
prompt: info.prompt, prompt: info.prompt,
promptId: info.promptId, promptId: info.promptId,
}; };
await chatStore.addChat(info.title, info.model, promptData, ""); const modelData = chatStore.modelList.find((item:any) => {
return item.model == info.model;
});
await chatStore.addChat(info.title, modelData, promptData, "");
notifySuccess(t("aichat.addsuccess")); notifySuccess(t("aichat.addsuccess"));
} }
await chatStore.getActiveChat(); await chatStore.getActiveChat();

2
frontend/src/components/localchat/AiChatMain.vue

@ -83,6 +83,7 @@ const createCompletion = async () => {
let postMsg: any = { let postMsg: any = {
messages: requestMessages.value, messages: requestMessages.value,
model: chatStore.chatInfo.model, model: chatStore.chatInfo.model,
engine: chatStore.chatInfo.engine,
stream: false, stream: false,
options: chatConfig, options: chatConfig,
}; };
@ -92,6 +93,7 @@ const createCompletion = async () => {
postMsg = { postMsg = {
model: img2txtModel.model, model: img2txtModel.model,
//"prompt":userMessage.value, //"prompt":userMessage.value,
engine: img2txtModel.info.engine,
stream: false, stream: false,
options: chatConfig, options: chatConfig,
messages: [ messages: [

2
frontend/src/i18n/lang/en.json

@ -269,7 +269,7 @@
"noDown": "Incomplete", "noDown": "Incomplete",
"downloading": "Downloading", "downloading": "Downloading",
"modelLabel": "ModelLabel", "modelLabel": "ModelLabel",
"modelDown": "Download", "modelDown": "Add Model",
"help_label": "Choose category", "help_label": "Choose category",
"help_labelDesc": "Select the model category you want to download", "help_labelDesc": "Select the model category you want to download",
"help_showdown": "View Download", "help_showdown": "View Download",

2
frontend/src/i18n/lang/zh.json

@ -272,7 +272,7 @@
"noDown": "未下载", "noDown": "未下载",
"downloading": "下载中", "downloading": "下载中",
"modelLabel": "模型标签", "modelLabel": "模型标签",
"modelDown": "模型下载", "modelDown": "添加模型",
"labelName": "标签名称", "labelName": "标签名称",
"family": "家族", "family": "家族",
"category": "分类", "category": "分类",

7
frontend/src/stores/aichat.ts

@ -26,7 +26,7 @@ export const useAiChatStore = defineStore('aichat', () => {
return false return false
} }
const promptData = await promptStore.getPrompt('chat') const promptData = await promptStore.getPrompt('chat')
return await addChat(t('aichat.newchat'), currentModel.model, promptData, "") return await addChat(t('aichat.newchat'), currentModel, promptData, "")
} }
const initChat = async () => { const initChat = async () => {
if (activeId.value === 0) { if (activeId.value === 0) {
@ -53,12 +53,13 @@ export const useAiChatStore = defineStore('aichat', () => {
return list return list
} }
// 添加聊天 // 添加聊天
async function addChat(title: string, model: any, promptData: any, knowledgeId: string) { async function addChat(title: string, modelData: any, promptData: any, knowledgeId: string) {
const newChat = { const newChat = {
title, title,
prompt: promptData.prompt, prompt: promptData.prompt,
promptId: promptData.id, promptId: promptData.id,
model, model: modelData.model,
engine: modelData.info.engine,
createdAt: Date.now(), createdAt: Date.now(),
knowledgeId knowledgeId
} }

2
frontend/src/stores/db.ts

@ -11,7 +11,7 @@ dbInit.version(1).stores({
// 模型列表 // 模型列表
modelslist: '++id,model,label,action,status,params,type,isdef,info,created_at', modelslist: '++id,model,label,action,status,params,type,isdef,info,created_at',
// ai对话列表 // ai对话列表
aichatlist: '++id,title,model,promptId,prompt,knowledgeId,createdAt', aichatlist: '++id,title,model,engine,promptId,prompt,knowledgeId,createdAt',
// ai对话消息 // ai对话消息
aichatmsg: '++id,chatId,role,content,createdAt', aichatmsg: '++id,chatId,role,content,createdAt',
// 用户列表 // 用户列表

12
frontend/src/stores/model.ts

@ -76,7 +76,7 @@ export const useModelStore = defineStore('modelStore', () => {
if (existingModels.includes(d.model)) { if (existingModels.includes(d.model)) {
d.isdef = 1 d.isdef = 1
} }
if(d.action == ""){ if (d.action == "") {
d.action = "chat" d.action = "chat"
} }
}); });
@ -104,7 +104,7 @@ export const useModelStore = defineStore('modelStore', () => {
return model return model
} }
} }
async function getModelAction(action:string) { async function getModelAction(action: string) {
return await db.rows("modelslist", { action }) return await db.rows("modelslist", { action })
} }
async function getList() { async function getList() {
@ -203,12 +203,15 @@ export const useModelStore = defineStore('modelStore', () => {
modelData.isLoading = 0; modelData.isLoading = 0;
modelData.progress = 0; modelData.progress = 0;
deleteDownload(modelData.model); deleteDownload(modelData.model);
await checkModelList(modelData);
}
}
}
async function checkModelList(modelData: any) {
await getModelList(); await getModelList();
await checkLabelData(modelData); await checkLabelData(modelData);
await setCurrentModel(modelData.action, modelData.model); await setCurrentModel(modelData.action, modelData.model);
} }
}
}
function parseJson(str: string): any { function parseJson(str: string): any {
try { try {
return JSON.parse(str); return JSON.parse(str);
@ -274,6 +277,7 @@ export const useModelStore = defineStore('modelStore', () => {
deleteDownload, deleteDownload,
updateDownload, updateDownload,
checkLabelData, checkLabelData,
checkModelList,
getLabelCate, getLabelCate,
getLabelSearch, getLabelSearch,
getLabelList, getLabelList,

75
frontend/src/stores/modelconfig.ts

@ -38,52 +38,59 @@ export const netEngines = [
cpp: "openai", cpp: "openai",
needID: false, needID: false,
}, },
// {
// name: "Google",
// cpp: "gemini"
// },
{ {
name: "GiteeAI", name: "GiteeAI",
cpp: "giteeAI", cpp: "gitee",
needID: false, needID: false,
}, },
// {
// name: "Baidu",
// cpp: "baidu"
// },
{ {
name: "Alibaba", name: "CloudflareWorkersAI",
cpp: "alibaba", cpp: "cloudflare",
needID: true,
},
{
name: "DeepSeek",
cpp: "deepseek",
needID: false, needID: false,
}, },
// {
// name: "Tencent",
// cpp: "tencent"
// },
// {
// name: "Kimi",
// cpp: "Moonshot"
// },
{ {
name: "BigModel", name: "智谱清言语BigModel",
cpp: "BigModel", cpp: "bigmodel",
needID: false,
},
{
name: "火山方舟",
cpp: "volces",
needID: false,
},
{
name: "阿里通义",
cpp: "alibaba",
needID: false, needID: false,
}, },
// {
// name: "xAI",
// cpp: "xAI"
// },
// {
// name: "Stability",
// cpp: "stability"
// },
// {
// name: "Anthropic",
// cpp: "claude"
// },
{ {
name: "Groq", name: "Groq",
cpp: "groqcloud", cpp: "groq",
needID: false,
},
{
name: "Mistral",
cpp: "mistral",
needID: false,
},
{
name: "Anthropic",
cpp: "anthropic",
needID: false,
},
{
name: "llama.family",
cpp: "llamafamily",
needID: false,
},
{
name: "硅基流动",
cpp: "siliconflow",
needID: false, needID: false,
}, },
] ]

60
frontend/src/system/aiconfig.ts

@ -0,0 +1,60 @@
export const parseAiConfig = (config: any) => {
if (!config.ollamaUrl) {
config.ollamaUrl = `${window.location.protocol}//${window.location.hostname}:11434`
}
if(!config.ollamaDir) {
config.ollamaDir = ''
}
if (!config.dataDir) {
config.dataDir = ''
}
if (!config.aiUrl) {
config.aiUrl = config.apiUrl
}
//openai
if (!config.openaiUrl) {
config.openaiUrl = 'https://api.openai.com/v1'
}
if (!config.openaiSecret) {
config.openaiSecret = ""
}
//gitee
if (!config.giteeSecret) {
config.giteeSecret = ""
}
//cloudflare
if(!config.cloudflareUserId){
config.cloudflareUserId = ""
}
if(!config.cloudflareSecret){
config.cloudflareSecret = ""
}
if(!config.deepseekSecret) {
config.deepseekSecret = ""
}
if(!config.bigmodelSecret) {
config.bigmodelSecret = ""
}
if(!config.volcesSecret){
config.volcesSecret = ""
}
if(!config.alibabaSecret) {
config.alibabaSecret = ""
}
if(!config.groqSecret) {
config.groqSecret = ""
}
if(!config.mistralSecret) {
config.mistralSecret = ""
}
if(!config.anthropicSecret) {
config.anthropicSecret = ""
}
if(!config.llamafamilySecret) {
config.llamafamilySecret = ""
}
if(!config.siliconflowSecret) {
config.siliconflowSecret = ""
}
return config
};

23
frontend/src/system/config.ts

@ -1,5 +1,6 @@
import { generateRandomString } from "../util/common.ts"; import { generateRandomString } from "../util/common.ts";
export const configStoreType = localStorage.getItem('GodoOS-storeType') || 'local'; export const configStoreType = localStorage.getItem('GodoOS-storeType') || 'local';
import { parseAiConfig } from "./aiconfig.ts";
/** /**
* *
* *
@ -10,7 +11,7 @@ export const getSystemConfig = (ifset = false) => {
// 从本地存储中尝试获取配置信息,若不存在则使用默认空对象 // 从本地存储中尝试获取配置信息,若不存在则使用默认空对象
const configSetting = localStorage.getItem('GodoOS-config') || '{}'; const configSetting = localStorage.getItem('GodoOS-config') || '{}';
// 解析配置信息为JSON对象 // 解析配置信息为JSON对象
const config = JSON.parse(configSetting); let config:any = JSON.parse(configSetting);
// 初始化配置对象的各项属性,若本地存储中已存在则不进行覆盖 // 初始化配置对象的各项属性,若本地存储中已存在则不进行覆盖
if (!config.version) { if (!config.version) {
@ -148,24 +149,7 @@ export const getSystemConfig = (ifset = false) => {
'fourthEnd': '254' 'fourthEnd': '254'
} }
} }
if (!config.ollamaUrl) {
config.ollamaUrl = `${window.location.protocol}//${window.location.hostname}:11434`
}
if (!config.dataDir) {
config.dataDir = ''
}
if (!config.aiUrl) {
config.aiUrl = config.apiUrl
}
if (!config.openaiUrl) {
config.openaiUrl = 'https://api.openai.com/v1'
}
if (!config.openaiSecret) {
config.openaiSecret = ""
}
if(!config.giteeSecret){
config.giteeSecret = ""
}
// 初始化桌面快捷方式列表,若本地存储中已存在则不进行覆盖 // 初始化桌面快捷方式列表,若本地存储中已存在则不进行覆盖
if (!config.desktopList) { if (!config.desktopList) {
config.desktopList = []; config.desktopList = [];
@ -178,6 +162,7 @@ export const getSystemConfig = (ifset = false) => {
if (!config.token) { if (!config.token) {
config.token = generateRandomString(16); config.token = generateRandomString(16);
} }
config = parseAiConfig(config);
// 根据参数决定是否更新本地存储中的配置信息 // 根据参数决定是否更新本地存储中的配置信息
if (ifset) { if (ifset) {
setSystemConfig(config) setSystemConfig(config)

24
frontend/src/util/goutil.ts

@ -1,15 +1,25 @@
export async function OpenDirDialog(){ export async function OpenDirDialog() {
if((window as any).go) { if ((window as any).go) {
return (window as any)['go']['app']['App']['OpenDirDialog'](); return (window as any)['go']['app']['App']['OpenDirDialog']();
}else { } else {
return "" return ""
} }
} }
export async function checkUrl(url: string) {
export function RestartApp(){ try {
if(!(window as any).go){ await fetch(url, {
method: "GET",
mode: "no-cors",
});
return true;
} catch (error) {
return false;
}
}
export function RestartApp() {
if (!(window as any).go) {
window.location.reload(); window.location.reload();
}else{ } else {
return (window as any)['go']['app']['App']['RestartApp'](); return (window as any)['go']['app']['App']['RestartApp']();
} }

3
godo/ai/server/chat.go

@ -3,6 +3,7 @@ package server
import ( import (
"encoding/json" "encoding/json"
"godo/libs" "godo/libs"
"log"
"net/http" "net/http"
) )
@ -16,6 +17,8 @@ func ChatHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
headers, url, err := GetHeadersAndUrl(req, "chat") headers, url, err := GetHeadersAndUrl(req, "chat")
log.Printf("url: %s", url)
log.Printf("headers: %v", headers)
if err != nil { if err != nil {
libs.ErrorMsg(w, err.Error()) libs.ErrorMsg(w, err.Error())
return return

8
godo/ai/server/down.go

@ -54,6 +54,14 @@ func Download(w http.ResponseWriter, r *http.Request) {
return return
} }
if reqBody.Type == "net" {
if err := config.SetModel(reqBody); err != nil {
libs.ErrorMsg(w, "Set model error")
return
}
noticeSuccess(w)
return
}
if reqBody.Info.From == "ollama" && reqBody.Info.Engine == "ollama" { if reqBody.Info.From == "ollama" && reqBody.Info.Engine == "ollama" {
setOllamaInfo(w, r, reqBody) setOllamaInfo(w, r, reqBody)
return return

29
godo/ai/server/llms.go

@ -12,6 +12,7 @@ var OpenAIApiMaps = map[string]string{
"gitee": "", "gitee": "",
"cloudflare": "", "cloudflare": "",
"deepseek": "https://api.deepseek.com/v1", "deepseek": "https://api.deepseek.com/v1",
"volces": "https://ark.cn-beijing.volces.com/api/v3",
"bigmodel": "https://open.bigmodel.cn/api/paas/v4", "bigmodel": "https://open.bigmodel.cn/api/paas/v4",
"alibaba": "https://dashscope.aliyuncs.com/compatible-mode/v1", "alibaba": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"01ai": "https://api.lingyiwanwu.com/v1", "01ai": "https://api.lingyiwanwu.com/v1",
@ -19,6 +20,7 @@ var OpenAIApiMaps = map[string]string{
"mistral": "https://api.mistral.ai/v1", "mistral": "https://api.mistral.ai/v1",
"anthropic": "https://api.anthropic.com/v1", "anthropic": "https://api.anthropic.com/v1",
"llamafamily": "https://api.atomecho.cn/v1", "llamafamily": "https://api.atomecho.cn/v1",
"siliconflow": "https://api.siliconflow.cn/v1",
} }
func GetHeadersAndUrl(req map[string]interface{}, chattype string) (map[string]string, string, error) { func GetHeadersAndUrl(req map[string]interface{}, chattype string) (map[string]string, string, error) {
@ -59,14 +61,31 @@ func GetHeadersAndUrl(req map[string]interface{}, chattype string) (map[string]s
if chattype == "embeddings" { if chattype == "embeddings" {
typeUrl = "/embeddings" typeUrl = "/embeddings"
} else if chattype == "text2img" { } else if chattype == "text2img" {
if engine == "gitee" {
typeUrl = "/text-to-image"
} else {
typeUrl = "/images/generations" typeUrl = "/images/generations"
} }
}
return headers, url + typeUrl, nil return headers, url + typeUrl, nil
} }
func GetOpenAIHeaders(types string) (map[string]string, error) { func GetOpenAIHeaders(types string) (map[string]string, error) {
if types == "ollama" {
return map[string]string{
"Content-Type": "application/json",
}, nil
}
secret, err := GetOpenAISecret(types) secret, err := GetOpenAISecret(types)
if types == "gitee" {
return map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer " + secret,
"X-Package": "1910",
}, nil
}
if err != nil { if err != nil {
return map[string]string{ return map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
@ -109,13 +128,5 @@ func GetCloudflareUrl() (string, error) {
return "https://api.cloudflare.com/client/v4/accounts/" + userId + "/ai/v1", nil return "https://api.cloudflare.com/client/v4/accounts/" + userId + "/ai/v1", nil
} }
func GetGiteeUrl(model string, chatType string) string { func GetGiteeUrl(model string, chatType string) string {
if chatType == "chat" { return "https://ai.gitee.com/api/serverless/" + model
return "https://ai.gitee.com/api/serverless/" + model + "/chat/completions"
} else if chatType == "embeddings" {
return "https://ai.gitee.com/api/serverless/" + model + "/embeddings"
} else if chatType == "text2img" {
return "https://ai.gitee.com/api/serverless/" + model + "/text-to-image"
} else {
return "https://ai.gitee.com/api/serverless/" + model + "/chat/completions"
}
} }

12
godo/libs/dir.go

@ -55,7 +55,7 @@ func InitServer() error {
} }
SetConfig(ipSetting) SetConfig(ipSetting)
} }
InitOllamaModelPath()
return nil return nil
} }
func InitOsDir() (string, error) { func InitOsDir() (string, error) {
@ -69,6 +69,16 @@ func InitOsDir() (string, error) {
} }
return osDir, nil return osDir, nil
} }
func InitOllamaModelPath() {
ollamaDir, ok := GetConfig("ollamaDir")
if ok && ollamaDir.(string) != "" {
os.Setenv("OLLAMA_MODELS", ollamaDir.(string))
}
ollamaUrl, ok := GetConfig("ollamaUrl")
if ok && ollamaUrl.(string) != "" {
os.Setenv("OLLAMA_HOST", ollamaUrl.(string))
}
}
func GetOsDir() (string, error) { func GetOsDir() (string, error) {
osDir, ok := GetConfig("osPath") osDir, ok := GetConfig("osPath")
if !ok { if !ok {

Loading…
Cancel
Save