✨ 添加标题生成

This commit is contained in:
chaos-zhu
2025-04-17 22:04:24 +08:00
parent 4643588fed
commit 505c026202
5 changed files with 166 additions and 30 deletions
+20 -12
View File
@@ -37,25 +37,33 @@ async function saveAIConfig({ res, request }) {
async function getChatHistory({ res }) {
const chatHistory = await chatHistoryDB.findAsync({})
chatHistory.sort((a, b) => b.createdAt - a.createdAt)
res.success({ data: chatHistory || [] })
const newChatHistory = chatHistory.map(item => {
item.id = item._id
delete item._id
return item
}).sort((a, b) => b.createdAt - a.createdAt)
res.success({ data: newChatHistory || [] })
}
async function saveChatHistory({ res, request }) {
const chatRecord = request.body
const { id, _id } = chatRecord
if (!id) return res.fail({ data: false, msg: '参数错误' })
if (!_id) {
console.log('不存在-创建')
chatRecord.createdAt = Date.now()
await chatHistoryDB.insertAsync(chatRecord)
} else {
const { id = '', chatList } = chatRecord
if (!chatList) return res.fail({ data: false, msg: '参数错误' })
let updateChat = chatRecord
if (id) {
console.log('存在-更新')
chatRecord.updatedAt = Date.now()
await chatHistoryDB.updateAsync({ _id }, chatRecord)
await chatHistoryDB.updateAsync({ _id: id }, chatRecord)
} else {
console.log('不存在-创建')
chatRecord.createdAt = Date.now()
delete chatRecord.id
const result = await chatHistoryDB.insertAsync(chatRecord)
updateChat = result
updateChat.id = result._id
delete updateChat._id
}
res.success({ data: true })
res.success({ data: { updateChat } })
}
async function removeChatHistory({ res, request }) {
+18 -4
View File
@@ -40,12 +40,20 @@
tag-type="success"
tag-effect="plain"
clearable
@change="handleModelsChange"
/>
<el-button type="primary" :loading="fetchingModels" @click="handleFetchModels">获取模型</el-button>
</div>
</el-form-item>
<el-form-item label="标题生成" prop="titlePrompt">
<el-input v-model="AIconfigFormData.titlePrompt" clearable placeholder="例如: 使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,不要加粗,如果没有主题,请直接返回“闲聊”" />
<el-form-item label="标题生成" prop="titleGenMedel">
<el-select v-model="AIconfigFormData.titleGenMedel" clearable placeholder="请选择标题生成模型">
<el-option
v-for="(item, index) in AIconfigFormData.models"
:key="index"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-form>
<el-alert title="提示:" type="warning" :closable="false">
@@ -104,7 +112,7 @@ const AIconfigFormData = ref({
apiUrl: '',
apiKey: '',
models: [],
titlePrompt: '使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,不要加粗,如果没有主题,请直接返回“闲聊”'
titleGenMedel: ''
})
watch(props.aiConfig, (newVal) => {
@@ -124,7 +132,7 @@ const rules = {
],
apiKey: [{ required: true, message: '请输入API KEY', trigger: 'change' },],
models: [{ required: true, message: '请输入或获取模型列表', trigger: 'change' },],
titlePrompt: [{ required: false, message: '请输入标题生成提示', trigger: 'change' },]
titleGenMedel: [{ required: true, message: '请选择标题生成模型', trigger: 'change' },]
}
const handleFetchModels = async () => {
@@ -177,6 +185,12 @@ const inputApiUrlSuggestion = (query, cb) => {
const origin = new URL(AIconfigFormData.value.apiUrl).origin
cb([{ value: `${ origin }/v1/chat/completions` },])
}
const handleModelsChange = (newVal) => {
if (!newVal?.includes(AIconfigFormData.value.titleGenMedel)) {
AIconfigFormData.value.titleGenMedel = ''
}
}
</script>
<style lang="scss" scoped>
+48 -8
View File
@@ -35,7 +35,7 @@
<el-icon
class="right_wrap_icon"
title="新建对话"
@click="addChat"
@click="handleNewChat"
>
<Plus />
</el-icon>
@@ -56,13 +56,14 @@
v-for="chatItem in chatHistory"
:key="chatItem.id"
:class="{ 'active': chatId === chatItem.id }"
@click="changeChat(chatItem.id)"
@click="handleChangeChat(chatItem.id)"
>
<div class="chat_item">
<div class="chat_item_info">
<span>{{ new Date(chatItem.createdAt).toLocaleString() }}</span>
<span class="descript">{{ chatItem.describe }}</span>
<span class="created">{{ new Date(chatItem.createdAt).toLocaleString() }}</span>
</div>
<el-icon class="chat_item_icon" @click.stop="removeChat(chatItem._id)">
<el-icon class="chat_item_icon" @click.stop="handleDeleteChat(chatItem.id)">
<Delete />
</el-icon>
</div>
@@ -340,6 +341,30 @@ const handleSetting = () => {
aiApiConfigVisible.value = true
}
const isLoadingTips = () => {
if (loading.value) {
$message.warning('请等待当前对话响应完成')
return true
}
return false
}
const handleNewChat = () => {
if (isLoadingTips()) return
addChat()
inputFocus()
}
const handleDeleteChat = async (id) => {
if (isLoadingTips()) return
await removeChat(id)
inputFocus()
}
const handleChangeChat = (id) => {
if (isLoadingTips()) return
changeChat(id)
inputFocus()
}
</script>
<style scoped lang="scss">
.ai_header_wrap {
@@ -480,12 +505,11 @@ const handleSetting = () => {
}
}
}
}
.chat_list_dropdown_menu {
max-height: 70vh;
width: 175px;
width: 310px;
max-width: 375px;
overflow: auto;
.el-dropdown-menu__item {
@@ -499,14 +523,30 @@ const handleSetting = () => {
justify-content: space-between;
.chat_item_info {
flex: 1;
display: flex;
align-content: center;
justify-content: space-between;
.descript {
width: 160px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.created {
flex: 1;
padding: 0 10px;
opacity: 0.6;
font-size: 12px;
}
}
&:hover .chat_item_icon {
display: inline-block;
opacity: 1;
}
.chat_item_icon {
opacity: 0;
flex-shrink: 0;
cursor: pointer;
color: var(--el-text-color-regular);
display: none;
&:hover {
color: var(--el-color-primary);
}
+19 -6
View File
@@ -20,8 +20,8 @@ const defaultChatList = () => {
const defaultCurChat = () => {
return {
id: randomStr(16),
describe: Date.now(),
id: '',
describe: '新对话',
chatList: defaultChatList()
}
}
@@ -34,6 +34,7 @@ export function useAIChat() {
const chatId = computed(() => curChat.value.id)
const chatList = computed(() => curChat.value.chatList)
const aiConfig = computed(() => $store.aiConfig)
const titleGenMedel = computed(() => aiConfig.value.titleGenMedel)
const chatHistory = computed(() => $store.chatHistory)
watch(() => aiConfig.value, (newConfig) => {
@@ -223,10 +224,22 @@ export function useAIChat() {
}
const saveChat = async () => {
await $api.saveChatHistory(curChat.value)
setTimeout(() => {
$store.getChatHistory()
}, 1000)
if (curChat.value.chatList.length === 3) {
const title = await aiService.value.generateTitle(curChat.value.chatList, titleGenMedel.value)
curChat.value.describe = title
}
const { data } = await $api.saveChatHistory(curChat.value)
if (!data.updateChat) ElMessage.error('对话保存失败')
else {
curChat.value.id = data.updateChat.id
curChat.value.createdAt = data.updateChat.createdAt
curChat.value.updatedAt = data.updateChat.updatedAt
// curChat.value = {
// id: data.updateChat._id,
// ...data.updateChat
// }
}
$store.getChatHistory()
}
onUnmounted(() => {
+61
View File
@@ -151,6 +151,67 @@ export class AIChatService {
}
}
async generateTitle(messages, model, options = {}) {
if (!messages || messages.length === 0) {
return '闲聊'
}
const titleMessages = JSON.parse(JSON.stringify(messages)).splice(1) // 移除打招呼的内容
titleMessages.push({
role: 'user',
content: '使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,不要加粗,如果没有主题,请直接返回"闲聊"'
})
const defaultOptions = {
stream: false,
temperature: 0.5,
top_p: 1,
max_tokens: 4000,
presence_penalty: 0,
frequency_penalty: 0
}
// 合并参数
const requestBody = {
...defaultOptions,
...options,
messages: titleMessages,
model
}
try {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${ this.apiKey }`
},
body: JSON.stringify(requestBody)
})
if (!response.ok) {
let errorMessage = `HTTP error! status: ${ response.status }`
try {
const errorData = await response.json()
errorMessage = errorData.error?.message || errorMessage
} catch (e) {
// 如果无法解析错误响应,使用默认错误信息
}
console.error('生成标题失败:', errorMessage)
return '闲聊'
}
const data = await response.json()
if (data.choices && data.choices[0] && data.choices[0].message) {
return data.choices[0].message.content.trim() || '闲聊'
}
return '闲聊'
} catch (error) {
console.error('生成标题错误:', error)
return '闲聊'
}
}
closeConnection() {
if (this.controller) {
this.controller.abort()