mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
chore: use-context-selector
This commit is contained in:
parent
c3b03776ae
commit
f60cd60c33
@ -50,17 +50,6 @@ export function createDetailsWithPaginationUISchema(options: {
|
||||
'x-read-pretty': true,
|
||||
'x-use-component-props': 'useDetailsWithPaginationProps',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-initializer': hideActionInitializer ? undefined : 'aiEmployees:configure',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-initializer': hideActionInitializer ? undefined : 'details:configureActions',
|
||||
|
@ -51,17 +51,6 @@ export function createDetailsUISchema(options: {
|
||||
'x-read-pretty': true,
|
||||
'x-use-component-props': 'useDetailsProps',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-initializer': 'aiEmployees:configure',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-initializer': 'details:configureActions',
|
||||
|
@ -60,18 +60,6 @@ export function createCreateFormBlockUISchema(options: CreateFormBlockUISchemaOp
|
||||
'x-initializer': 'form:configureFields',
|
||||
properties: {},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-initializer': 'aiEmployees:configure',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
layout: 'one-column',
|
||||
style: {
|
||||
marginBottom: 8,
|
||||
},
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-initializer': 'createForm:configureActions',
|
||||
|
@ -17,6 +17,7 @@
|
||||
"@langchain/core": "^0.3.39",
|
||||
"@langchain/deepseek": "^0.0.1",
|
||||
"@langchain/openai": "^0.4.3",
|
||||
"snowflake-id": "^1.1.0"
|
||||
"snowflake-id": "^1.1.0",
|
||||
"use-context-selector": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -19,13 +19,11 @@ import { Sender } from '@ant-design/x';
|
||||
import { ProfileCard } from '../ProfileCard';
|
||||
|
||||
export const AIEmployeeHeader: React.FC = () => {
|
||||
const t = useT();
|
||||
const { token } = useToken();
|
||||
const {
|
||||
service: { loading },
|
||||
aiEmployees,
|
||||
} = useAIEmployeesContext();
|
||||
const { switchAIEmployee } = useChatBoxContext();
|
||||
const switchAIEmployee = useChatBoxContext('switchAIEmployee');
|
||||
return (
|
||||
<Sender.Header closable={false}>
|
||||
<List
|
||||
|
@ -7,19 +7,21 @@
|
||||
* 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 { CloseOutlined, ExpandOutlined, EditOutlined, LayoutOutlined } from '@ant-design/icons';
|
||||
import { useToken } from '@nocobase/client';
|
||||
const { Header, Footer, Sider, Content } = Layout;
|
||||
import { ChatBoxContext } from './ChatBoxContext';
|
||||
import { useChatBoxContext } from './ChatBoxContext';
|
||||
import { Conversations } from './Conversations';
|
||||
import { Messages } from './Messages';
|
||||
import { Sender } from './Sender';
|
||||
import { useAISelectionContext } from '../selector/AISelectorProvider';
|
||||
|
||||
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 [showConversations, setShowConversations] = useState(false);
|
||||
const { selectable } = useAISelectionContext();
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
} from '../types';
|
||||
import { Avatar, GetProp, GetRef, Button, Alert, Space, Popover } from 'antd';
|
||||
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 { useAPIClient, useRequest } from '@nocobase/client';
|
||||
import { AIEmployeesContext } from '../AIEmployeesProvider';
|
||||
@ -31,8 +31,8 @@ import { uid } from '@formily/shared';
|
||||
import { useT } from '../../locale';
|
||||
import { createForm, Form } from '@formily/core';
|
||||
import { ProfileCard } from '../ProfileCard';
|
||||
import _ from 'lodash';
|
||||
import { InfoFormMessage } from './InfoForm';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
|
||||
export const ChatBoxContext = createContext<{
|
||||
setOpen: (open: boolean) => void;
|
||||
@ -213,7 +213,8 @@ export const useSetChatBoxContext = () => {
|
||||
conversations.refresh();
|
||||
},
|
||||
};
|
||||
if (!_.isEmpty(infoForm?.values)) {
|
||||
const hasInfoFormValues = Object.values(infoForm?.values || []).filter(Boolean).length;
|
||||
if (hasInfoFormValues) {
|
||||
sendOptions.infoFormValues = { ...infoForm.values };
|
||||
}
|
||||
setSenderValue('');
|
||||
@ -230,7 +231,8 @@ export const useSetChatBoxContext = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const switchAIEmployee = (aiEmployee: AIEmployee) => {
|
||||
const switchAIEmployee = useCallback(
|
||||
(aiEmployee: AIEmployee) => {
|
||||
const greetingMsg = {
|
||||
key: uid(),
|
||||
role: aiEmployee.username,
|
||||
@ -249,18 +251,21 @@ export const useSetChatBoxContext = () => {
|
||||
addMessage(greetingMsg);
|
||||
setSenderValue('');
|
||||
}
|
||||
};
|
||||
},
|
||||
[currentConversation, infoForm],
|
||||
);
|
||||
|
||||
const startNewConversation = () => {
|
||||
const startNewConversation = useCallback(() => {
|
||||
setCurrentConversation(undefined);
|
||||
setCurrentEmployee(null);
|
||||
setSenderValue('');
|
||||
infoForm.reset();
|
||||
setMessages([]);
|
||||
senderRef.current?.focus();
|
||||
};
|
||||
}, [infoForm]);
|
||||
|
||||
const triggerShortcut = (options: ShortcutOptions) => {
|
||||
const triggerShortcut = useCallback(
|
||||
(options: ShortcutOptions) => {
|
||||
const { aiEmployee, message, infoFormValues, autoSend } = options;
|
||||
updateRole(aiEmployee);
|
||||
if (!open) {
|
||||
@ -294,7 +299,9 @@ export const useSetChatBoxContext = () => {
|
||||
messages: [message],
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
[open, currentConversation, infoForm],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!aiEmployees) {
|
||||
@ -343,6 +350,6 @@ export const useSetChatBoxContext = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useChatBoxContext = () => {
|
||||
return useContext(ChatBoxContext);
|
||||
export const useChatBoxContext = (name: string) => {
|
||||
return useContextSelector(ChatBoxContext, (v) => v[name]);
|
||||
};
|
||||
|
@ -22,13 +22,11 @@ export const Conversations: React.FC = () => {
|
||||
const api = useAPIClient();
|
||||
const { modal, message } = App.useApp();
|
||||
const { token } = useToken();
|
||||
const {
|
||||
conversations: conversationsService,
|
||||
currentConversation,
|
||||
setCurrentConversation,
|
||||
setMessages,
|
||||
startNewConversation,
|
||||
} = useChatBoxContext();
|
||||
const conversationsService = useChatBoxContext('conversations');
|
||||
const currentConversation = useChatBoxContext('currentConversation');
|
||||
const setCurrentConversation = useChatBoxContext('setCurrentConversation');
|
||||
const setMessages = useChatBoxContext('setMessages');
|
||||
const startNewConversation = useChatBoxContext('startNewConversation');
|
||||
const { loading: ConversationsLoading, data: conversationsRes } = conversationsService;
|
||||
const conversations: ConversationsProps['items'] = (conversationsRes || []).map((conversation) => ({
|
||||
key: conversation.sessionId,
|
||||
|
@ -14,7 +14,10 @@ import { useChatBoxContext } from './ChatBoxContext';
|
||||
import { useAISelectionContext } from '../selector/AISelectorProvider';
|
||||
|
||||
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 handleSelect = () => {
|
||||
|
@ -7,7 +7,7 @@
|
||||
* 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 { SchemaComponent } from '@nocobase/client';
|
||||
import { BlockSelector } from '../selector/BlockSelector';
|
||||
@ -87,8 +87,8 @@ export const ReadPrettyInfoForm: React.FC<{
|
||||
|
||||
export const InfoFormMessage: React.FC<{
|
||||
values: any;
|
||||
}> = ({ values }) => {
|
||||
const { currentEmployee } = useChatBoxContext();
|
||||
}> = memo(({ values }) => {
|
||||
const currentEmployee = useChatBoxContext('currentEmployee');
|
||||
const t = useT();
|
||||
const form = useMemo(
|
||||
() =>
|
||||
@ -122,4 +122,4 @@ export const InfoFormMessage: React.FC<{
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -13,7 +13,8 @@ import { useChatBoxContext } from './ChatBoxContext';
|
||||
import { ReactComponent as EmptyIcon } from '../empty-icon.svg';
|
||||
|
||||
export const Messages: React.FC = () => {
|
||||
const { messages, roles } = useChatBoxContext();
|
||||
const messages = useChatBoxContext('messages');
|
||||
const roles = useChatBoxContext('roles');
|
||||
return (
|
||||
<>
|
||||
{messages?.length ? (
|
||||
|
@ -19,17 +19,15 @@ import { SenderFooter } from './SenderFooter';
|
||||
|
||||
export const Sender: React.FC = memo(() => {
|
||||
const t = useT();
|
||||
const {
|
||||
senderValue,
|
||||
setSenderValue,
|
||||
senderPlaceholder,
|
||||
send,
|
||||
currentConversation,
|
||||
currentEmployee,
|
||||
responseLoading,
|
||||
showInfoForm,
|
||||
senderRef,
|
||||
} = useChatBoxContext();
|
||||
const senderValue = useChatBoxContext('senderValue');
|
||||
const setSenderValue = useChatBoxContext('setSenderValue');
|
||||
const senderPlaceholder = useChatBoxContext('senderPlaceholder');
|
||||
const send = useChatBoxContext('send');
|
||||
const currentConversation = useChatBoxContext('currentConversation');
|
||||
const currentEmployee = useChatBoxContext('currentEmployee');
|
||||
const responseLoading = useChatBoxContext('responseLoading');
|
||||
const showInfoForm = useChatBoxContext('showInfoForm');
|
||||
const senderRef = useChatBoxContext('senderRef');
|
||||
return (
|
||||
<AntSender
|
||||
value={senderValue}
|
||||
|
@ -18,7 +18,7 @@ export const SenderFooter: React.FC<{
|
||||
}> = ({ components }) => {
|
||||
const t = useT();
|
||||
const { SendButton, LoadingButton } = components;
|
||||
const { responseLoading: loading } = useChatBoxContext();
|
||||
const { responseLoading: loading } = useChatBoxContext('responseLoading');
|
||||
|
||||
return (
|
||||
<Flex justify="space-between" align="center">
|
||||
|
@ -21,7 +21,10 @@ import { uid } from '@formily/shared';
|
||||
export const SenderHeader: React.FC = () => {
|
||||
const t = useT();
|
||||
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 ? (
|
||||
showInfoForm ? (
|
||||
<Sender.Header
|
||||
|
@ -8,12 +8,12 @@
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { ChatBoxContext } from './ChatBoxContext';
|
||||
import { useChatBoxContext } from './ChatBoxContext';
|
||||
import { avatars } from '../avatars';
|
||||
import { Avatar } from 'antd';
|
||||
|
||||
export const SenderPrefix: React.FC = () => {
|
||||
const { currentEmployee } = useContext(ChatBoxContext);
|
||||
const currentEmployee = useChatBoxContext('currentEmployee');
|
||||
return currentEmployee ? (
|
||||
<Avatar
|
||||
src={avatars(currentEmployee.avatar)}
|
||||
|
@ -67,7 +67,7 @@ export const AIEmployeeButton: React.FC<{
|
||||
};
|
||||
infoForm: any;
|
||||
}> = withDynamicSchemaProps(({ aiEmployee, taskDesc, message, infoForm: infoFormValues, autoSend }) => {
|
||||
const { triggerShortcut } = useChatBoxContext();
|
||||
const triggerShortcut = useChatBoxContext('triggerShortcut');
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { render } = useSchemaToolbarRender(fieldSchema);
|
||||
const variables = useVariables();
|
||||
|
@ -204,7 +204,6 @@ it('should convert ui schema to json schema', async () => {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-initializer': 'aiEmployees:configure',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
layout: 'one-column',
|
||||
|
@ -127,6 +127,8 @@ export default {
|
||||
ctx.res.end();
|
||||
return next();
|
||||
}
|
||||
let stream: any;
|
||||
try {
|
||||
const conversation = await ctx.db.getRepository('aiConversations').findOne({
|
||||
filterByTk: sessionId,
|
||||
});
|
||||
@ -172,6 +174,26 @@ export default {
|
||||
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) => ({
|
||||
@ -196,6 +218,9 @@ export default {
|
||||
if (msg.content.type === 'info') {
|
||||
content = await parseInfoMessage(ctx.db, employee, content);
|
||||
}
|
||||
if (!content) {
|
||||
continue;
|
||||
}
|
||||
userMessages.push({
|
||||
role: 'user',
|
||||
content,
|
||||
@ -206,8 +231,10 @@ export default {
|
||||
role: 'system',
|
||||
content: employee.about,
|
||||
},
|
||||
...history,
|
||||
...userMessages,
|
||||
];
|
||||
console.log(msgs);
|
||||
const Provider = providerOptions.provider;
|
||||
const provider = new Provider({
|
||||
app: ctx.app,
|
||||
@ -217,7 +244,6 @@ export default {
|
||||
messages: msgs,
|
||||
},
|
||||
});
|
||||
let stream: any;
|
||||
try {
|
||||
stream = await provider.stream();
|
||||
} catch (err) {
|
||||
@ -226,6 +252,12 @@ export default {
|
||||
ctx.res.end();
|
||||
return next();
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
let message = '';
|
||||
for await (const chunk of stream) {
|
||||
if (!chunk.content) {
|
||||
@ -239,6 +271,7 @@ export default {
|
||||
ctx.res.end();
|
||||
return next();
|
||||
}
|
||||
console.log(message);
|
||||
await ctx.db.getRepository('aiConversations.messages', sessionId).create({
|
||||
values: {
|
||||
messageId: snowflake.generate(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user