feat: 修改chat

This commit is contained in:
XIE7654
2025-11-01 20:35:45 +08:00
parent e5ec2fec56
commit 102974b667
7 changed files with 166 additions and 42 deletions

View File

@@ -1,4 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.serializers import ModelSerializer
from ai.models import ChatConversation from ai.models import ChatConversation
from utils.serializers import CustomModelSerializer from utils.serializers import CustomModelSerializer
@@ -25,6 +27,14 @@ class ChatConversationFilter(filters.FilterSet):
'system_message', 'max_tokens', 'max_contexts'] 'system_message', 'max_tokens', 'max_contexts']
class ConversationsSerializer(ModelSerializer):
"""
AI 聊天对话 列表序列化器
"""
class Meta:
model = ChatConversation
fields = ['id', 'title', 'update_time']
class ChatConversationViewSet(CustomModelViewSet): class ChatConversationViewSet(CustomModelViewSet):
""" """
AI 聊天对话 视图集 AI 聊天对话 视图集
@@ -36,4 +46,22 @@ class ChatConversationViewSet(CustomModelViewSet):
ordering_fields = ['create_time', 'id'] ordering_fields = ['create_time', 'id']
ordering = ['-create_time'] ordering = ['-create_time']
# 移入urls中 def create(self, request, *args, **kwargs):
request.data['max_tokens'] = 2048
request.data['max_contexts'] = 10
if request.data['platform'] == 'tongyi':
model = 'qwen-plus'
else:
model = 'deepseek-chat'
request.data['model'] = model
request.data['temperature'] = 0.7
return super().create(request, *args, **kwargs)
@action(methods=['get'], detail=False)
def conversations(self):
queryset = self.get_queryset().filter(creator=self.request.user.username).values('id', 'title', 'update_time')
serializer = ConversationsSerializer(queryset, many=True)
return self._build_response(
data=serializer.data,
message="ok"
)

View File

@@ -42,6 +42,7 @@
"@vben/utils": "workspace:*", "@vben/utils": "workspace:*",
"@vueuse/core": "catalog:", "@vueuse/core": "catalog:",
"@vben-core/menu-ui": "workspace:*", "@vben-core/menu-ui": "workspace:*",
"@vben-core/popup-ui": "workspace:*",
"ant-design-vue": "catalog:", "ant-design-vue": "catalog:",
"dayjs": "catalog:", "dayjs": "catalog:",
"pinia": "catalog:", "pinia": "catalog:",

View File

@@ -0,0 +1,79 @@
import { computed, ref } from 'vue';
import { $t } from '@vben/locales';
import { useVbenModal } from '@vben-core/popup-ui';
import { useVbenForm } from '#/adapter/form';
import { BaseModel } from '#/models';
export interface BaseEntity {
id?: number;
// 其他公共字段...
}
export function useModelForm<T extends BaseEntity>(options: {
model: BaseModel<T>;
schema: any;
titleKey: string;
}) {
const formData = ref<T>();
const emit = defineEmits(['success']);
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', [$t(options.titleKey)])
: $t('ui.actionTitle.create', [$t(options.titleKey)]);
});
const [Form, formApi] = useVbenForm({
layout: 'horizontal',
schema: options.schema,
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (valid) {
modalApi.lock();
const rawFormData = await formApi.getValues();
const formattedData = rawFormData as Partial<T>; // 关键:类型断言
try {
await (formData.value?.id
? options.model.update(formData.value.id, formattedData)
: options.model.create(formattedData as Omit<T, any>));
await modalApi.close();
emit('success');
} finally {
modalApi.lock(false);
}
}
},
onOpenChange(isOpen) {
if (isOpen) {
const data = modalApi.getData<T>();
if (data) {
formData.value = data;
formApi.setValues(formData.value);
}
}
},
});
function resetForm() {
formApi.resetForm();
formApi.setValues(formData.value || {});
}
return {
Form,
Modal,
formData,
getTitle,
resetForm,
formApi,
modalApi,
};
}

View File

