chore: use-context-selector

This commit is contained in:
xilesun 2025-04-10 14:07:42 +08:00
parent c3b03776ae
commit f60cd60c33
18 changed files with 234 additions and 225 deletions

View File

@ -50,17 +50,6 @@ export function createDetailsWithPaginationUISchema(options: {
'x-read-pretty': true, 'x-read-pretty': true,
'x-use-component-props': 'useDetailsWithPaginationProps', 'x-use-component-props': 'useDetailsWithPaginationProps',
properties: { properties: {
[uid()]: {
type: 'void',
'x-initializer': hideActionInitializer ? undefined : 'aiEmployees:configure',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 16,
},
},
properties: {},
},
[uid()]: { [uid()]: {
type: 'void', type: 'void',
'x-initializer': hideActionInitializer ? undefined : 'details:configureActions', 'x-initializer': hideActionInitializer ? undefined : 'details:configureActions',

View File

@ -51,17 +51,6 @@ export function createDetailsUISchema(options: {
'x-read-pretty': true, 'x-read-pretty': true,
'x-use-component-props': 'useDetailsProps', 'x-use-component-props': 'useDetailsProps',
properties: { properties: {
[uid()]: {
type: 'void',
'x-initializer': 'aiEmployees:configure',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 16,
},
},
properties: {},
},
[uid()]: { [uid()]: {
type: 'void', type: 'void',
'x-initializer': 'details:configureActions', 'x-initializer': 'details:configureActions',

View File

@ -60,18 +60,6 @@ export function createCreateFormBlockUISchema(options: CreateFormBlockUISchemaOp
'x-initializer': 'form:configureFields', 'x-initializer': 'form:configureFields',
properties: {}, properties: {},
}, },
[uid()]: {
type: 'void',
'x-initializer': 'aiEmployees:configure',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
style: {
marginBottom: 8,
},
},
properties: {},
},
[uid()]: { [uid()]: {
type: 'void', type: 'void',
'x-initializer': 'createForm:configureActions', 'x-initializer': 'createForm:configureActions',

View File

@ -17,6 +17,7 @@
"@langchain/core": "^0.3.39", "@langchain/core": "^0.3.39",
"@langchain/deepseek": "^0.0.1", "@langchain/deepseek": "^0.0.1",
"@langchain/openai": "^0.4.3", "@langchain/openai": "^0.4.3",
"snowflake-id": "^1.1.0" "snowflake-id": "^1.1.0",
"use-context-selector": "^2.0.0"
} }
} }

View File

@ -19,13 +19,11 @@ import { Sender } from '@ant-design/x';
import { ProfileCard } from '../ProfileCard'; import { ProfileCard } from '../ProfileCard';
export const AIEmployeeHeader: React.FC = () => { export const AIEmployeeHeader: React.FC = () => {
const t = useT();
const { token } = useToken();
const { const {
service: { loading }, service: { loading },
aiEmployees, aiEmployees,
} = useAIEmployeesContext(); } = useAIEmployeesContext();
const { switchAIEmployee } = useChatBoxContext(); const switchAIEmployee = useChatBoxContext('switchAIEmployee');
return ( return (
<Sender.Header closable={false}> <Sender.Header closable={false}>
<List <List

View File

@ -7,19 +7,21 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import React, { useContext, useState } from 'react'; import React, { useState } from 'react';
import { Layout, Card, Button } from 'antd'; import { Layout, Card, Button } from 'antd';
import { CloseOutlined, ExpandOutlined, EditOutlined, LayoutOutlined } from '@ant-design/icons'; import { CloseOutlined, ExpandOutlined, EditOutlined, LayoutOutlined } from '@ant-design/icons';
import { useToken } from '@nocobase/client'; import { useToken } from '@nocobase/client';
const { Header, Footer, Sider, Content } = Layout; const { Header, Footer, Sider, Content } = Layout;
import { ChatBoxContext } from './ChatBoxContext'; import { useChatBoxContext } from './ChatBoxContext';
import { Conversations } from './Conversations'; import { Conversations } from './Conversations';
import { Messages } from './Messages'; import { Messages } from './Messages';
import { Sender } from './Sender'; import { Sender } from './Sender';
import { useAISelectionContext } from '../selector/AISelectorProvider'; import { useAISelectionContext } from '../selector/AISelectorProvider';
export const ChatBox: React.FC = () => { export const ChatBox: React.FC = () => {
const { setOpen, startNewConversation, currentEmployee } = useContext(ChatBoxContext); const setOpen = useChatBoxContext('setOpen');
const startNewConversation = useChatBoxContext('startNewConversation');
const currentEmployee = useChatBoxContext('currentEmployee');
const { token } = useToken(); const { token } = useToken();
const [showConversations, setShowConversations] = useState(false); const [showConversations, setShowConversations] = useState(false);
const { selectable } = useAISelectionContext(); const { selectable } = useAISelectionContext();

View File

@ -19,7 +19,7 @@ import {
} from '../types'; } from '../types';
import { Avatar, GetProp, GetRef, Button, Alert, Space, Popover } from 'antd'; import { Avatar, GetProp, GetRef, Button, Alert, Space, Popover } from 'antd';
import type { Sender } from '@ant-design/x'; import type { Sender } from '@ant-design/x';
import React, { createContext, useContext, useEffect, useState, useRef, useMemo } from 'react'; import React, { useContext, useEffect, useState, useRef, useMemo, useCallback } from 'react';
import { Bubble } from '@ant-design/x'; import { Bubble } from '@ant-design/x';
import { useAPIClient, useRequest } from '@nocobase/client'; import { useAPIClient, useRequest } from '@nocobase/client';
import { AIEmployeesContext } from '../AIEmployeesProvider'; import { AIEmployeesContext } from '../AIEmployeesProvider';
@ -31,8 +31,8 @@ import { uid } from '@formily/shared';
import { useT } from '../../locale'; import { useT } from '../../locale';
import { createForm, Form } from '@formily/core'; import { createForm, Form } from '@formily/core';
import { ProfileCard } from '../ProfileCard'; import { ProfileCard } from '../ProfileCard';
import _ from 'lodash';
import { InfoFormMessage } from './InfoForm'; import { InfoFormMessage } from './InfoForm';
import { createContext, useContextSelector } from 'use-context-selector';
export const ChatBoxContext = createContext<{ export const ChatBoxContext = createContext<{
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
@ -213,7 +213,8 @@ export const useSetChatBoxContext = () => {
conversations.refresh(); conversations.refresh();
}, },
}; };
if (!_.isEmpty(infoForm?.values)) { const hasInfoFormValues = Object.values(infoForm?.values || []).filter(Boolean).length;
if (hasInfoFormValues) {
sendOptions.infoFormValues = { ...infoForm.values }; sendOptions.infoFormValues = { ...infoForm.values };
} }
setSenderValue(''); setSenderValue('');
@ -230,71 +231,77 @@ export const useSetChatBoxContext = () => {
} }
}; };
const switchAIEmployee = (aiEmployee: AIEmployee) => { const switchAIEmployee = useCallback(
const greetingMsg = { (aiEmployee: AIEmployee) => {
key: uid(), const greetingMsg = {
role: aiEmployee.username,
content: {
type: 'greeting',
content: aiEmployee.greeting || t('Default greeting message', { nickname: aiEmployee.nickname }),
},
};
setCurrentEmployee(aiEmployee);
setSenderPlaceholder(aiEmployee.chatSettings?.senderPlaceholder);
infoForm.reset();
senderRef.current?.focus();
if (!currentConversation) {
setMessages([greetingMsg]);
} else {
addMessage(greetingMsg);
setSenderValue('');
}
};
const startNewConversation = () => {
setCurrentConversation(undefined);
setCurrentEmployee(null);
setSenderValue('');
infoForm.reset();
setMessages([]);
senderRef.current?.focus();
};
const triggerShortcut = (options: ShortcutOptions) => {
const { aiEmployee, message, infoFormValues, autoSend } = options;
updateRole(aiEmployee);
if (!open) {
setOpen(true);
}
if (currentConversation) {
setCurrentConversation(undefined);
setMessages([]);
}
setCurrentEmployee(aiEmployee);
if (message && message.type === 'text') {
setSenderValue(message.content);
} else {
setSenderValue('');
}
setMessages([
{
key: uid(), key: uid(),
role: aiEmployee.username, role: aiEmployee.username,
content: { content: {
type: 'greeting', type: 'greeting',
content: aiEmployee.greeting || t('Default greeting message', { nickname: aiEmployee.nickname }), content: aiEmployee.greeting || t('Default greeting message', { nickname: aiEmployee.nickname }),
}, },
}, };
]); setCurrentEmployee(aiEmployee);
setSenderPlaceholder(aiEmployee.chatSettings?.senderPlaceholder);
infoForm.reset();
senderRef.current?.focus();
if (!currentConversation) {
setMessages([greetingMsg]);
} else {
addMessage(greetingMsg);
setSenderValue('');
}
},
[currentConversation, infoForm],
);
const startNewConversation = useCallback(() => {
setCurrentConversation(undefined);
setCurrentEmployee(null);
setSenderValue('');
infoForm.reset();
setMessages([]);
senderRef.current?.focus(); senderRef.current?.focus();
infoForm.setValues(infoFormValues); }, [infoForm]);
if (autoSend) {
send({ const triggerShortcut = useCallback(
aiEmployee, (options: ShortcutOptions) => {
messages: [message], const { aiEmployee, message, infoFormValues, autoSend } = options;
}); updateRole(aiEmployee);
} if (!open) {
}; setOpen(true);
}
if (currentConversation) {
setCurrentConversation(undefined);
setMessages([]);
}
setCurrentEmployee(aiEmployee);
if (message && message.type === 'text') {
setSenderValue(message.content);
} else {
setSenderValue('');
}
setMessages([
{
key: uid(),
role: aiEmployee.username,
content: {
type: 'greeting',
content: aiEmployee.greeting || t('Default greeting message', { nickname: aiEmployee.nickname }),
},
},
]);
senderRef.current?.focus();
infoForm.setValues(infoFormValues);
if (autoSend) {
send({
aiEmployee,
messages: [message],
});
}
},
[open, currentConversation, infoForm],
);
useEffect(() => { useEffect(() => {
if (!aiEmployees) { if (!aiEmployees) {
@ -343,6 +350,6 @@ export const useSetChatBoxContext = () => {
}; };
}; };
export const useChatBoxContext = () => { export const useChatBoxContext = (name: string) => {
return useContext(ChatBoxContext); return useContextSelector(ChatBoxContext, (v) => v[name]);
}; };

View File

@ -22,13 +22,11 @@ export const Conversations: React.FC = () => {
const api = useAPIClient(); const api = useAPIClient();
const { modal, message } = App.useApp(); const { modal, message } = App.useApp();
const { token } = useToken(); const { token } = useToken();
const { const conversationsService = useChatBoxContext('conversations');
conversations: conversationsService, const currentConversation = useChatBoxContext('currentConversation');
currentConversation, const setCurrentConversation = useChatBoxContext('setCurrentConversation');
setCurrentConversation, const setMessages = useChatBoxContext('setMessages');
setMessages, const startNewConversation = useChatBoxContext('startNewConversation');
startNewConversation,
} = useChatBoxContext();
const { loading: ConversationsLoading, data: conversationsRes } = conversationsService; const { loading: ConversationsLoading, data: conversationsRes } = conversationsService;
const conversations: ConversationsProps['items'] = (conversationsRes || []).map((conversation) => ({ const conversations: ConversationsProps['items'] = (conversationsRes || []).map((conversation) => ({
key: conversation.sessionId, key: conversation.sessionId,

View File

@ -14,7 +14,10 @@ import { useChatBoxContext } from './ChatBoxContext';
import { useAISelectionContext } from '../selector/AISelectorProvider'; import { useAISelectionContext } from '../selector/AISelectorProvider';
export const FieldSelector: React.FC = () => { export const FieldSelector: React.FC = () => {
const { currentEmployee, senderValue, setSenderValue, senderRef } = useChatBoxContext(); const currentEmployee = useChatBoxContext('currentEmployee');
const senderValue = useChatBoxContext('senderValue');
const setSenderValue = useChatBoxContext('setSenderValue');
const senderRef = useChatBoxContext('senderRef');
const { startSelect } = useAISelectionContext(); const { startSelect } = useAISelectionContext();
const handleSelect = () => { const handleSelect = () => {

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import React, { useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { AIEmployee } from '../types'; import { AIEmployee } from '../types';
import { SchemaComponent } from '@nocobase/client'; import { SchemaComponent } from '@nocobase/client';
import { BlockSelector } from '../selector/BlockSelector'; import { BlockSelector } from '../selector/BlockSelector';
@ -87,8 +87,8 @@ export const ReadPrettyInfoForm: React.FC<{
export const InfoFormMessage: React.FC<{ export const InfoFormMessage: React.FC<{
values: any; values: any;
}> = ({ values }) => { }> = memo(({ values }) => {
const { currentEmployee } = useChatBoxContext(); const currentEmployee = useChatBoxContext('currentEmployee');
const t = useT(); const t = useT();
const form = useMemo( const form = useMemo(
() => () =>
@ -122,4 +122,4 @@ export const InfoFormMessage: React.FC<{
/> />
</> </>
); );
}; });

View File

@ -13,7 +13,8 @@ import { useChatBoxContext } from './ChatBoxContext';
import { ReactComponent as EmptyIcon } from '../empty-icon.svg'; import { ReactComponent as EmptyIcon } from '../empty-icon.svg';
export const Messages: React.FC = () => { export const Messages: React.FC = () => {
const { messages, roles } = useChatBoxContext(); const messages = useChatBoxContext('messages');
const roles = useChatBoxContext('roles');
return ( return (
<> <>
{messages?.length ? ( {messages?.length ? (

View File

@ -19,17 +19,15 @@ import { SenderFooter } from './SenderFooter';
export const Sender: React.FC = memo(() => { export const Sender: React.FC = memo(() => {
const t = useT(); const t = useT();
const { const senderValue = useChatBoxContext('senderValue');
senderValue, const setSenderValue = useChatBoxContext('setSenderValue');
setSenderValue, const senderPlaceholder = useChatBoxContext('senderPlaceholder');
senderPlaceholder, const send = useChatBoxContext('send');
send, const currentConversation = useChatBoxContext('currentConversation');
currentConversation, const currentEmployee = useChatBoxContext('currentEmployee');
currentEmployee, const responseLoading = useChatBoxContext('responseLoading');
responseLoading, const showInfoForm = useChatBoxContext('showInfoForm');
showInfoForm, const senderRef = useChatBoxContext('senderRef');
senderRef,
} = useChatBoxContext();
return ( return (
<AntSender <AntSender
value={senderValue} value={senderValue}

View File

@ -18,7 +18,7 @@ export const SenderFooter: React.FC<{
}> = ({ components }) => { }> = ({ components }) => {
const t = useT(); const t = useT();
const { SendButton, LoadingButton } = components; const { SendButton, LoadingButton } = components;
const { responseLoading: loading } = useChatBoxContext(); const { responseLoading: loading } = useChatBoxContext('responseLoading');
return ( return (
<Flex justify="space-between" align="center"> <Flex justify="space-between" align="center">

View File

@ -21,7 +21,10 @@ import { uid } from '@formily/shared';
export const SenderHeader: React.FC = () => { export const SenderHeader: React.FC = () => {
const t = useT(); const t = useT();
const { token } = useToken(); const { token } = useToken();
const { currentEmployee, startNewConversation, showInfoForm, infoForm: form } = useChatBoxContext(); const currentEmployee = useChatBoxContext('currentEmployee');
const startNewConversation = useChatBoxContext('startNewConversation');
const showInfoForm = useChatBoxContext('showInfoForm');
const form = useChatBoxContext('infoForm');
return currentEmployee ? ( return currentEmployee ? (
showInfoForm ? ( showInfoForm ? (
<Sender.Header <Sender.Header

View File

@ -8,12 +8,12 @@
*/ */
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { ChatBoxContext } from './ChatBoxContext'; import { useChatBoxContext } from './ChatBoxContext';
import { avatars } from '../avatars'; import { avatars } from '../avatars';
import { Avatar } from 'antd'; import { Avatar } from 'antd';
export const SenderPrefix: React.FC = () => { export const SenderPrefix: React.FC = () => {
const { currentEmployee } = useContext(ChatBoxContext); const currentEmployee = useChatBoxContext('currentEmployee');
return currentEmployee ? ( return currentEmployee ? (
<Avatar <Avatar
src={avatars(currentEmployee.avatar)} src={avatars(currentEmployee.avatar)}

View File

@ -67,7 +67,7 @@ export const AIEmployeeButton: React.FC<{
}; };
infoForm: any; infoForm: any;
}> = withDynamicSchemaProps(({ aiEmployee, taskDesc, message, infoForm: infoFormValues, autoSend }) => { }> = withDynamicSchemaProps(({ aiEmployee, taskDesc, message, infoForm: infoFormValues, autoSend }) => {
const { triggerShortcut } = useChatBoxContext(); const triggerShortcut = useChatBoxContext('triggerShortcut');
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { render } = useSchemaToolbarRender(fieldSchema); const { render } = useSchemaToolbarRender(fieldSchema);
const variables = useVariables(); const variables = useVariables();

View File

@ -204,7 +204,6 @@ it('should convert ui schema to json schema', async () => {
_isJSONSchemaObject: true, _isJSONSchemaObject: true,
version: '2.0', version: '2.0',
type: 'void', type: 'void',
'x-initializer': 'aiEmployees:configure',
'x-component': 'ActionBar', 'x-component': 'ActionBar',
'x-component-props': { 'x-component-props': {
layout: 'one-column', layout: 'one-column',

View File

@ -127,99 +127,131 @@ export default {
ctx.res.end(); ctx.res.end();
return next(); return next();
} }
const conversation = await ctx.db.getRepository('aiConversations').findOne({
filterByTk: sessionId,
});
if (!conversation) {
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'conversation not found' })}\n\n`);
ctx.res.end();
return next();
}
const employee = await ctx.db.getRepository('aiEmployees').findOne({
filter: {
username: aiEmployee,
},
});
if (!employee) {
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'AI employee not found' })}\n\n`);
ctx.res.end();
return next();
}
const modelSettings = employee.modelSettings;
if (!modelSettings?.llmService) {
ctx.log.error('llmService not configured');
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
ctx.res.end();
return next();
}
const service = await ctx.db.getRepository('llmServices').findOne({
filter: {
name: modelSettings.llmService,
},
});
if (!service) {
ctx.log.error('llmService not found');
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
ctx.res.end();
return next();
}
const plugin = ctx.app.pm.get('ai') as PluginAIServer;
const providerOptions = plugin.aiManager.llmProviders.get(service.provider);
if (!providerOptions) {
ctx.log.error('llmService provider not found');
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
ctx.res.end();
return next();
}
try {
await ctx.db.getRepository('aiConversations.messages', sessionId).create({
values: messages.map((message: any) => ({
messageId: snowflake.generate(),
role: message.role,
content: message.content,
})),
});
} catch (err) {
ctx.log.error(err);
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
ctx.res.end();
return next();
}
ctx.status = 200;
const userMessages = [];
for (const msg of messages) {
if (msg.role !== 'user' && msg.role !== 'info') {
continue;
}
let content = msg.content.content;
if (msg.content.type === 'info') {
content = await parseInfoMessage(ctx.db, employee, content);
}
userMessages.push({
role: 'user',
content,
});
}
const msgs = [
{
role: 'system',
content: employee.about,
},
...userMessages,
];
const Provider = providerOptions.provider;
const provider = new Provider({
app: ctx.app,
serviceOptions: service.options,
chatOptions: {
...modelSettings,
messages: msgs,
},
});
let stream: any; let stream: any;
try { try {
stream = await provider.stream(); const conversation = await ctx.db.getRepository('aiConversations').findOne({
filterByTk: sessionId,
});
if (!conversation) {
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'conversation not found' })}\n\n`);
ctx.res.end();
return next();
}
const employee = await ctx.db.getRepository('aiEmployees').findOne({
filter: {
username: aiEmployee,
},
});
if (!employee) {
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'AI employee not found' })}\n\n`);
ctx.res.end();
return next();
}
const modelSettings = employee.modelSettings;
if (!modelSettings?.llmService) {
ctx.log.error('llmService not configured');
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
ctx.res.end();
return next();
}
const service = await ctx.db.getRepository('llmServices').findOne({
filter: {
name: modelSettings.llmService,
},
});
if (!service) {
ctx.log.error('llmService not found');
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
ctx.res.end();
return next();
}
const plugin = ctx.app.pm.get('ai') as PluginAIServer;
const providerOptions = plugin.aiManager.llmProviders.get(service.provider);
if (!providerOptions) {
ctx.log.error('llmService provider not found');
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
ctx.res.end();
return next();
}
const historyMessages = await ctx.db.getRepository('aiConversations.messages', sessionId).find({
sort: ['messageId'],
});
const history = [];
for (const msg of historyMessages) {
let content = msg.content.content;
if (msg.content.type === 'info') {
content = await parseInfoMessage(ctx.db, employee, content);
}
let role = msg.role;
if (role === 'info' || role === 'user') {
role = 'user';
} else {
role = 'ai';
}
history.push({
role,
content,
});
}
try {
await ctx.db.getRepository('aiConversations.messages', sessionId).create({
values: messages.map((message: any) => ({
messageId: snowflake.generate(),
role: message.role,
content: message.content,
})),
});
} catch (err) {
ctx.log.error(err);
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
ctx.res.end();
return next();
}
ctx.status = 200;
const userMessages = [];
for (const msg of messages) {
if (msg.role !== 'user' && msg.role !== 'info') {
continue;
}
let content = msg.content.content;
if (msg.content.type === 'info') {
content = await parseInfoMessage(ctx.db, employee, content);
}
if (!content) {
continue;
}
userMessages.push({
role: 'user',
content,
});
}
const msgs = [
{
role: 'system',
content: employee.about,
},
...history,
...userMessages,
];
console.log(msgs);
const Provider = providerOptions.provider;
const provider = new Provider({
app: ctx.app,
serviceOptions: service.options,
chatOptions: {
...modelSettings,
messages: msgs,
},
});
try {
stream = await provider.stream();
} catch (err) {
ctx.log.error(err);
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
ctx.res.end();
return next();
}
} catch (err) { } catch (err) {
ctx.log.error(err); ctx.log.error(err);
ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`); ctx.res.write(`data: ${JSON.stringify({ type: 'error', body: 'Chat error warning' })}\n\n`);
@ -239,6 +271,7 @@ export default {
ctx.res.end(); ctx.res.end();
return next(); return next();
} }
console.log(message);
await ctx.db.getRepository('aiConversations.messages', sessionId).create({ await ctx.db.getRepository('aiConversations.messages', sessionId).create({
values: { values: {
messageId: snowflake.generate(), messageId: snowflake.generate(),