1
0
mirror of https://gitee.com/Doocs/md synced 2025-04-29 09:32:27 +08:00

feat(ai): add default ai service (#666)

This commit is contained in:
Libin YANG 2025-04-28 17:42:51 +08:00 committed by GitHub
parent ba63ba75ba
commit ece39cb816
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 72 additions and 24 deletions

View File

@ -755,8 +755,13 @@ function onDrop(e: DragEvent) {
<TabsContent value="mp">
<Form :validation-schema="mpSchema" :initial-values="mpConfig" @submit="mpSubmit">
<Field v-slot="{ field, errorMessage }" name="proxyOrigin">
<FormItem label="代理域名" :required="isProxyRequired" :error="errorMessage">
<!-- 只有在需要代理时才显示 proxyOrigin 字段 -->
<Field
v-if="isProxyRequired"
v-slot="{ field, errorMessage }"
name="proxyOrigin"
>
<FormItem label="代理域名" required :error="errorMessage">
<Input
v-bind="field"
v-model="field.value"

View File

@ -44,7 +44,7 @@ interface ChatMessage {
}
const messages = ref<ChatMessage[]>([])
const { apiKey, endpoint, model, temperature, maxToken } = useAIConfig()
const { apiKey, endpoint, model, temperature, maxToken, type } = useAIConfig()
onMounted(async () => {
const saved = localStorage.getItem(memoryKey)
@ -160,6 +160,14 @@ async function sendMessage() {
stream: true,
}
const headers: Record<string, string> = {
'Content-Type': `application/json`,
}
if (apiKey.value && type.value !== `default`) {
headers.Authorization = `Bearer ${apiKey.value}`
}
try {
const url = new URL(endpoint.value)
if (!url.pathname.endsWith(`/chat/completions`)) {

View File

@ -9,7 +9,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { type ServiceOption, serviceOptions } from '@/config/ai-services'
import { DEFAULT_SERVICE_ENDPOINT, type ServiceOption, serviceOptions } from '@/config/ai-services'
import { onMounted, reactive, ref, watch } from 'vue'
@ -23,7 +23,7 @@ const config = reactive<{
temperature: number
maxToken: number
}>({
type: `deepseek`,
type: `default`,
endpoint: ``,
apiKey: ``,
model: ``,
@ -39,15 +39,21 @@ function currentService() {
}
function initConfigFromStorage() {
const savedType = localStorage.getItem(`openai_type`) || `deepseek`
const savedType = localStorage.getItem(`openai_type`) || `default`
const service = serviceOptions.find(s => s.value === savedType) || serviceOptions[0]
config.type = savedType
config.endpoint = localStorage.getItem(`openai_endpoint`) || service.endpoint
if (savedType === `default`) {
config.endpoint = DEFAULT_SERVICE_ENDPOINT
}
else {
config.endpoint = localStorage.getItem(`openai_endpoint`) || service.endpoint
}
config.apiKey = localStorage.getItem(`openai_key_${savedType}`) || ``
config.model = service.models.includes(localStorage.getItem(`openai_model`) || ``)
? localStorage.getItem(`openai_model`)!
: service.models[0] || ``
const savedModel = localStorage.getItem(`openai_model`)
config.model = savedModel && service.models.includes(savedModel) ? savedModel : (service.models[0] || ``)
config.temperature = Number(localStorage.getItem(`openai_temperature`) || 1)
config.maxToken = Number(localStorage.getItem(`openai_max_token`) || 1024)
}
@ -56,20 +62,26 @@ onMounted(() => {
initConfigFromStorage()
})
watch(() => config.type, () => {
const service = currentService()
config.endpoint = service.endpoint
const savedModel = localStorage.getItem(`openai_model`)
config.model = savedModel && service.models.includes(savedModel) ? savedModel : (service.models[0] || ``)
config.apiKey = localStorage.getItem(`openai_key_${config.type}`) || ``
testResult.value = `` //
})
//
watch(() => config.model, () => {
testResult.value = `` //
})
watch(() => config.type, () => {
const service = currentService()
if (config.type === `default`) {
config.endpoint = DEFAULT_SERVICE_ENDPOINT
config.model = service.models[0] || ``
}
else {
const savedModel = localStorage.getItem(`openai_model`)
config.endpoint = service.endpoint
config.model = savedModel && service.models.includes(savedModel) ? savedModel : (service.models[0] || ``)
config.apiKey = localStorage.getItem(`openai_key_${config.type}`) || ``
}
testResult.value = `` //
})
function saveConfig(emitEvent = true) {
localStorage.setItem(`openai_type`, config.type)
localStorage.setItem(`openai_endpoint`, config.endpoint)
@ -102,6 +114,14 @@ async function testConnection() {
testResult.value = ``
loading.value = true
const headers: Record<string, string> = {
'Content-Type': `application/json`,
}
if (config.apiKey && config.type !== `default`) {
headers.Authorization = `Bearer ${config.apiKey}`
}
try {
const url = new URL(config.endpoint)
if (!url.pathname.endsWith(`/chat/completions`)) {
@ -192,7 +212,7 @@ async function testConnection() {
</div>
<!-- API 端点 -->
<div>
<div v-if="config.type !== 'default'">
<Label class="mb-1 block text-sm font-medium">API 端点</Label>
<Input
v-model="config.endpoint"
@ -201,8 +221,8 @@ async function testConnection() {
/>
</div>
<!-- API 密钥 -->
<div>
<!-- API 密钥仅非 default 显示 -->
<div v-if="config.type !== 'default'">
<Label class="mb-1 block text-sm font-medium">API 密钥</Label>
<Input
v-model="config.apiKey"

View File

@ -1,3 +1,5 @@
export const DEFAULT_SERVICE_ENDPOINT = `https://proxy-ai.doocs.org/v1`
export interface ServiceOption {
value: string
label: string
@ -6,6 +8,19 @@ export interface ServiceOption {
}
export const serviceOptions: ServiceOption[] = [
{
value: `default`,
label: `默认服务(无需配置 sk`,
endpoint: DEFAULT_SERVICE_ENDPOINT,
models: [
`Qwen/Qwen2.5-7B-Instruct`,
`Qwen/Qwen2.5-Coder-7B-Instruct`,
`deepseek-ai/DeepSeek-R1-Distill-Qwen-7B`,
`deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B`,
`THUDM/GLM-Z1-9B-0414`,
`THUDM/GLM-4-9B-0414`,
],
},
{
value: `deepseek`,
label: `DeepSeek`,

View File

@ -525,8 +525,8 @@ export const useStore = defineStore(`store`, () => {
const el = document.querySelector(` #output-wrapper>.preview`)! as HTMLElement
toPng(el, {
backgroundColor: isDark.value ? `` : `#fff`,
skipFonts: true, // 如果加载字体控制台报错,打开这段的注释
pixelRatio: Math.max(window.devicePixelRatio || 1, 2), // 添加 || 1 以防 devicePixelRatio 不可用
skipFonts: true,
pixelRatio: Math.max(window.devicePixelRatio || 1, 2),
}).then((url) => {
const a = document.createElement(`a`)
a.download = filename