Commit eb768d9c by archer

chat box

parent cd77d811
...@@ -58,15 +58,15 @@ export const putChatHistory = (data: UpdateHistoryProps) => ...@@ -58,15 +58,15 @@ export const putChatHistory = (data: UpdateHistoryProps) =>
*/ */
export const createShareChat = ( export const createShareChat = (
data: ShareChatEditType & { data: ShareChatEditType & {
modelId: string; appId: string;
} }
) => POST<string>(`/chat/shareChat/create`, data); ) => POST<string>(`/chat/shareChat/create`, data);
/** /**
* get shareChat * get shareChat
*/ */
export const getShareChatList = (modelId: string) => export const getShareChatList = (appId: string) =>
GET<ShareChatSchema[]>(`/chat/shareChat/list?modelId=${modelId}`); GET<ShareChatSchema[]>(`/chat/shareChat/list`, { appId });
/** /**
* delete a shareChat * delete a shareChat
...@@ -76,5 +76,5 @@ export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?i ...@@ -76,5 +76,5 @@ export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?i
/** /**
* 初始化分享聊天 * 初始化分享聊天
*/ */
export const initShareChatInfo = (data: { shareId: string; password: string }) => export const initShareChatInfo = (data: { shareId: string }) =>
GET<InitShareChatResponse>(`/chat/shareChat/init?${Obj2Query(data)}`); GET<InitShareChatResponse>(`/chat/shareChat/init`, data);
import { Props } from '@/pages/api/openapi/v1/chat/completions';
import { sseResponseEventEnum } from '@/constants/chat'; import { sseResponseEventEnum } from '@/constants/chat';
import { getErrText } from '@/utils/tools'; import { getErrText } from '@/utils/tools';
import { parseStreamChunk } from '@/utils/adapt'; import { parseStreamChunk } from '@/utils/adapt';
interface StreamFetchProps { interface StreamFetchProps {
data: Props; url?: string;
data: Record<string, any>;
onMessage: (text: string) => void; onMessage: (text: string) => void;
abortSignal: AbortController; abortSignal: AbortController;
} }
export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) => export const streamFetch = ({
url = '/api/openapi/v1/chat/completions2',
data,
onMessage,
abortSignal
}: StreamFetchProps) =>
new Promise<{ responseText: string; errMsg: string; newChatId: string | null }>( new Promise<{ responseText: string; errMsg: string; newChatId: string | null }>(
async (resolve, reject) => { async (resolve, reject) => {
try { try {
const response = await window.fetch('/api/openapi/v1/chat/completions2', { const response = await window.fetch(url, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
......
import type { ChatPopulate, AppSchema } from '@/types/mongoSchema'; import type { ChatPopulate, AppSchema } from '@/types/mongoSchema';
import type { ChatItemType } from '@/types/chat'; import type { ChatItemType } from '@/types/chat';
import { VariableItemType } from '@/types/app';
export interface InitChatResponse { export interface InitChatResponse {
chatId: string; chatId: string;
...@@ -17,13 +18,13 @@ export interface InitChatResponse { ...@@ -17,13 +18,13 @@ export interface InitChatResponse {
} }
export interface InitShareChatResponse { export interface InitShareChatResponse {
maxContext: number;
userAvatar: string; userAvatar: string;
appId: string; maxContext: number;
model: { app: {
variableModules?: VariableItemType[];
welcomeText?: string;
name: string; name: string;
avatar: string; avatar: string;
intro: string; intro: string;
}; };
chatModel: AppSchema['chat']['chatModel']; // 对话模型名
} }
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1679805221456" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1173" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M267.3 834.6h-96.5c-27.4 0-49.7-22.3-49.7-49.7V115.2c0-27.4 22.3-49.7 49.7-49.7H727c27.4 0 49.7 22.3 49.7 49.7v96.5h-42.6v-96.5c0-3.9-3.2-7.1-7.1-7.1H170.8c-3.9 0-7.1 3.2-7.1 7.1v669.7c0 3.9 3.2 7.1 7.1 7.1h96.5v42.6z" p-id="1174"></path><path d="M851.9 959.5H295.7c-27.4 0-49.7-22.3-49.7-49.7V240.1c0-27.4 22.3-49.7 49.7-49.7h556.2c27.4 0 49.7 22.3 49.7 49.7v669.7c-0.1 27.4-22.3 49.7-49.7 49.7zM295.7 233c-3.9 0-7.1 3.2-7.1 7.1v669.7c0 3.9 3.2 7.1 7.1 7.1h556.2c3.9 0 7.1-3.2 7.1-7.1V240.1c0-3.9-3.2-7.1-7.1-7.1H295.7z" p-id="1175"></path></svg> <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689057990782" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1770" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M878.5 255.1H770V146.7c0-43.6-35.5-79.1-79.1-79.1H145.2c-43.6 0-79.1 35.5-79.1 79.1v545.8c0 43.6 35.5 79.1 79.1 79.1h108.4V880c0 43.6 35.5 79.1 79.1 79.1h545.8c43.6 0 79.1-35.5 79.1-79.1V334.2c-0.1-43.6-35.6-79.1-79.1-79.1zM145.2 707.5c-8.3 0-15.1-6.8-15.1-15.1V146.7c0-8.3 6.8-15.1 15.1-15.1H691c8.3 0 15.1 6.8 15.1 15.1v545.8c0 8.3-6.8 15.1-15.1 15.1H145.2zM893.5 880c0 8.3-6.8 15.1-15.1 15.1H332.7c-8.3 0-15.1-6.8-15.1-15.1V771.5H691c43.6 0 79.1-35.5 79.1-79.1V319.1h108.4c8.3 0 15.1 6.8 15.1 15.1V880z" p-id="1771"></path></svg>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1688993655221" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1709" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M354.7912723 841.95191467l94.44951229 94.4495123c13.67032415 13.67032415 26.71926992 19.88410785 47.22475616 19.88410785 13.67032415 0 33.554432-6.83516208 47.22475614-19.88410785l19.88410786-20.50548622c20.50548622-19.88410785 33.554432-47.22475615 33.554432-73.94402608v-60.27370192c0-6.83516208 6.83516208-26.71926992 13.67032414-33.554432l175.22870044-175.22870045c6.83516208-6.83516208 19.88410785-13.67032415 33.554432-13.67032415h60.27370192c26.71926992 0 60.27370192-13.67032415 73.94402608-33.554432l20.50548622-19.88410785c6.83516208-6.83516208 13.67032415-19.88410785 13.67032415-40.38959408 0-13.67032415-6.83516208-33.554432-19.88410785-47.22475614l-329.95191467-329.95191467c-13.67032415-13.67032415-26.71926992-19.88410785-47.22475615-19.88410786-13.67032415 0-33.554432 6.83516208-47.22475614 19.88410786l-13.67032414 20.50548622c-20.50548622 19.88410785-33.554432 54.05991822-33.554432 73.94402607v60.27370193c0 6.83516208-6.83516208 26.71926992-13.67032416 33.554432L307.56651615 451.1049197c-6.83516208 6.83516208-19.88410785 13.67032415-26.71926993 13.67032415H213.11700385c-26.71926992 0-60.27370192 13.67032415-73.94402607 33.554432l-19.88410786 19.88410785c-19.88410785 20.50548622-19.88410785 60.27370192 0 87.61435022L213.73838222 700.27764622l141.05289008 141.67426845z m-87.61435022-94.4495123l47.22475614 47.22475615-47.22475614-47.22475615z m639.3983431-262.22167229c-6.83516208 6.83516208-20.50548622 13.67032415-33.554432 13.67032414h-60.27370193c-26.71926992 0-60.27370192 13.67032415-73.94402607 33.554432l-175.22870043 175.22870045c-19.88410785 19.88410785-33.554432 47.22475615-33.554432 73.94402608v60.27370192c0 6.83516208-6.83516208 26.71926992-13.67032416 33.554432l-19.88410784 19.88410785-329.95191467-336.78707674 19.88410784-20.50548623c6.83516208 0 19.88410785-6.83516208 26.71926993-6.83516207h60.27370193c26.71926992 0 60.27370192-13.67032415 73.94402607-33.554432l175.22870045-175.22870045c26.71926992-20.50548622 40.38959408-54.05991822 40.38959407-73.94402606V182.04808533c0-6.83516208 6.83516208-26.71926992 13.67032415-33.554432l13.67032415-13.67032415 329.95191466 329.95191467-13.67032415 20.50548623z" p-id="1710"></path><path d="M269.04105718 753.0948077l33.554432 33.554432-164.66526815 164.66526815-33.554432-33.554432c0.62137837 0 164.66526815-164.66526815 164.66526815-164.66526815z" p-id="1711"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1688977884412" class="icon" viewBox="0 0 1682 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12489" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M906.971429 548.571429l234.057142 234.057142c29.257143 29.257143 29.257143 73.142857 0 102.4-29.257143 29.257143-73.142857 29.257143-102.4 0L804.571429 650.971429l-234.057143 234.057142c-29.257143 29.257143-73.142857 29.257143-102.4 0s-29.257143-73.142857 0-102.4l234.057143-234.057142-234.057143-234.057143c-29.257143-29.257143-29.257143-73.142857 0-102.4s73.142857-29.257143 102.4 0L804.571429 446.171429l234.057142-234.057143c29.257143-29.257143 73.142857-29.257143 102.4 0s29.257143 73.142857 0 102.4l-234.057142 234.057143zM256 980.114286c-14.628571 0-36.571429-7.314286-51.2-21.942857C80.457143 841.142857 0 665.6 0 490.057143 0 307.2 73.142857 146.285714 204.8 21.942857c29.257143-29.257143 73.142857-29.257143 102.4 0 29.257143 29.257143 29.257143 73.142857 0 102.4C204.8 219.428571 146.285714 351.085714 146.285714 490.057143c0 131.657143 58.514286 270.628571 160.914286 365.714286 29.257143 29.257143 29.257143 73.142857 0 102.4-14.628571 14.628571-29.257143 21.942857-51.2 21.942857z m1104.457143-7.314286c-21.942857 0-36.571429-7.314286-51.2-21.942857-29.257143-29.257143-29.257143-73.142857 0-102.4 102.4-95.085714 160.914286-226.742857 160.914286-365.714286s-58.514286-263.314286-160.914286-351.085714c-29.257143-29.257143-36.571429-73.142857-7.314286-102.4 29.257143-29.257143 73.142857-36.571429 102.4-7.314286C1536 138.971429 1609.142857 307.2 1609.142857 490.057143c0 182.857143-73.142857 343.771429-204.8 468.114286-7.314286 7.314286-29.257143 14.628571-43.885714 14.628571z" p-id="12490"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1688977803658" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8763" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M512.000557 0c282.779765 0 511.999889 229.220124 511.999888 511.999889 0 271.404463-211.166563 493.478849-478.163374 510.886845l1.068521 1.113043H60.03822a13.356519 13.356519 0 0 1-10.885563-21.12556c37.509557-52.491119 62.241378-105.004499 74.239984-157.517879A509.951889 509.951889 0 0 1 0.000668 511.999889C0.000668 229.220124 229.220792 0 512.000557 0z m0 80.139113C273.497652 80.139113 80.139781 273.496984 80.139781 511.999889c0 101.286935 34.905036 197.008653 97.680674 273.563766l6.366608 7.568694 26.312342 30.675472-8.971129 39.40173a458.2399 458.2399 0 0 1-25.377386 77.623636l-1.335651 3.027477h351.476793l14.335997-0.934956c223.610386-14.580866 399.248609-198.856305 403.166521-423.268082L943.861332 511.999889c0-238.502905-193.357871-431.860776-431.860775-431.860776z m109.078237 205.601347c90.735285 0 163.72866 75.553375 163.72866 168.069528 0 59.102596-30.007646 113.085193-78.358244 143.449013l4.140521-2.715826-122.323452 124.727625a106.896672 106.896672 0 0 1-143.40449 8.281042l-4.051478-3.428173-3.917912-3.673043-123.725886-126.063277-5.943651-4.185043c-40.626078-30.052167-66.003464-77.779461-67.895638-130.070232l-0.133565-6.322086c0-92.516154 72.993375-168.069529 163.7064-168.069528 40.959991 0 79.137374 15.560344 108.365889 42.073034l0.734609 0.667826 5.721042-5.008695a160.834748 160.834748 0 0 1 97.257718-37.620861z m0 80.139113c-30.853559 0-58.879987 17.786431-73.438593 46.035468-14.914779 28.939124-56.275466 28.983646-71.234767 0.044522l-2.270608-4.162782c-15.137388-25.867125-41.917208-41.917208-71.234767-41.917208-45.83512 0-83.567286 39.067818-83.567287 87.930415 0 31.454602 15.760692 59.814944 40.804165 75.553375l2.582261 1.780869a40.069557 40.069557 0 0 1 4.674781 4.095999l125.88519 128.267103a26.713038 26.713038 0 0 0 37.776688-0.356174l125.462233-127.888668a40.069557 40.069557 0 0 1 7.301564-5.854607l4.095999-2.738086c22.66156-16.183649 36.730427-43.141556 36.730427-72.859811 0-48.862598-37.709905-87.930416-83.545025-87.930415z" p-id="8764"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689059818626" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2851" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M370.675057 293.943709c-20.172608-25.93621-17.290806-66.281425 8.645403-86.454032 25.93621-23.054409 66.281425-17.290806 86.454032 8.645403 69.163226 83.572231 103.744839 190.198871 103.744839 299.707312 0 106.62664-34.581613 213.253279-103.744839 296.82551-20.172608 25.93621-60.517823 31.699812-86.454032 8.645403-25.93621-20.172608-28.818011-60.517823-8.645403-86.454032 48.990618-60.517823 74.926828-138.326451 74.926827-219.016881s-25.93621-161.38086-74.926827-221.898683z m288.180107-184.435268c-23.054409-25.93621-20.172608-66.281425 8.645403-89.335833s69.163226-20.172608 92.217634 8.645403c109.508441 135.44465 164.262661 311.234516 164.262661 487.024381 0 172.908064-54.75422 348.69793-164.262661 484.14258-23.054409 28.818011-63.399624 31.699812-92.217634 8.645403-28.818011-20.172608-31.699812-63.399624-8.645403-89.335833 89.335833-112.390242 135.44465-259.362096 135.44465-403.45215 0-146.971855-46.108817-293.943709-135.44465-406.333951zM108.431159 449.560967c-17.290806-25.93621-14.409005-60.517823 8.645403-80.69043 23.054409-17.290806 60.517823-14.409005 77.808629 8.645403 31.699812 37.463414 48.990618 89.335833 48.990618 138.326452s-17.290806 97.981236-48.990618 135.44465c-17.290806 23.054409-54.75422 28.818011-77.808629 8.645403s-25.93621-54.75422-8.645403-77.808629c14.409005-20.172608 23.054409-43.227016 23.054409-66.281424 0-25.93621-8.645403-48.990618-23.054409-66.281425z" p-id="2852"></path></svg>
\ No newline at end of file
...@@ -46,12 +46,19 @@ const map = { ...@@ -46,12 +46,19 @@ const map = {
appLight: require('./icons/light/app.svg').default, appLight: require('./icons/light/app.svg').default,
appFill: require('./icons/fill/app.svg').default, appFill: require('./icons/fill/app.svg').default,
meLight: require('./icons/light/me.svg').default, meLight: require('./icons/light/me.svg').default,
meFill: require('./icons/fill/me.svg').default meFill: require('./icons/fill/me.svg').default,
welcomeText: require('./icons/modules/welcomeText.svg').default,
variable: require('./icons/modules/variable.svg').default,
setTop: require('./icons/light/setTop.svg').default,
voice: require('./icons/voice.svg').default
}; };
export type IconName = keyof typeof map; export type IconName = keyof typeof map;
const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconName } & IconProps) => { const MyIcon = (
{ name, w = 'auto', h = 'auto', ...props }: { name: IconName } & IconProps,
ref: any
) => {
return map[name] ? ( return map[name] ? (
<Icon <Icon
as={map[name]} as={map[name]}
...@@ -65,4 +72,4 @@ const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconName } & ...@@ -65,4 +72,4 @@ const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconName } &
) : null; ) : null;
}; };
export default MyIcon; export default React.forwardRef(MyIcon);
...@@ -14,6 +14,7 @@ const MyTooltip = ({ children, ...props }: TooltipProps) => { ...@@ -14,6 +14,7 @@ const MyTooltip = ({ children, ...props }: TooltipProps) => {
py={2} py={2}
borderRadius={'8px'} borderRadius={'8px'}
whiteSpace={'pre-wrap'} whiteSpace={'pre-wrap'}
shouldWrapChildren
{...props} {...props}
> >
{children} {children}
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Box, Box,
MenuList, MenuList,
MenuItem, MenuItem,
MenuButton,
Button, Button,
useDisclosure, useDisclosure,
useOutsideClick useOutsideClick
...@@ -44,7 +43,13 @@ const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props ...@@ -44,7 +43,13 @@ const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props
return ( return (
<Menu autoSelect={false} isOpen={isOpen} onOpen={onOpen} onClose={onClose}> <Menu autoSelect={false} isOpen={isOpen} onOpen={onOpen} onClose={onClose}>
<Box ref={SelectRef} position={'relative'} onClick={() => (isOpen ? onClose() : onOpen())}> <Box
ref={SelectRef}
position={'relative'}
onClick={() => {
isOpen ? onClose() : onOpen();
}}
>
<Button <Button
ref={ref} ref={ref}
width={width} width={width}
...@@ -65,6 +70,7 @@ const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props ...@@ -65,6 +70,7 @@ const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props
{...props} {...props}
> >
{list.find((item) => item.value === value)?.label || placeholder} {list.find((item) => item.value === value)?.label || placeholder}
<Box flex={1} />
<ChevronDownIcon /> <ChevronDownIcon />
</Button> </Button>
......
...@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; ...@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
import { Box, type BoxProps } from '@chakra-ui/react'; import { Box, type BoxProps } from '@chakra-ui/react';
interface Props extends BoxProps { interface Props extends BoxProps {
children: string; children: React.ReactNode | React.ReactNode[];
colorSchema?: 'blue' | 'green' | 'gray'; colorSchema?: 'blue' | 'green' | 'gray';
} }
...@@ -33,7 +33,7 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => { ...@@ -33,7 +33,7 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
border={'1px solid'} border={'1px solid'}
px={2} px={2}
lineHeight={1} lineHeight={1}
py={'2px'} py={1}
borderRadius={'md'} borderRadius={'md'}
fontSize={'xs'} fontSize={'xs'}
{...theme} {...theme}
......
...@@ -2,12 +2,15 @@ import type { AppItemType } from '@/types/app'; ...@@ -2,12 +2,15 @@ import type { AppItemType } from '@/types/app';
/* app */ /* app */
export enum AppModuleItemTypeEnum { export enum AppModuleItemTypeEnum {
'userGuide' = 'userGuide', // default chat input: userChatInput, history
'initInput' = 'initInput', // default chat input: userChatInput, history 'initInput' = 'initInput', // default chat input: userChatInput, history
'http' = 'http', // send a http request 'http' = 'http', // send a http request
'switch' = 'switch', // one input and two outputs 'switch' = 'switch', // one input and two outputs
'answer' = 'answer' // redirect response 'answer' = 'answer' // redirect response
} }
export enum SystemInputEnum { export enum SystemInputEnum {
'welcomeText' = 'welcomeText',
'variables' = 'variables',
'switch' = 'switch', // a trigger switch 'switch' = 'switch', // a trigger switch
'history' = 'history', 'history' = 'history',
'userChatInput' = 'userChatInput' 'userChatInput' = 'userChatInput'
...@@ -15,6 +18,10 @@ export enum SystemInputEnum { ...@@ -15,6 +18,10 @@ export enum SystemInputEnum {
export enum SpecificInputEnum { export enum SpecificInputEnum {
'answerText' = 'answerText' // answer module text key 'answerText' = 'answerText' // answer module text key
} }
export enum VariableInputEnum {
input = 'input',
select = 'select'
}
// template // template
export const appTemplates: (AppItemType & { avatar: string; intro: string })[] = [ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] = [
......
...@@ -8,6 +8,27 @@ import { ...@@ -8,6 +8,27 @@ import {
Input_Template_UserChatInput Input_Template_UserChatInput
} from './inputTemplate'; } from './inputTemplate';
export const VariableInputModule: AppModuleTemplateItemType = {
logo: '/imgs/module/userGuide.png',
name: '开场引导',
intro: '可以在每个新对话开始前,给用户发送一段开场白,或要求用户填写一些内容作为本轮对话的变量。',
type: AppModuleItemTypeEnum.userGuide,
flowType: FlowModuleTypeEnum.userGuide,
inputs: [
{
key: SystemInputEnum.welcomeText,
type: FlowInputItemTypeEnum.input,
label: '开场白'
},
{
key: SystemInputEnum.variables,
type: FlowInputItemTypeEnum.systemInput,
label: '变量输入',
value: []
}
],
outputs: []
};
export const UserInputModule: AppModuleTemplateItemType = { export const UserInputModule: AppModuleTemplateItemType = {
logo: '/imgs/module/userChatInput.png', logo: '/imgs/module/userChatInput.png',
name: '用户问题', name: '用户问题',
...@@ -311,7 +332,7 @@ export const ClassifyQuestionModule: AppModuleTemplateItemType = { ...@@ -311,7 +332,7 @@ export const ClassifyQuestionModule: AppModuleTemplateItemType = {
export const ModuleTemplates = [ export const ModuleTemplates = [
{ {
label: '输入模块', label: '输入模块',
list: [UserInputModule, HistoryModule] list: [UserInputModule, HistoryModule, VariableInputModule]
}, },
{ {
label: '对话模块', label: '对话模块',
......
export enum FlowInputItemTypeEnum { export enum FlowInputItemTypeEnum {
systemInput = 'systemInput', // history, userChatInput systemInput = 'systemInput', // history, userChatInput, variableInput
input = 'input', input = 'input',
textarea = 'textarea', textarea = 'textarea',
numberInput = 'numberInput', numberInput = 'numberInput',
...@@ -19,6 +19,7 @@ export enum FlowOutputItemTypeEnum { ...@@ -19,6 +19,7 @@ export enum FlowOutputItemTypeEnum {
} }
export enum FlowModuleTypeEnum { export enum FlowModuleTypeEnum {
userGuide = 'userGuide',
questionInputNode = 'questionInput', questionInputNode = 'questionInput',
historyNode = 'historyNode', historyNode = 'historyNode',
chatNode = 'chatNode', chatNode = 'chatNode',
......
...@@ -165,7 +165,8 @@ const Textarea: ComponentStyleConfig = { ...@@ -165,7 +165,8 @@ const Textarea: ComponentStyleConfig = {
borderColor: 'myGray.200', borderColor: 'myGray.200',
_focus: { _focus: {
borderColor: 'myBlue.600', borderColor: 'myBlue.600',
boxShadow: '0px 0px 4px #A8DBFF' boxShadow: '0px 0px 4px #A8DBFF',
bg: 'white'
} }
} }
}, },
......
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { sseErrRes } from '@/service/response';
import { sseResponseEventEnum } from '@/constants/chat';
import { sseResponse } from '@/service/utils/tools';
import { type ChatCompletionRequestMessage } from 'openai';
import { AppModuleItemType } from '@/types/app';
import { dispatchModules } from '../openapi/v1/chat/completions2';
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
export type Props = {
history: MessageItemType[];
prompt: string;
modules: AppModuleItemType[];
variable: Record<string, any>;
};
export type ChatResponseType = {
newChatId: string;
quoteLen?: number;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
res.on('close', () => {
res.end();
});
res.on('error', () => {
console.log('error: ', 'request error');
res.end();
});
let { modules = [], history = [], prompt, variable = {} } = req.body as Props;
try {
if (!history || !modules || !prompt) {
throw new Error('Prams Error');
}
if (!Array.isArray(modules)) {
throw new Error('history is not array');
}
await connectToDatabase();
/* user auth */
const { userId } = await authUser({ req });
/* start process */
const { responseData } = await dispatchModules({
res,
modules: modules,
variable,
params: {
history,
userChatInput: prompt
},
stream: true
});
sseResponse({
res,
event: sseResponseEventEnum.answer,
data: '[DONE]'
});
sseResponse({
res,
event: sseResponseEventEnum.appStreamResponse,
data: JSON.stringify(responseData)
});
res.end();
// bill
} catch (err: any) {
res.status(500);
sseErrRes(res, err);
res.end();
}
}
...@@ -3,33 +3,36 @@ import { jsonRes } from '@/service/response'; ...@@ -3,33 +3,36 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo'; import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authApp, authUser } from '@/service/utils/auth'; import { authApp, authUser } from '@/service/utils/auth';
import type { ShareChatEditType } from '@/types/app'; import type { ShareChatEditType } from '@/types/app';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
/* create a shareChat */ /* create a shareChat */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
const { modelId, name, maxContext, password } = req.body as ShareChatEditType & { const { appId, name, maxContext } = req.body as ShareChatEditType & {
modelId: string; appId: string;
}; };
await connectToDatabase(); await connectToDatabase();
const { userId } = await authUser({ req, authToken: true }); const { userId } = await authUser({ req, authToken: true });
await authApp({ await authApp({
appId: modelId, appId,
userId, userId,
authOwner: false authOwner: false
}); });
const { _id } = await ShareChat.create({ const shareId = nanoid();
await ShareChat.create({
shareId,
userId, userId,
modelId, appId,
name, name,
password,
maxContext maxContext
}); });
jsonRes(res, { jsonRes(res, {
data: _id data: shareId
}); });
} catch (err) { } catch (err) {
jsonRes(res, { jsonRes(res, {
......
...@@ -5,13 +5,14 @@ import type { InitShareChatResponse } from '@/api/response/chat'; ...@@ -5,13 +5,14 @@ import type { InitShareChatResponse } from '@/api/response/chat';
import { authApp } from '@/service/utils/auth'; import { authApp } from '@/service/utils/auth';
import { hashPassword } from '@/service/utils/tools'; import { hashPassword } from '@/service/utils/tools';
import { HUMAN_ICON } from '@/constants/chat'; import { HUMAN_ICON } from '@/constants/chat';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
/* 初始化我的聊天框,需要身份验证 */ /* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
let { shareId, password = '' } = req.query as { let { shareId } = req.query as {
shareId: string; shareId: string;
password: string;
}; };
if (!shareId) { if (!shareId) {
...@@ -21,22 +22,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -21,22 +22,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase(); await connectToDatabase();
// get shareChat // get shareChat
const shareChat = await ShareChat.findById(shareId); const shareChat = await ShareChat.findOne({ shareId });
if (!shareChat) { if (!shareChat) {
throw new Error('分享链接已失效');
}
if (shareChat.password !== hashPassword(password)) {
return jsonRes(res, { return jsonRes(res, {
code: 501, code: 501,
message: '密码不正确' error: '分享链接已失效'
}); });
} }
// 校验使用权限 // 校验使用权限
const { app } = await authApp({ const { app } = await authApp({
appId: shareChat.modelId, appId: shareChat.appId,
userId: String(shareChat.userId), userId: String(shareChat.userId),
authOwner: false authOwner: false
}); });
...@@ -45,15 +42,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -45,15 +42,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
jsonRes<InitShareChatResponse>(res, { jsonRes<InitShareChatResponse>(res, {
data: { data: {
appId: shareChat.modelId,
maxContext: shareChat.maxContext,
userAvatar: user?.avatar || HUMAN_ICON, userAvatar: user?.avatar || HUMAN_ICON,
model: { maxContext:
app.modules
?.find((item) => item.flowType === FlowModuleTypeEnum.historyNode)
?.inputs?.find((item) => item.key === 'maxContext')?.value || 0,
app: {
variableModules: app.modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.variables)?.value,
welcomeText: app.modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
name: app.name, name: app.name,
avatar: app.avatar, avatar: app.avatar,
intro: app.intro intro: app.intro
}, }
chatModel: app.chat.chatModel
} }
}); });
} catch (err) { } catch (err) {
......
...@@ -4,11 +4,11 @@ import { connectToDatabase, ShareChat } from '@/service/mongo'; ...@@ -4,11 +4,11 @@ import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth'; import { authUser } from '@/service/utils/auth';
import { hashPassword } from '@/service/utils/tools'; import { hashPassword } from '@/service/utils/tools';
/* get shareChat list by modelId */ /* get shareChat list by appId */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
const { modelId } = req.query as { const { appId } = req.query as {
modelId: string; appId: string;
}; };
await connectToDatabase(); await connectToDatabase();
...@@ -16,19 +16,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -16,19 +16,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { userId } = await authUser({ req, authToken: true }); const { userId } = await authUser({ req, authToken: true });
const data = await ShareChat.find({ const data = await ShareChat.find({
modelId, appId,
userId userId
}).sort({ }).sort({
_id: -1 _id: -1
}); });
const blankPassword = hashPassword('');
jsonRes(res, { jsonRes(res, {
data: data.map((item) => ({ data: data.map((item) => ({
_id: item._id, _id: item._id,
shareId: item.shareId,
name: item.name, name: item.name,
password: item.password === blankPassword ? '' : '1',
tokens: item.tokens, tokens: item.tokens,
maxContext: item.maxContext, maxContext: item.maxContext,
lastTime: item.lastTime lastTime: item.lastTime
......
...@@ -131,6 +131,7 @@ export async function chatCompletion({ ...@@ -131,6 +131,7 @@ export async function chatCompletion({
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false }); const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
const chatAPI = getOpenAIApi(); const chatAPI = getOpenAIApi();
console.log(adaptMessages);
/* count response max token */ /* count response max token */
const promptsToken = modelToolMap.countTokens({ const promptsToken = modelToolMap.countTokens({
......
...@@ -23,7 +23,6 @@ type FastGptWebChatProps = { ...@@ -23,7 +23,6 @@ type FastGptWebChatProps = {
appId?: string; appId?: string;
}; };
type FastGptShareChatProps = { type FastGptShareChatProps = {
password?: string;
shareId?: string; shareId?: string;
}; };
export type Props = CreateChatCompletionRequest & export type Props = CreateChatCompletionRequest &
...@@ -31,6 +30,7 @@ export type Props = CreateChatCompletionRequest & ...@@ -31,6 +30,7 @@ export type Props = CreateChatCompletionRequest &
FastGptShareChatProps & { FastGptShareChatProps & {
messages: MessageItemType[]; messages: MessageItemType[];
stream?: boolean; stream?: boolean;
variables: Record<string, any>;
}; };
export type ChatResponseType = { export type ChatResponseType = {
newChatId: string; newChatId: string;
...@@ -46,7 +46,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -46,7 +46,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
res.end(); res.end();
}); });
let { chatId, appId, shareId, password = '', stream = false, messages = [] } = req.body as Props; let { chatId, appId, shareId, stream = false, messages = [], variables = {} } = req.body as Props;
try { try {
if (!messages) { if (!messages) {
...@@ -66,8 +66,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -66,8 +66,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
authType authType
} = await (shareId } = await (shareId
? authShareChat({ ? authShareChat({
shareId, shareId
password
}) })
: authUser({ req })); : authUser({ req }));
...@@ -105,6 +104,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -105,6 +104,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { responseData, answerText } = await dispatchModules({ const { responseData, answerText } = await dispatchModules({
res, res,
modules: app.modules, modules: app.modules,
variables,
params: { params: {
history: prompts, history: prompts,
userChatInput: prompt.value userChatInput: prompt.value
...@@ -175,18 +175,20 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -175,18 +175,20 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
} }
}); });
async function dispatchModules({ export async function dispatchModules({
res, res,
modules, modules,
params = {}, params = {},
variables = {},
stream = false stream = false
}: { }: {
res: NextApiResponse; res: NextApiResponse;
modules: AppModuleItemType[]; modules: AppModuleItemType[];
params?: Record<string, any>; params?: Record<string, any>;
variables?: Record<string, any>;
stream?: boolean; stream?: boolean;
}) { }) {
const runningModules = loadModules(modules); const runningModules = loadModules(modules, variables);
let storeData: Record<string, any> = {}; let storeData: Record<string, any> = {};
let responseData: Record<string, any> = {}; let responseData: Record<string, any> = {};
let answerText = ''; let answerText = '';
...@@ -333,7 +335,10 @@ async function dispatchModules({ ...@@ -333,7 +335,10 @@ async function dispatchModules({
}; };
} }
function loadModules(modules: AppModuleItemType[]): RunningModuleItemType[] { function loadModules(
modules: AppModuleItemType[],
variables: Record<string, any>
): RunningModuleItemType[] {
return modules.map((module) => { return modules.map((module) => {
return { return {
moduleId: module.moduleId, moduleId: module.moduleId,
...@@ -341,10 +346,25 @@ function loadModules(modules: AppModuleItemType[]): RunningModuleItemType[] { ...@@ -341,10 +346,25 @@ function loadModules(modules: AppModuleItemType[]): RunningModuleItemType[] {
url: module.url, url: module.url,
inputs: module.inputs inputs: module.inputs
.filter((item) => item.type !== FlowInputItemTypeEnum.target || item.connected) // filter unconnected target input .filter((item) => item.type !== FlowInputItemTypeEnum.target || item.connected) // filter unconnected target input
.map((item) => ({ .map((item) => {
key: item.key, if (typeof item.value !== 'string') {
value: item.value return {
})), key: item.key,
value: item.value
};
}
// variables replace
const replacedVal = item.value.replace(
/{{(.*?)}}/g,
(match, key) => variables[key.trim()] || match
);
return {
key: item.key,
value: replacedVal
};
}),
outputs: module.outputs.map((item) => ({ outputs: module.outputs.map((item) => ({
key: item.key, key: item.key,
answer: item.key === SpecificInputEnum.answerText, answer: item.key === SpecificInputEnum.answerText,
......
...@@ -8,7 +8,7 @@ const APIKeyModal = dynamic(() => import('@/components/APIKeyModal'), { ...@@ -8,7 +8,7 @@ const APIKeyModal = dynamic(() => import('@/components/APIKeyModal'), {
ssr: false ssr: false
}); });
const API = ({ modelId }: { modelId: string }) => { const API = ({ appId }: { appId: string }) => {
const theme = useTheme(); const theme = useTheme();
const { copyData } = useCopyData(); const { copyData } = useCopyData();
const [baseUrl, setBaseUrl] = useState('https://fastgpt.run/api/openapi'); const [baseUrl, setBaseUrl] = useState('https://fastgpt.run/api/openapi');
...@@ -33,9 +33,9 @@ const API = ({ modelId }: { modelId: string }) => { ...@@ -33,9 +33,9 @@ const API = ({ modelId }: { modelId: string }) => {
ml={2} ml={2}
fontWeight={'bold'} fontWeight={'bold'}
cursor={'pointer'} cursor={'pointer'}
onClick={() => copyData(modelId, '已复制 AppId')} onClick={() => copyData(appId, '已复制 AppId')}
> >
{modelId} {appId}
</Box> </Box>
</Box> </Box>
<Flex <Flex
......
...@@ -16,7 +16,7 @@ import type { AppSchema } from '@/types/mongoSchema'; ...@@ -16,7 +16,7 @@ import type { AppSchema } from '@/types/mongoSchema';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
const Settings = ({ modelId }: { modelId: string }) => { const Settings = ({ appId }: { appId: string }) => {
const { toast } = useToast(); const { toast } = useToast();
const router = useRouter(); const router = useRouter();
const { Loading, setIsLoading } = useLoading(); const { Loading, setIsLoading } = useLoading();
...@@ -136,10 +136,10 @@ const Settings = ({ modelId }: { modelId: string }) => { ...@@ -136,10 +136,10 @@ const Settings = ({ modelId }: { modelId: string }) => {
); );
// load model data // load model data
const { isLoading } = useQuery([modelId], () => loadAppDetail(modelId, true), { const { isLoading } = useQuery([appId], () => loadAppDetail(appId, true), {
onSuccess(res) { onSuccess(res) {
res && reset(res); res && reset(res);
modelId && setLastModelId(modelId); appId && setLastModelId(appId);
setRefresh(!refresh); setRefresh(!refresh);
}, },
onError(err: any) { onError(err: any) {
...@@ -240,7 +240,7 @@ const Settings = ({ modelId }: { modelId: string }) => { ...@@ -240,7 +240,7 @@ const Settings = ({ modelId }: { modelId: string }) => {
router.prefetch('/chat'); router.prefetch('/chat');
await saveUpdateModel(); await saveUpdateModel();
} catch (error) {} } catch (error) {}
router.push(`/chat?appId=${modelId}`); router.push(`/chat?appId=${appId}`);
}} }}
> >
对话 对话
......
...@@ -38,7 +38,7 @@ import { defaultShareChat } from '@/constants/model'; ...@@ -38,7 +38,7 @@ import { defaultShareChat } from '@/constants/model';
import type { ShareChatEditType } from '@/types/app'; import type { ShareChatEditType } from '@/types/app';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
const Share = ({ modelId }: { modelId: string }) => { const Share = ({ appId }: { appId: string }) => {
const { toast } = useToast(); const { toast } = useToast();
const { Loading, setIsLoading } = useLoading(); const { Loading, setIsLoading } = useLoading();
const { copyData } = useCopyData(); const { copyData } = useCopyData();
...@@ -63,7 +63,7 @@ const Share = ({ modelId }: { modelId: string }) => { ...@@ -63,7 +63,7 @@ const Share = ({ modelId }: { modelId: string }) => {
isFetching, isFetching,
data: shareChatList = [], data: shareChatList = [],
refetch: refetchShareChatList refetch: refetchShareChatList
} = useQuery(['initShareChatList', modelId], () => getShareChatList(modelId)); } = useQuery(['initShareChatList', appId], () => getShareChatList(appId));
const onclickCreateShareChat = useCallback( const onclickCreateShareChat = useCallback(
async (e: ShareChatEditType) => { async (e: ShareChatEditType) => {
...@@ -71,13 +71,12 @@ const Share = ({ modelId }: { modelId: string }) => { ...@@ -71,13 +71,12 @@ const Share = ({ modelId }: { modelId: string }) => {
setIsLoading(true); setIsLoading(true);
const id = await createShareChat({ const id = await createShareChat({
...e, ...e,
modelId appId
}); });
onCloseCreateShareChat(); onCloseCreateShareChat();
refetchShareChatList(); refetchShareChatList();
const url = `对话地址为:${location.origin}/chat/share?shareId=${id} const url = `对话地址为:${location.origin}/chat/share?shareId=${id}`;
${e.password ? `密码为: ${e.password}` : ''}`;
copyData(url, '已复制分享地址'); copyData(url, '已复制分享地址');
resetShareChat(defaultShareChat); resetShareChat(defaultShareChat);
...@@ -91,8 +90,8 @@ ${e.password ? `密码为: ${e.password}` : ''}`; ...@@ -91,8 +90,8 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
setIsLoading(false); setIsLoading(false);
}, },
[ [
appId,
copyData, copyData,
modelId,
onCloseCreateShareChat, onCloseCreateShareChat,
refetchShareChatList, refetchShareChatList,
resetShareChat, resetShareChat,
...@@ -136,7 +135,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`; ...@@ -136,7 +135,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<Thead> <Thead>
<Tr> <Tr>
<Th>名称</Th> <Th>名称</Th>
<Th>密码</Th>
<Th>最大上下文</Th> <Th>最大上下文</Th>
<Th>tokens消耗</Th> <Th>tokens消耗</Th>
<Th>最后使用时间</Th> <Th>最后使用时间</Th>
...@@ -147,7 +145,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`; ...@@ -147,7 +145,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
{shareChatList.map((item) => ( {shareChatList.map((item) => (
<Tr key={item._id}> <Tr key={item._id}>
<Td>{item.name}</Td> <Td>{item.name}</Td>
<Td>{item.password === '1' ? '已开启' : '未使用'}</Td>
<Td>{item.maxContext}</Td> <Td>{item.maxContext}</Td>
<Td>{formatTokens(item.tokens)}</Td> <Td>{formatTokens(item.tokens)}</Td>
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td> <Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
...@@ -160,7 +157,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`; ...@@ -160,7 +157,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
cursor={'pointer'} cursor={'pointer'}
_hover={{ color: 'myBlue.600' }} _hover={{ color: 'myBlue.600' }}
onClick={() => { onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item._id}`; const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
copyData(url, '已复制分享地址'); copyData(url, '已复制分享地址');
}} }}
/> />
...@@ -216,17 +213,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`; ...@@ -216,17 +213,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
/> />
</Flex> </Flex>
</FormControl> </FormControl>
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 60px'} w={0}>
密码:
</Box>
<Input placeholder={'不设置密码,可直接访问'} {...registerShareChat('password')} />
</Flex>
<Box fontSize={'xs'} ml={'60px'} color={'myGray.600'}>
密码不会再次展示,请记住你的密码
</Box>
</FormControl>
<FormControl mt={9}> <FormControl mt={9}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box flex={'0 0 120px'} w={0}> <Box flex={'0 0 120px'} w={0}>
......
import { AppModuleItemType } from '@/types/app'; import { AppModuleItemType } from '@/types/app';
import { AppSchema } from '@/types/mongoSchema'; import { AppSchema } from '@/types/mongoSchema';
import React, { useState, useEffect, useRef, useMemo } from 'react'; import React, {
import { Box, useOutsideClick, Flex, IconButton } from '@chakra-ui/react'; useMemo,
useCallback,
useRef,
forwardRef,
useImperativeHandle,
ForwardedRef
} from 'react';
import { Box, Flex, IconButton, useOutsideClick } from '@chakra-ui/react';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { useChat } from '@/hooks/useChat'; import { FlowModuleTypeEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
import { streamFetch } from '@/api/fetch';
import MyTooltip from '@/components/MyTooltip';
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
const ChatTest = ({ export type ChatTestComponentRef = {
app, resetChatTest: () => void;
modules, };
onClose
}: { const ChatTest = (
app: AppSchema; {
modules?: AppModuleItemType[]; app,
onClose: () => void; modules = [],
}) => { onClose
const isOpen = useMemo(() => !!modules, [modules]); }: {
app: AppSchema;
modules?: AppModuleItemType[];
onClose: () => void;
},
ref: ForwardedRef<ChatTestComponentRef>
) => {
const BoxRef = useRef(null);
const ChatBoxRef = useRef<ComponentRef>(null);
const isOpen = useMemo(() => modules && modules.length > 0, [modules]);
const variableModules = useMemo(
() =>
modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs.find((item) => item.key === SystemInputEnum.variables)?.value,
[modules]
);
const welcomeText = useMemo(
() =>
modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
[modules]
);
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const historyMaxLen =
modules
?.find((item) => item.flowType === FlowModuleTypeEnum.historyNode)
?.inputs?.find((item) => item.key === 'maxContext')?.value || 0;
const history = messages.slice(-historyMaxLen - 2, -2);
console.log(history, 'history====');
const { ChatBox, ChatInput, ChatBoxParentRef, setChatHistory } = useChat({ // 流请求,获取数据
appId: app._id const { responseText } = await streamFetch({
url: '/api/chat/chatTest',
data: {
history,
prompt: messages[messages.length - 2].content,
modules,
variables
},
onMessage: generatingMessage,
abortSignal: controller
});
return { responseText };
},
[modules]
);
useOutsideClick({
ref: BoxRef,
handler: () => {
onClose();
}
}); });
useImperativeHandle(ref, () => ({
resetChatTest() {
console.log(ChatBoxRef.current, '===');
ChatBoxRef.current?.resetHistory([]);
ChatBoxRef.current?.resetVariables();
}
}));
return ( return (
<Flex <>
zIndex={3} <Box
flexDirection={'column'} zIndex={2}
position={'absolute'} display={isOpen ? 'block' : 'none'}
top={5} position={'fixed'}
right={0} top={0}
h={isOpen ? '95%' : '0'} left={0}
w={isOpen ? '460px' : '0'} bottom={0}
bg={'white'} right={0}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'} />
borderRadius={'md'} <Flex
overflow={'hidden'} ref={BoxRef}
transition={'.2s ease'} zIndex={3}
> flexDirection={'column'}
<Flex py={4} px={5} whiteSpace={'nowrap'}> position={'absolute'}
<Box fontSize={'xl'} fontWeight={'bold'} flex={1}> top={5}
调试预览 right={0}
h={isOpen ? '95%' : '0'}
w={isOpen ? '460px' : '0'}
bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'md'}
overflow={'hidden'}
transition={'.2s ease'}
>
<Flex py={4} px={5} whiteSpace={'nowrap'}>
<Box fontSize={'xl'} fontWeight={'bold'} flex={1}>
调试预览
</Box>
<MyTooltip label={'重置'}>
<IconButton
className="chat"
size={'sm'}
icon={<MyIcon name={'clearLight'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
onClick={(e) => {
e.stopPropagation();
ChatBoxRef.current?.resetHistory([]);
ChatBoxRef.current?.resetVariables();
}}
/>
</MyTooltip>
</Flex>
<Box flex={1}>
<ChatBox
ref={ChatBoxRef}
appAvatar={app.avatar}
variableModules={variableModules}
welcomeText={welcomeText}
onStartChat={startChat}
/>
</Box> </Box>
<IconButton
className="chat"
size={'sm'}
icon={<MyIcon name={'clearLight'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
onClick={(e) => {
e.stopPropagation();
setChatHistory([]);
}}
/>
</Flex> </Flex>
<Box ref={ChatBoxParentRef} flex={1} px={5} overflow={'overlay'}> </>
<ChatBox appAvatar={app.avatar} />
</Box>
<Box px={5}>
<ChatInput />
</Box>
</Flex>
); );
}; };
export default ChatTest; export default React.memo(forwardRef(ChatTest));
...@@ -12,13 +12,7 @@ const NodeKbSearch = ({ ...@@ -12,13 +12,7 @@ const NodeKbSearch = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props } data: { moduleId, inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => { }: NodeProps<FlowModuleItemType>) => {
return ( return (
<NodeCard <NodeCard minW={'400px'} moduleId={moduleId} {...props}>
minW={'400px'}
logo={'/icon/logo.png'}
name={'知识库搜索'}
moduleId={moduleId}
{...props}
>
<Divider text="Input" /> <Divider text="Input" />
<Container> <Container>
<RenderInput <RenderInput
......
...@@ -14,10 +14,10 @@ const ModuleStoreList = ({ ...@@ -14,10 +14,10 @@ const ModuleStoreList = ({
onAddNode: (e: { template: AppModuleTemplateItemType; position: XYPosition }) => void; onAddNode: (e: { template: AppModuleTemplateItemType; position: XYPosition }) => void;
onClose: () => void; onClose: () => void;
}) => { }) => {
const ContextMenuRef = useRef(null); const BoxRef = useRef(null);
useOutsideClick({ useOutsideClick({
ref: ContextMenuRef, ref: BoxRef,
handler: () => { handler: () => {
onClose(); onClose();
} }
...@@ -36,7 +36,7 @@ const ModuleStoreList = ({ ...@@ -36,7 +36,7 @@ const ModuleStoreList = ({
></Box> ></Box>
<Flex <Flex
zIndex={3} zIndex={3}
ref={ContextMenuRef} ref={BoxRef}
flexDirection={'column'} flexDirection={'column'}
position={'absolute'} position={'absolute'}
top={'65px'} top={'65px'}
...@@ -52,7 +52,7 @@ const ModuleStoreList = ({ ...@@ -52,7 +52,7 @@ const ModuleStoreList = ({
userSelect={'none'} userSelect={'none'}
> >
<Box w={'330px'} py={4} fontSize={'xl'} fontWeight={'bold'}> <Box w={'330px'} py={4} fontSize={'xl'} fontWeight={'bold'}>
添加模块 系统模块
</Box> </Box>
<Box w={'330px'} flex={'1 0 0'} overflow={'overlay'}> <Box w={'330px'} flex={'1 0 0'} overflow={'overlay'}>
{ModuleTemplates.map((item) => {ModuleTemplates.map((item) =>
......
...@@ -3,12 +3,15 @@ import { Box, Flex, useTheme } from '@chakra-ui/react'; ...@@ -3,12 +3,15 @@ import { Box, Flex, useTheme } from '@chakra-ui/react';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import type { FlowModuleItemType } from '@/types/flow'; import type { FlowModuleItemType } from '@/types/flow';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
type Props = { type Props = {
children: React.ReactNode | React.ReactNode[] | string; children: React.ReactNode | React.ReactNode[] | string;
logo?: string; logo: string;
name?: string; name: string;
intro?: string; description?: string;
intro: string;
minW?: string | number; minW?: string | number;
moduleId: string; moduleId: string;
onDelNode: FlowModuleItemType['onDelNode']; onDelNode: FlowModuleItemType['onDelNode'];
...@@ -18,6 +21,7 @@ const NodeCard = ({ ...@@ -18,6 +21,7 @@ const NodeCard = ({
children, children,
logo = '/icon/logo.png', logo = '/icon/logo.png',
name = '未知模块', name = '未知模块',
description,
minW = '300px', minW = '300px',
onDelNode, onDelNode,
moduleId moduleId
...@@ -28,9 +32,15 @@ const NodeCard = ({ ...@@ -28,9 +32,15 @@ const NodeCard = ({
<Box minW={minW} bg={'white'} border={theme.borders.md} borderRadius={'md'} boxShadow={'sm'}> <Box minW={minW} bg={'white'} border={theme.borders.md} borderRadius={'md'} boxShadow={'sm'}>
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}> <Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
<Avatar src={logo} borderRadius={'md'} w={'30px'} h={'30px'} /> <Avatar src={logo} borderRadius={'md'} w={'30px'} h={'30px'} />
<Box ml={3} flex={1} fontSize={'lg'} color={'myGray.600'}> <Box ml={3} fontSize={'lg'} color={'myGray.600'}>
{name} {name}
</Box> </Box>
{description && (
<MyTooltip label={description}>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
)}
<Box flex={1} />
<MyIcon <MyIcon
className={'nodrag'} className={'nodrag'}
name="delete" name="delete"
......
...@@ -17,7 +17,7 @@ import MySelect from '@/components/Select'; ...@@ -17,7 +17,7 @@ import MySelect from '@/components/Select';
import MySlider from '@/components/Slider'; import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
const Label = ({ export const Label = ({
required = false, required = false,
children, children,
description description
......
...@@ -28,6 +28,9 @@ import dynamic from 'next/dynamic'; ...@@ -28,6 +28,9 @@ import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import ButtonEdge from './components/modules/ButtonEdge'; import ButtonEdge from './components/modules/ButtonEdge';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import TemplateList from './components/TemplateList';
import ChatTest, { type ChatTestComponentRef } from './components/ChatTest';
const NodeChat = dynamic(() => import('./components/NodeChat'), { const NodeChat = dynamic(() => import('./components/NodeChat'), {
ssr: false ssr: false
}); });
...@@ -46,13 +49,10 @@ const NodeAnswer = dynamic(() => import('./components/NodeAnswer'), { ...@@ -46,13 +49,10 @@ const NodeAnswer = dynamic(() => import('./components/NodeAnswer'), {
const NodeQuestionInput = dynamic(() => import('./components/NodeQuestionInput'), { const NodeQuestionInput = dynamic(() => import('./components/NodeQuestionInput'), {
ssr: false ssr: false
}); });
const TemplateList = dynamic(() => import('./components/TemplateList'), { const NodeCQNode = dynamic(() => import('./components/NodeCQNode'), {
ssr: false
});
const ChatTest = dynamic(() => import('./components/ChatTest'), {
ssr: false ssr: false
}); });
const NodeCQNode = dynamic(() => import('./components/NodeCQNode'), { const NodeUserGuide = dynamic(() => import('./components/NodeUserGuide'), {
ssr: false ssr: false
}); });
...@@ -63,6 +63,7 @@ import { AppModuleItemType, AppModuleTemplateItemType } from '@/types/app'; ...@@ -63,6 +63,7 @@ import { AppModuleItemType, AppModuleTemplateItemType } from '@/types/app';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
const nodeTypes = { const nodeTypes = {
[FlowModuleTypeEnum.userGuide]: NodeUserGuide,
[FlowModuleTypeEnum.questionInputNode]: NodeQuestionInput, [FlowModuleTypeEnum.questionInputNode]: NodeQuestionInput,
[FlowModuleTypeEnum.historyNode]: NodeHistory, [FlowModuleTypeEnum.historyNode]: NodeHistory,
[FlowModuleTypeEnum.chatNode]: NodeChat, [FlowModuleTypeEnum.chatNode]: NodeChat,
...@@ -78,6 +79,7 @@ type Props = { app: AppSchema; onBack: () => void }; ...@@ -78,6 +79,7 @@ type Props = { app: AppSchema; onBack: () => void };
const AppEdit = ({ app, onBack }: Props) => { const AppEdit = ({ app, onBack }: Props) => {
const reactFlowWrapper = useRef<HTMLDivElement>(null); const reactFlowWrapper = useRef<HTMLDivElement>(null);
const ChatTestRef = useRef<ChatTestComponentRef>(null);
const theme = useTheme(); const theme = useTheme();
const { x, y, zoom } = useViewport(); const { x, y, zoom } = useViewport();
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]); const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
...@@ -222,7 +224,10 @@ const AppEdit = ({ app, onBack }: Props) => { ...@@ -222,7 +224,10 @@ const AppEdit = ({ app, onBack }: Props) => {
}); });
}, },
successToast: '保存配置成功', successToast: '保存配置成功',
errorToast: '保存配置异常' errorToast: '保存配置异常',
onSuccess() {
ChatTestRef.current?.resetChatTest();
}
}); });
const initData = useCallback( const initData = useCallback(
...@@ -289,8 +294,6 @@ const AppEdit = ({ app, onBack }: Props) => { ...@@ -289,8 +294,6 @@ const AppEdit = ({ app, onBack }: Props) => {
aria-label={'save'} aria-label={'save'}
variant={'base'} variant={'base'}
onClick={() => { onClick={() => {
// @ts-ignore
onclickSave();
setTestModules(flow2Modules()); setTestModules(flow2Modules());
}} }}
/> />
...@@ -366,7 +369,12 @@ const AppEdit = ({ app, onBack }: Props) => { ...@@ -366,7 +369,12 @@ const AppEdit = ({ app, onBack }: Props) => {
</ReactFlow> </ReactFlow>
<TemplateList isOpen={isOpenTemplate} onAddNode={onAddNode} onClose={onCloseTemplate} /> <TemplateList isOpen={isOpenTemplate} onAddNode={onAddNode} onClose={onCloseTemplate} />
<ChatTest modules={testModules} app={app} onClose={() => setTestModules(undefined)} /> <ChatTest
ref={ChatTestRef}
modules={testModules}
app={app}
onClose={() => setTestModules(undefined)}
/>
</Box> </Box>
</Flex> </Flex>
); );
...@@ -378,4 +386,4 @@ const Flow = (data: Props) => ( ...@@ -378,4 +386,4 @@ const Flow = (data: Props) => (
</ReactFlowProvider> </ReactFlowProvider>
); );
export default Flow; export default React.memo(Flow);
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'; import React, { useEffect, useMemo, useCallback } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react'; import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react';
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
...@@ -155,14 +155,14 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { ...@@ -155,14 +155,14 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
/> />
</Box> </Box>
<Box flex={1}> <Box flex={1}>
{currentTab === TabEnum.settings && <Settings modelId={appId} />} {currentTab === TabEnum.settings && <Settings appId={appId} />}
{currentTab === TabEnum.edit && ( {currentTab === TabEnum.edit && (
<Box position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}> <Box position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
<EditApp app={appDetail} onBack={() => setCurrentTab(TabEnum.settings)} /> <EditApp app={appDetail} onBack={() => setCurrentTab(TabEnum.settings)} />
</Box> </Box>
)} )}
{currentTab === TabEnum.API && <API modelId={appId} />} {currentTab === TabEnum.API && <API appId={appId} />}
{currentTab === TabEnum.share && <Share modelId={appId} />} {currentTab === TabEnum.share && <Share appId={appId} />}
</Box> </Box>
</Box> </Box>
</PageContainer> </PageContainer>
......
import React from 'react';
import { AddIcon } from '@chakra-ui/icons';
import {
Box,
Button,
Flex,
useTheme,
Menu,
MenuButton,
MenuList,
MenuItem
} from '@chakra-ui/react';
import { useRouter } from 'next/router';
import MyIcon from '@/components/Icon';
import type { ShareChatHistoryItemType, ExportChatType } from '@/types/chat';
import { useChatStore } from '@/store/chat';
import { useGlobalStore } from '@/store/global';
import Avatar from '@/components/Avatar';
const ChatHistorySlider = ({
appName,
appAvatar,
history,
activeHistoryId,
onChangeChat,
onDelHistory,
onCloseSlider
}: {
appName: string;
appAvatar: string;
history: {
id: string;
title: string;
}[];
activeHistoryId: string;
onChangeChat: (historyId?: string) => void;
onDelHistory: (historyId: string) => void;
onCloseSlider: () => void;
}) => {
const router = useRouter();
const theme = useTheme();
const { isPc } = useGlobalStore();
return (
<Flex
position={'relative'}
flexDirection={'column'}
w={'100%'}
h={'100%'}
bg={'white'}
px={[2, 5]}
borderRight={['', theme.borders.base]}
>
{isPc && (
<Flex pt={5} pb={2} alignItems={'center'} whiteSpace={'nowrap'}>
<Avatar src={appAvatar} />
<Box ml={2} fontWeight={'bold'} className={'textEllipsis'}>
{appName}
</Box>
</Flex>
)}
{/* 新对话 */}
<Box w={'100%'} h={'36px'} my={5}>
<Button
variant={'base'}
w={'100%'}
h={'100%'}
color={'myBlue.700'}
borderRadius={'xl'}
leftIcon={<MyIcon name={'edit'} w={'16px'} />}
overflow={'hidden'}
onClick={() => onChangeChat()}
>
新对话
</Button>
</Box>
{/* chat history */}
<Box flex={'1 0 0'} h={0} overflow={'overlay'}>
{history.map((item) => (
<Flex
position={'relative'}
key={item.id}
alignItems={'center'}
py={3}
px={4}
cursor={'pointer'}
userSelect={'none'}
borderRadius={'lg'}
mb={2}
_hover={{
bg: 'myGray.100',
'& .more': {
display: 'block'
}
}}
{...(item.id === activeHistoryId
? {
backgroundColor: 'myBlue.100 !important',
color: 'myBlue.700'
}
: {
onClick: () => {
onChangeChat(item.id);
}
})}
>
<MyIcon name={item.id === activeHistoryId ? 'chatFill' : 'chatLight'} w={'16px'} />
<Box flex={'1 0 0'} ml={3} className="textEllipsis">
{item.title}
</Box>
<Box className="more" display={['block', 'none']}>
<Menu autoSelect={false} isLazy offset={[0, 5]}>
<MenuButton
_hover={{ bg: 'white' }}
cursor={'pointer'}
borderRadius={'md'}
onClick={(e) => {
e.stopPropagation();
}}
>
<MyIcon name={'more'} w={'14px'} p={1} />
</MenuButton>
<MenuList color={'myGray.700'} minW={`90px !important`}>
<MenuItem>
<MyIcon mr={2} name={'setTop'} w={'16px'}></MyIcon>
置顶
</MenuItem>
<MenuItem
_hover={{ color: 'red.500' }}
onClick={(e) => {
e.stopPropagation();
onDelHistory(item.id);
if (item.id === activeHistoryId) {
onChangeChat();
}
}}
>
<MyIcon mr={2} name={'delete'} w={'16px'}></MyIcon>
删除
</MenuItem>
</MenuList>
</Menu>
</Box>
</Flex>
))}
{history.length === 0 && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'30vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
还没有聊天记录
</Box>
</Flex>
)}
</Box>
</Flex>
);
};
export default ChatHistorySlider;
import React, { useCallback, useRef, useState } from 'react';
import type { MouseEvent } from 'react';
import { AddIcon } from '@chakra-ui/icons';
import {
Box,
Button,
Flex,
useTheme,
Menu,
MenuList,
MenuItem,
useOutsideClick
} from '@chakra-ui/react';
import { ChatIcon } from '@chakra-ui/icons';
import { useRouter } from 'next/router';
import { formatTimeToChatTime } from '@/utils/tools';
import MyIcon from '@/components/Icon';
import type { ShareChatHistoryItemType, ExportChatType } from '@/types/chat';
import { useChatStore } from '@/store/chat';
import { useGlobalStore } from '@/store/global';
import styles from '../index.module.scss';
const PcSliderBar = ({
onclickDelHistory,
onclickExportChat,
onCloseSlider
}: {
onclickDelHistory: (historyId: string) => void;
onclickExportChat: (type: ExportChatType) => void;
onCloseSlider: () => void;
}) => {
const router = useRouter();
const { shareId = '', historyId = '' } = router.query as {
shareId: string;
historyId: string;
};
const theme = useTheme();
const { isPc } = useGlobalStore();
const ContextMenuRef = useRef(null);
const onclickContext = useRef(false);
const [contextMenuData, setContextMenuData] = useState<{
left: number;
top: number;
history: ShareChatHistoryItemType;
}>();
const { shareChatHistory } = useChatStore();
// close contextMenu
useOutsideClick({
ref: ContextMenuRef,
handler: () => {
setTimeout(() => {
if (contextMenuData && !onclickContext.current) {
setContextMenuData(undefined);
}
}, 10);
setTimeout(() => {
onclickContext.current = false;
}, 10);
}
});
const onclickContextMenu = useCallback(
(e: MouseEvent<HTMLDivElement>, history: ShareChatHistoryItemType) => {
e.preventDefault(); // 阻止默认右键菜单
if (!isPc) return;
onclickContext.current = true;
setContextMenuData({
left: e.clientX,
top: e.clientY,
history
});
},
[isPc]
);
const replaceChatPage = useCallback(
({ hId = '', shareId }: { hId?: string; shareId: string }) => {
if (hId === historyId) return;
router.replace(`/chat/share?shareId=${shareId}&historyId=${hId}`);
!isPc && onCloseSlider();
},
[historyId, isPc, onCloseSlider, router]
);
return (
<Flex
position={'relative'}
flexDirection={'column'}
w={'100%'}
h={'100%'}
bg={'white'}
borderRight={['', theme.borders.base]}
>
{/* 新对话 */}
<Box
className={styles.newChat}
zIndex={1000}
w={'90%'}
h={'40px'}
my={5}
mx={'auto'}
position={'relative'}
>
<Button
variant={'base'}
w={'100%'}
h={'100%'}
leftIcon={<AddIcon />}
onClick={() => replaceChatPage({ shareId })}
>
新对话
</Button>
</Box>
{/* chat history */}
<Box flex={'1 0 0'} h={0} overflow={'overlay'}>
{shareChatHistory.map((item) => (
<Flex
position={'relative'}
key={item._id}
alignItems={'center'}
py={3}
pr={[0, 3]}
pl={[6, 3]}
cursor={'pointer'}
transition={'background-color .2s ease-in'}
borderLeft={['none', '5px solid transparent']}
userSelect={'none'}
_hover={{
backgroundColor: ['', '#dee0e3']
}}
{...(item._id === historyId
? {
backgroundColor: '#eff0f1',
borderLeftColor: 'myBlue.600 !important'
}
: {})}
onClick={() => replaceChatPage({ hId: item._id, shareId: item.shareId })}
onContextMenu={(e) => onclickContextMenu(e, item)}
>
<ChatIcon fontSize={'16px'} color={'myGray.500'} />
<Box flex={'1 0 0'} w={0} ml={3}>
<Flex alignItems={'center'}>
<Box flex={'1 0 0'} w={0} className="textEllipsis" color={'myGray.1000'}>
{item.title}
</Box>
<Box color={'myGray.400'} fontSize={'sm'}>
{formatTimeToChatTime(item.updateTime)}
</Box>
</Flex>
<Box className="textEllipsis" mt={1} fontSize={'sm'} color={'myGray.500'}>
{item.latestChat || '……'}
</Box>
</Box>
{/* phone quick delete */}
{!isPc && (
<MyIcon
px={3}
name={'delete'}
w={'16px'}
onClickCapture={(e) => {
e.stopPropagation();
onclickDelHistory(item._id);
item._id === historyId && replaceChatPage({ shareId: item.shareId });
}}
/>
)}
</Flex>
))}
{shareChatHistory.length === 0 && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'30vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
还没有聊天记录
</Box>
</Flex>
)}
</Box>
{/* context menu */}
{contextMenuData && (
<Box zIndex={10} position={'fixed'} top={contextMenuData.top} left={contextMenuData.left}>
<Box ref={ContextMenuRef}></Box>
<Menu isOpen>
<MenuList>
<MenuItem
onClick={() => {
onclickDelHistory(contextMenuData.history._id);
contextMenuData.history._id === historyId && replaceChatPage({ shareId });
}}
>
删除记录
</MenuItem>
<MenuItem onClick={() => onclickExportChat('html')}>导出HTML格式</MenuItem>
<MenuItem onClick={() => onclickExportChat('pdf')}>导出PDF格式</MenuItem>
<MenuItem onClick={() => onclickExportChat('md')}>导出Markdown格式</MenuItem>
</MenuList>
</Menu>
</Box>
)}
</Flex>
);
};
export default PcSliderBar;
import { Schema, model, models, Model } from 'mongoose'; import { Schema, model, models, Model } from 'mongoose';
import { ShareChatSchema as ShareChatSchemaType } from '@/types/mongoSchema'; import { ShareChatSchema as ShareChatSchemaType } from '@/types/mongoSchema';
import { hashPassword } from '@/service/utils/tools';
const ShareChatSchema = new Schema({ const ShareChatSchema = new Schema({
shareId: {
type: String,
required: true
},
userId: { userId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'user', ref: 'user',
required: true required: true
}, },
modelId: { appId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'model', ref: 'model',
required: true required: true
...@@ -17,10 +20,6 @@ const ShareChatSchema = new Schema({ ...@@ -17,10 +20,6 @@ const ShareChatSchema = new Schema({
type: String, type: String,
required: true required: true
}, },
password: {
type: String,
set: (val: string) => hashPassword(val)
},
tokens: { tokens: {
type: Number, type: Number,
default: 0 default: 0
......
...@@ -317,30 +317,17 @@ export const authChat = async ({ ...@@ -317,30 +317,17 @@ export const authChat = async ({
showModelDetail showModelDetail
}; };
}; };
export const authShareChat = async ({ export const authShareChat = async ({ shareId }: { shareId: string }) => {
shareId,
password
}: {
shareId: string;
password: string;
}) => {
// get shareChat // get shareChat
const shareChat = await ShareChat.findById(shareId); const shareChat = await ShareChat.findOne({ shareId });
if (!shareChat) { if (!shareChat) {
return Promise.reject('分享链接已失效'); return Promise.reject('分享链接已失效');
} }
if (shareChat.password !== hashPassword(password)) {
return Promise.reject({
code: 501,
message: '密码不正确'
});
}
return { return {
userId: String(shareChat.userId), userId: String(shareChat.userId),
appId: String(shareChat.modelId), appId: String(shareChat.appId),
authType: 'token' as AuthType authType: 'token' as AuthType
}; };
}; };
...@@ -3,13 +3,7 @@ import { devtools, persist } from 'zustand/middleware'; ...@@ -3,13 +3,7 @@ import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import { OpenAiChatEnum } from '@/constants/model'; import { OpenAiChatEnum } from '@/constants/model';
import { import { ChatSiteItemType, HistoryItemType, ChatType } from '@/types/chat';
ChatSiteItemType,
HistoryItemType,
ShareChatHistoryItemType,
ChatType,
ShareChatType
} from '@/types/chat';
import { getChatHistory } from '@/api/chat'; import { getChatHistory } from '@/api/chat';
import { HUMAN_ICON } from '@/constants/chat'; import { HUMAN_ICON } from '@/constants/chat';
...@@ -32,16 +26,6 @@ type State = { ...@@ -32,16 +26,6 @@ type State = {
setLastChatModelId: (id: string) => void; setLastChatModelId: (id: string) => void;
lastChatId: string; lastChatId: string;
setLastChatId: (id: string) => void; setLastChatId: (id: string) => void;
shareChatData: ShareChatType;
setShareChatData: (e?: ShareChatType | ((e: ShareChatType) => ShareChatType)) => void;
password: string;
setPassword: (val: string) => void;
shareChatHistory: ShareChatHistoryItemType[];
setShareChatHistory: (e: SetShareChatHistoryItem) => void;
delShareHistoryById: (historyId: string) => void;
delShareChatHistoryItemById: (historyId: string, index: number) => void;
delShareChatHistory: (shareId?: string) => void;
}; };
const defaultChatData: ChatType = { const defaultChatData: ChatType = {
...@@ -56,18 +40,6 @@ const defaultChatData: ChatType = { ...@@ -56,18 +40,6 @@ const defaultChatData: ChatType = {
chatModel: OpenAiChatEnum.GPT3516k, chatModel: OpenAiChatEnum.GPT3516k,
history: [] history: []
}; };
const defaultShareChatData: ShareChatType = {
appId: '',
maxContext: 5,
userAvatar: HUMAN_ICON,
model: {
name: '',
avatar: '/icon/logo.png',
intro: ''
},
chatModel: OpenAiChatEnum.GPT3516k,
history: []
};
export const useChatStore = create<State>()( export const useChatStore = create<State>()(
devtools( devtools(
...@@ -114,114 +86,13 @@ export const useChatStore = create<State>()( ...@@ -114,114 +86,13 @@ export const useChatStore = create<State>()(
state.chatData = e; state.chatData = e;
}); });
} }
},
shareChatData: defaultShareChatData,
setShareChatData(
e: ShareChatType | ((e: ShareChatType) => ShareChatType) = defaultShareChatData
) {
if (typeof e === 'function') {
set((state) => {
state.shareChatData = e(state.shareChatData);
});
} else {
set((state) => {
state.shareChatData = e;
});
}
},
password: '',
setPassword(val: string) {
set((state) => {
state.password = val;
});
},
shareChatHistory: [],
setShareChatHistory({
historyId,
shareId,
title,
latestChat,
chats = []
}: SetShareChatHistoryItem) {
set((state) => {
const history = state.shareChatHistory.find((item) => item._id === historyId);
let historyList: ShareChatHistoryItemType[] = [];
if (history) {
historyList = state.shareChatHistory.map((item) =>
item._id === historyId
? {
...item,
title,
latestChat,
updateTime: new Date(),
chats
}
: item
);
} else {
historyList = [
...state.shareChatHistory,
{
_id: historyId,
shareId,
title,
latestChat,
updateTime: new Date(),
chats
}
];
}
// @ts-ignore
historyList.sort((a, b) => new Date(b.updateTime) - new Date(a.updateTime));
state.shareChatHistory = historyList.slice(0, 30);
});
},
delShareHistoryById(historyId: string) {
set((state) => {
state.shareChatHistory = state.shareChatHistory.filter(
(item) => item._id !== historyId
);
});
},
delShareChatHistoryItemById(historyId: string, index: number) {
set((state) => {
// update history store
const newHistoryList = state.shareChatHistory.map((item) =>
item._id === historyId
? {
...item,
chats: [...item.chats.slice(0, index), ...item.chats.slice(index + 1)]
}
: item
);
state.shareChatHistory = newHistoryList;
// update chatData
state.shareChatData.history =
newHistoryList.find((item) => item._id === historyId)?.chats || [];
});
},
delShareChatHistory(shareId?: string) {
set((state) => {
if (shareId) {
state.shareChatHistory = state.shareChatHistory.filter(
(item) => item.shareId !== shareId
);
} else {
state.shareChatHistory = [];
}
});
} }
})), })),
{ {
name: 'chatStore', name: 'chatStore',
partialize: (state) => ({ partialize: (state) => ({
lastChatModelId: state.lastChatModelId, lastChatModelId: state.lastChatModelId,
lastChatId: state.lastChatId, lastChatId: state.lastChatId
password: state.password,
shareChatHistory: state.shareChatHistory
}) })
} }
) )
......
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import type { ChatSiteItemType, ShareChatHistoryItemType, ShareChatType } from '@/types/chat';
import { HUMAN_ICON } from '@/constants/chat';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
type State = {
shareChatData: ShareChatType;
setShareChatData: (e: ShareChatType | ((e: ShareChatType) => ShareChatType)) => void;
shareChatHistory: ShareChatHistoryItemType[];
saveChatResponse: (e: {
historyId: string;
prompts: ChatSiteItemType[];
variables: Record<string, any>;
shareId: string;
}) => { newChatId: string };
delOneShareHistoryByHistoryId: (historyId: string) => void;
delShareChatHistoryItemById: (e: { historyId: string; index: number }) => void;
delManyShareChatHistoryByShareId: (shareId?: string) => void;
};
export const defaultHistory: ShareChatHistoryItemType = {
_id: `${Date.now()}`,
updateTime: new Date(),
title: '新对话',
shareId: '',
chats: []
};
const defaultShareChatData: ShareChatType = {
maxContext: 5,
userAvatar: HUMAN_ICON,
app: {
name: '',
avatar: '/icon/logo.png',
intro: ''
},
history: defaultHistory
};
export const useShareChatStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
shareChatData: defaultShareChatData,
setShareChatData(e) {
const val = (() => {
if (typeof e === 'function') {
return e(get().shareChatData);
} else {
return e;
}
})();
set((state) => {
state.shareChatData = val;
// update history
state.shareChatHistory = state.shareChatHistory.map((item) =>
item._id === val.history._id ? val.history : item
);
});
},
shareChatHistory: [],
saveChatResponse({ historyId, prompts, variables, shareId }) {
const history = get().shareChatHistory.find((item) => item._id === historyId);
const newChatId = history ? '' : nanoid();
const historyList = (() => {
if (history) {
return get().shareChatHistory.map((item) =>
item._id === historyId
? {
...item,
title: prompts[prompts.length - 2]?.value,
updateTime: new Date(),
chats: prompts,
variables
}
: item
);
}
return get().shareChatHistory.concat({
_id: newChatId,
shareId,
title: prompts[prompts.length - 2]?.value,
updateTime: new Date(),
chats: prompts,
variables
});
})();
// @ts-ignore
historyList.sort((a, b) => new Date(b.updateTime) - new Date(a.updateTime));
set((state) => {
state.shareChatHistory = historyList.slice(0, 100);
});
return {
newChatId
};
},
delOneShareHistoryByHistoryId(historyId: string) {
set((state) => {
state.shareChatHistory = state.shareChatHistory.filter(
(item) => item._id !== historyId
);
});
},
delShareChatHistoryItemById({ historyId, index }) {
set((state) => {
// update history store
const newHistoryList = state.shareChatHistory.map((item) =>
item._id === historyId
? {
...item,
chats: [...item.chats.slice(0, index), ...item.chats.slice(index + 1)]
}
: item
);
state.shareChatHistory = newHistoryList;
});
},
delManyShareChatHistoryByShareId(shareId?: string) {
set((state) => {
if (shareId) {
state.shareChatHistory = state.shareChatHistory.filter(
(item) => item.shareId !== shareId
);
} else {
state.shareChatHistory = [];
}
});
}
})),
{
name: 'shareChatStore',
partialize: (state) => ({
shareChatHistory: state.shareChatHistory
})
}
)
)
);
import { FlowModuleTypeEnum } from '@/constants/flow'; import { FlowModuleTypeEnum } from '@/constants/flow';
import { XYPosition } from 'reactflow'; import { XYPosition } from 'reactflow';
import { AppModuleItemTypeEnum, ModulesInputItemTypeEnum } from '../constants/app'; import {
AppModuleItemTypeEnum,
ModulesInputItemTypeEnum,
VariableInputEnum
} from '../constants/app';
import type { FlowInputItemType, FlowOutputItemType } from './flow'; import type { FlowInputItemType, FlowOutputItemType } from './flow';
import type { AppSchema, kbSchema } from './mongoSchema'; import type { AppSchema, kbSchema } from './mongoSchema';
import { ChatModelType } from '@/constants/model'; import { ChatModelType } from '@/constants/model';
...@@ -33,7 +37,6 @@ export interface ShareAppItem { ...@@ -33,7 +37,6 @@ export interface ShareAppItem {
export type ShareChatEditType = { export type ShareChatEditType = {
name: string; name: string;
password: string;
maxContext: number; maxContext: number;
}; };
...@@ -44,11 +47,22 @@ export type ClassifyQuestionAgentItemType = { ...@@ -44,11 +47,22 @@ export type ClassifyQuestionAgentItemType = {
key: string; key: string;
}; };
export type VariableItemType = {
id: string;
key: string;
label: string;
type: `${VariableInputEnum}`;
required: boolean;
maxLen: number;
enums: { value: string }[];
};
/* app module */ /* app module */
export type AppModuleTemplateItemType = { export type AppModuleTemplateItemType = {
logo?: string; logo: string;
name?: string; name: string;
intro?: string; description?: string;
intro: string;
flowType: `${FlowModuleTypeEnum}`; flowType: `${FlowModuleTypeEnum}`;
type: `${AppModuleItemTypeEnum}`; type: `${AppModuleItemTypeEnum}`;
......
...@@ -22,24 +22,22 @@ export interface ChatType extends InitChatResponse { ...@@ -22,24 +22,22 @@ export interface ChatType extends InitChatResponse {
history: ChatSiteItemType[]; history: ChatSiteItemType[];
} }
export interface ShareChatType extends InitShareChatResponse {
history: ChatSiteItemType[];
}
export type HistoryItemType = { export type HistoryItemType = {
_id: string; _id: string;
updateTime: Date; updateTime: Date;
modelId: string;
title: string; title: string;
latestChat: string; };
export type ChatHistoryItemType = HistoryItemType & {
appId: string;
top: boolean; top: boolean;
}; };
export type ShareChatHistoryItemType = { export type ShareChatHistoryItemType = HistoryItemType & {
_id: string;
shareId: string; shareId: string;
updateTime: Date; variables?: Record<string, any>;
title: string;
latestChat: string;
chats: ChatSiteItemType[]; chats: ChatSiteItemType[];
}; };
export type ShareChatType = InitShareChatResponse & {
history: ShareChatHistoryItemType;
};
...@@ -132,9 +132,9 @@ export interface PromotionRecordSchema { ...@@ -132,9 +132,9 @@ export interface PromotionRecordSchema {
export interface ShareChatSchema { export interface ShareChatSchema {
_id: string; _id: string;
shareId: string;
userId: string; userId: string;
modelId: string; appId: string;
password: string;
name: string; name: string;
tokens: number; tokens: number;
maxContext: number; maxContext: number;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment