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.decorators import action
from rest_framework.serializers import ModelSerializer
from ai.models import ChatConversation
from utils.serializers import CustomModelSerializer
@@ -25,6 +27,14 @@ class ChatConversationFilter(filters.FilterSet):
'system_message', 'max_tokens', 'max_contexts']
class ConversationsSerializer(ModelSerializer):
"""
AI 聊天对话 列表序列化器
"""
class Meta:
model = ChatConversation
fields = ['id', 'title', 'update_time']
class ChatConversationViewSet(CustomModelViewSet):
"""
AI 聊天对话 视图集
@@ -36,4 +46,22 @@ class ChatConversationViewSet(CustomModelViewSet):
ordering_fields = ['create_time', 'id']
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:*",
"@vueuse/core": "catalog:",
"@vben-core/menu-ui": "workspace:*",
"@vben-core/popup-ui": "workspace:*",
"ant-design-vue": "catalog:",
"dayjs": "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 { ExtendedModalApi } from '@vben-core/popup-ui';
import { message } from 'ant-design-vue';
import { requestClient } from '#/api/request';
export interface CoreModel {
@@ -18,6 +22,7 @@ export class BaseModel<
UpdateData = Partial<CreateData>,
> {
protected baseUrl: string;
protected formModalApi: ExtendedModalApi | undefined;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
@@ -70,9 +75,6 @@ export class BaseModel<
return requestClient.delete(`${this.baseUrl}${id}/`);
}
/**
* 导出数据
*/
/**
* 导出数据
*/
@@ -90,6 +92,29 @@ export class BaseModel<
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) {
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 { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { $t } from '#/locales';
@@ -17,19 +17,13 @@ import { AiAIModelModel } from '#/models/ai/ai_model';
import { useColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const formModel = new AiAIModelModel();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/**
* 编辑AI 模型
*/
function onEdit(row: AiAIModelApi.AiAIModel) {
formModalApi.setData(row).open();
}
const formModel = new AiAIModelModel();
formModel.setFormModalApi(formModalApi);
/**
* 创建新AI 模型
@@ -38,29 +32,6 @@ function onCreate() {
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>) {
switch (code) {
case 'delete': {
onDelete(row);
formModel.onDelete(row, refreshGrid);
break;
}
case 'edit': {
onEdit(row);
formModel.onEdit(row);
break;
}
}

View File

@@ -13,14 +13,17 @@ import {
Row,
Select,
} from 'ant-design-vue';
import {
createConversation,
fetchAIStream,
getConversations,
getMessages,
} 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 {
id: number;
type: 'assistant' | 'user';
@@ -60,14 +63,21 @@ const filteredChats = computed(() => {
async function selectChat(id: number) {
selectedChatId.value = id;
const { data } = await getMessages(id);
const data = await aiChatMessageModel.list({
conversation_id: id,
});
console.log('history', data);
messages.value = data;
nextTick(scrollToBottom);
}
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();
// 选中新建的对话
@@ -127,7 +137,9 @@ function scrollToBottom() {
// 获取历史对话
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) => ({
id: item.id,
title: item.title,

4
web/pnpm-lock.yaml generated
View File

@@ -656,6 +656,9 @@ importers:
'@vben-core/menu-ui':
specifier: workspace:*
version: link:../../packages/@core/ui-kit/menu-ui
'@vben-core/popup-ui':
specifier: workspace:*
version: link:../../packages/@core/ui-kit/popup-ui
'@vben/access':
specifier: workspace:*
version: link:../../packages/effects/access
@@ -9994,6 +9997,7 @@ packages:
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
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:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}