mirror of
https://github.com/chaos-zhu/easynode.git
synced 2026-10-06 15:13:18 +08:00
✨ 添加标题生成
This commit is contained in:
@@ -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 }) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user