feat: 修改chat
This commit is contained in:
@@ -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"
|
||||||
|
)
|
||||||
|
|||||||
@@ -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:",
|
||||||
|
|||||||
79
web/apps/web-antd/src/components/useModelForm/index.ts
Normal file
79
web/apps/web-antd/src/components/useModelForm/index.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 全量更新记录
|
* 全量更新记录
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
4
web/pnpm-lock.yaml
generated
@@ -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==}
|
||||||
|
|||||||
Reference in New Issue
Block a user