@@ -1,5 +1,9 @@
import type { Recordable } from '@vben/types'; import type { Recordable } from '@vben/types';
import type { ExtendedModalApi } from '@vben-core/popup-ui';
import { message } from 'ant-design-vue';
import { requestClient } from '#/api/request'; import { requestClient } from '#/api/request';
export interface CoreModel { export interface CoreModel {
@@ -18,6 +22,7 @@ export class BaseModel<
UpdateData = Partial<CreateData>, UpdateData = Partial<CreateData>,
> { > {
protected baseUrl: string; protected baseUrl: string;
protected formModalApi: ExtendedModalApi | undefined;
constructor(baseUrl: string) { constructor(baseUrl: string) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
@@ -70,9 +75,6 @@ export class BaseModel<
return requestClient.delete(`${this.baseUrl}${id}/`); return requestClient.delete(`${this.baseUrl}${id}/`);
} }
/**
* 导出数据
*/
/** /**
* 导出数据 * 导出数据
*/ */
@@ -90,6 +92,29 @@ export class BaseModel<
return requestClient.get<Array<T>>(this.baseUrl, { params }); return requestClient.get<Array<T>>(this.baseUrl, { params });
} }
onDelete<T extends { id: number }>(row: T, refreshGrid: () => void) {
const hideLoading = message.loading({
content: '删除中...',
duration: 0,
key: 'action_process_msg',
});
this.delete(row.id)
.then(() => {
message.success({
content: '删除成功',
key: 'action_process_msg',
});
refreshGrid();
})
.catch(() => {
hideLoading();
});
}
onEdit(row: T) {
this.formModalApi?.setData(row).open();
}
/** /**
* 部分更新记录 * 部分更新记录
*/ */
@@ -103,6 +128,10 @@ export class BaseModel<
async retrieve(id: number) { async retrieve(id: number) {
return requestClient.get<T>(`${this.baseUrl}${id}/`); return requestClient.get<T>(`${this.baseUrl}${id}/`);
} }
setFormModalApi(api: ExtendedModalApi) {
this.formModalApi = api;
}
/** /**
* 全量更新记录 * 全量更新记录
*/ */

View File

@@ -8,7 +8,7 @@ import type { AiAIModelApi } from '#/models/ai/ai_model';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons'; import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue'; import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { $t } from '#/locales'; import { $t } from '#/locales';
@@ -17,19 +17,13 @@ import { AiAIModelModel } from '#/models/ai/ai_model';
import { useColumns, useGridFormSchema } from './data'; import { useColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue'; import Form from './modules/form.vue';
const formModel = new AiAIModelModel();
const [FormModal, formModalApi] = useVbenModal({ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form, connectedComponent: Form,
destroyOnClose: true, destroyOnClose: true,
}); });
/** const formModel = new AiAIModelModel();
* 编辑AI 模型 formModel.setFormModalApi(formModalApi);
*/
function onEdit(row: AiAIModelApi.AiAIModel) {
formModalApi.setData(row).open();
}
/** /**
* 创建新AI 模型 * 创建新AI 模型
@@ -38,29 +32,6 @@ function onCreate() {
formModalApi.setData(null).open(); formModalApi.setData(null).open();
} }
/**
* 删除AI 模型
*/
function onDelete(row: AiAIModelApi.AiAIModel) {
const hideLoading = message.loading({
content: '删除AI 模型',
duration: 0,
key: 'action_process_msg',
});
formModel
.delete(row.id)
.then(() => {
message.success({
content: '删除成功',
key: 'action_process_msg',
});
refreshGrid();
})
.catch(() => {
hideLoading();
});
}
/** /**
* 表格操作按钮的回调函数 * 表格操作按钮的回调函数
*/ */
@@ -70,11 +41,11 @@ function onActionClick({
}: OnActionClickParams<AiAIModelApi.AiAIModel>) { }: OnActionClickParams<AiAIModelApi.AiAIModel>) {
switch (code) { switch (code) {
case 'delete': { case 'delete': {
onDelete(row); formModel.onDelete(row, refreshGrid);
break; break;
} }
case 'edit': { case 'edit': {
onEdit(row); formModel.onEdit(row);
break; break;
} }
} }

View File

@@ -13,14 +13,17 @@ import {
Row, Row,
Select, Select,
} from 'ant-design-vue'; } from 'ant-design-vue';
import { import {
createConversation, createConversation,
fetchAIStream, fetchAIStream,
getConversations, getConversations,
getMessages, getMessages,
} from '#/api/ai/chat'; } from '#/api/ai/chat';
import {AiChatConversationModel} from "#/models/ai/chat_conversation";
import {AiChatMessageModel} from "#/models/ai/chat_message";
const aiChatConversation = new AiChatConversationModel();
const aiChatMessageModel = new AiChatMessageModel();
interface Message { interface Message {
id: number; id: number;
type: 'assistant' | 'user'; type: 'assistant' | 'user';
@@ -60,14 +63,21 @@ const filteredChats = computed(() => {
async function selectChat(id: number) { async function selectChat(id: number) {
selectedChatId.value = id; selectedChatId.value = id;
const { data } = await getMessages(id); const data = await aiChatMessageModel.list({
conversation_id: id,
});
console.log('history', data);
messages.value = data; messages.value = data;
nextTick(scrollToBottom); nextTick(scrollToBottom);
} }
async function handleNewChat() { async function handleNewChat() {
// 调用后端新建对话 // 调用后端新建对话
const { data } = await createConversation(selectedPlatform.value!); // const { data } = await createConversation(selectedPlatform.value!);
const data = await aiChatConversation.create({
platform: selectedPlatform.value!,
title: '新对话',
});
// 刷新对话列表 // 刷新对话列表
await fetchConversations(); await fetchConversations();
// 选中新建的对话 // 选中新建的对话
@@ -127,7 +137,9 @@ function scrollToBottom() {
// 获取历史对话 // 获取历史对话
async function fetchConversations() { async function fetchConversations() {
const { data } = await getConversations(); // const { data } = await getConversations();
const data = await aiChatConversation.list();
console.log('history', data);
chatList.value = data.map((item: any) => ({ chatList.value = data.map((item: any) => ({
id: item.id, id: item.id,
title: item.title, title: item.title,

4
web/pnpm-lock.yaml generated
View File

@@ -656,6 +656,9 @@ importers:
'@vben-core/menu-ui': '@vben-core/menu-ui':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/@core/ui-kit/menu-ui version: link:../../packages/@core/ui-kit/menu-ui
'@vben-core/popup-ui':
specifier: workspace:*
version: link:../../packages/@core/ui-kit/popup-ui
'@vben/access': '@vben/access':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/effects/access version: link:../../packages/effects/access
@@ -9994,6 +9997,7 @@ packages:
source-map@0.8.0-beta.0: source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
deprecated: The work that was done in this beta branch won't be included in future versions
sourcemap-codec@1.4.8: sourcemap-codec@1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}