完善大语言模型生成文案相关功能

This commit is contained in:
YILS
2025-07-17 16:50:43 +08:00
parent 648f238739
commit 790fcb4f1f
5 changed files with 371 additions and 8 deletions

View File

@@ -5,6 +5,9 @@ import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import { aliases, mdi } from 'vuetify/iconsets/mdi'
import Toast, { PluginOptions } from 'vue-toastification'
import 'vue-toastification/dist/index.css'
import 'virtual:uno.css'
import './assets/base.scss'
@@ -31,6 +34,7 @@ document.title = GlobalSetting.appName
const app = createApp(App)
app.use(vuetify)
app.use(Toast, { position: 'bottom-left', pauseOnFocusLoss: false } as PluginOptions)
app.use(router)
app.use(store)

View File

@@ -1,9 +1,25 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useUserStore = defineStore(
export const useAppStore = defineStore(
'app',
() => {
return {}
const prompt = ref('')
const llmConfig = ref({
modelName: '',
apiUrl: '',
apiKey: '',
})
const updateLLMConfig = (newConfig: typeof llmConfig.value) => {
llmConfig.value = newConfig
}
return {
prompt,
llmConfig,
updateLLMConfig,
}
},
{
persist: true,

View File

@@ -3,15 +3,69 @@
<v-sheet class="h-[200px] p-2 flex gap-2" border rounded>
<v-textarea
class="h-full"
v-model="prompt"
v-model="appStore.prompt"
label="提示词"
counter
persistent-counter
no-resize
></v-textarea>
<div class="flex flex-col gap-2">
<v-btn prepend-icon="mdi-auto-fix" color="deep-purple-accent-3" stacked> 生成 </v-btn>
<v-btn> 配置 </v-btn>
<v-btn
v-if="!isGenerating"
prepend-icon="mdi-auto-fix"
color="deep-purple-accent-3"
stacked
@click="handleGenerate"
>
生成
</v-btn>
<v-btn v-else prepend-icon="mdi-stop" color="red" stacked @click="handleStopGenerate">
停止
</v-btn>
<v-dialog v-model="configDialogShow" max-width="600" persistent>
<template v-slot:activator="{ props: activatorProps }">
<v-btn v-bind="activatorProps"> 配置 </v-btn>
</template>
<v-card prepend-icon="mdi-text-box-edit-outline" title="配置大语言模型接口">
<v-card-text>
<v-text-field
label="模型名称"
v-model="config.modelName"
required
clearable
></v-text-field>
<v-text-field
label="API 地址"
v-model="config.apiUrl"
required
clearable
></v-text-field>
<v-text-field
label="API Key"
v-model="config.apiKey"
type="password"
required
clearable
></v-text-field>
<small class="text-caption text-medium-emphasis">兼容任意 OpenAI 标准接口</small>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text="关闭" variant="plain" @click="handleCloseDialog"></v-btn>
<v-btn
color="success"
text="测试"
variant="tonal"
:loading="testStatus === TestStatusEnum.LOADING"
@click="handleTestConfig"
></v-btn>
<v-btn color="primary" text="保存" variant="tonal" @click="handleSaveConfig"></v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</v-sheet>
<v-sheet class="h-0 flex-1 p-2" border rounded>
@@ -28,10 +82,95 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useAppStore } from '@/store'
import { nextTick, ref, toRaw } from 'vue'
import { createOpenAI } from '@ai-sdk/openai'
import { generateText, streamText } from 'ai'
import { useToast } from 'vue-toastification'
const toast = useToast()
const appStore = useAppStore()
const prompt = ref('')
const outputText = ref('')
const isGenerating = ref(false)
const abortController = ref<AbortController | null>(null)
const handleGenerate = async () => {
const openai = createOpenAI({
baseURL: appStore.llmConfig.apiUrl,
apiKey: appStore.llmConfig.apiKey,
})
abortController.value = new AbortController()
isGenerating.value = true
outputText.value = ''
try {
const result = streamText({
model: openai(appStore.llmConfig.modelName),
system: ``,
prompt: appStore.prompt,
onError: (error) => {
throw error
},
abortSignal: abortController.value.signal,
})
for await (const textPart of result.textStream) {
outputText.value += textPart
}
} catch (error) {
console.log(`error`, error)
// @ts-ignore
if (error?.name !== 'AbortError' && error?.error?.name !== 'AbortError') {
toast.error('生成失败,请检查大模型配置是否正确')
}
} finally {
abortController.value = null
isGenerating.value = false
}
}
const handleStopGenerate = () => {
if (abortController.value) {
abortController.value.abort()
}
}
// 配置大模型接口
const config = ref(structuredClone(toRaw(appStore.llmConfig)))
const configDialogShow = ref(false)
const resetConfigDialog = () => {
config.value = structuredClone(toRaw(appStore.llmConfig))
}
const handleCloseDialog = () => {
configDialogShow.value = false
nextTick(resetConfigDialog)
}
const handleSaveConfig = () => {
appStore.updateLLMConfig(config.value)
configDialogShow.value = false
}
// 测试大模型连通性
enum TestStatusEnum {
LOADING = 'loading',
SUCCESS = 'success',
ERROR = 'error',
}
const testStatus = ref<TestStatusEnum>()
const handleTestConfig = async () => {
testStatus.value = TestStatusEnum.LOADING
const openai = createOpenAI({ baseURL: config.value.apiUrl, apiKey: config.value.apiKey })
try {
const result = await generateText({
model: openai(config.value.modelName),
prompt: 'Hello world',
})
console.log(`result`, result)
testStatus.value = TestStatusEnum.SUCCESS
toast.success('大模型连接成功')
} catch (error) {
console.log(`error`, error)
testStatus.value = TestStatusEnum.ERROR
toast.error('大模型连接失败,请检查配置是否正确')
}
}
</script>
<style lang="scss" scoped>