Merge branch 'develop' of github.com:SuanmoSuanyangTechnology/MemoryBear into develop
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { request } from '@/utils/request'
|
import { request } from '@/utils/request'
|
||||||
import type { AiPromptForm } from '@/views/ApplicationConfig/types'
|
import type { AiPromptForm } from '@/views/ApplicationConfig/types'
|
||||||
|
import { handleSSE, type SSEMessage } from '@/utils/stream'
|
||||||
|
|
||||||
export const createPromptSessions = () => {
|
export const createPromptSessions = () => {
|
||||||
return request.post(`/prompt/sessions`)
|
return request.post(`/prompt/sessions`)
|
||||||
@@ -7,6 +8,6 @@ export const createPromptSessions = () => {
|
|||||||
export const getPrompt = (session_id: string) => {
|
export const getPrompt = (session_id: string) => {
|
||||||
return request.get(`/prompt/sessions/${session_id}`)
|
return request.get(`/prompt/sessions/${session_id}`)
|
||||||
}
|
}
|
||||||
export const updatePromptMessages = (session_id: string, data: AiPromptForm) => {
|
export const updatePromptMessages = (session_id: string, data: AiPromptForm, onMessage?: (data: SSEMessage[]) => void) => {
|
||||||
return request.post(`/prompt/sessions/${session_id}/messages`, data)
|
return handleSSE(`/prompt/sessions/${session_id}/messages`, data, onMessage)
|
||||||
}
|
}
|
||||||
@@ -1223,6 +1223,8 @@ export const en = {
|
|||||||
key_findings: 'Key Findings',
|
key_findings: 'Key Findings',
|
||||||
behavior_pattern: 'Behavior Pattern',
|
behavior_pattern: 'Behavior Pattern',
|
||||||
growth_trajectory: 'Growth Trajectory',
|
growth_trajectory: 'Growth Trajectory',
|
||||||
|
personality: 'Personality Traits',
|
||||||
|
core_values: 'Core Values',
|
||||||
},
|
},
|
||||||
space: {
|
space: {
|
||||||
createSpace: 'Create Space',
|
createSpace: 'Create Space',
|
||||||
|
|||||||
@@ -1304,6 +1304,8 @@ export const zh = {
|
|||||||
key_findings: '关键发现',
|
key_findings: '关键发现',
|
||||||
behavior_pattern: '行为模式',
|
behavior_pattern: '行为模式',
|
||||||
growth_trajectory: '成长轨迹',
|
growth_trajectory: '成长轨迹',
|
||||||
|
personality: '性格特点',
|
||||||
|
core_values: '核心价值观',
|
||||||
},
|
},
|
||||||
space: {
|
space: {
|
||||||
createSpace: '创建空间',
|
createSpace: '创建空间',
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpt
|
|||||||
import type { ChatItem } from '@/components/Chat/types'
|
import type { ChatItem } from '@/components/Chat/types'
|
||||||
import CustomSelect from '@/components/CustomSelect'
|
import CustomSelect from '@/components/CustomSelect'
|
||||||
import AiPromptVariableModal from './AiPromptVariableModal'
|
import AiPromptVariableModal from './AiPromptVariableModal'
|
||||||
|
import { type SSEMessage } from '@/utils/stream'
|
||||||
|
import Editor from './Editor'
|
||||||
|
|
||||||
interface AiPromptModalProps {
|
interface AiPromptModalProps {
|
||||||
refresh: (value: string) => void;
|
refresh: (value: string) => void;
|
||||||
@@ -35,7 +37,8 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
const [variables, setVariables] = useState<string[]>([])
|
const [variables, setVariables] = useState<string[]>([])
|
||||||
const [promptSession, setPromptSession] = useState<string | null>(null)
|
const [promptSession, setPromptSession] = useState<string | null>(null)
|
||||||
const aiPromptVariableModalRef = useRef<AiPromptVariableModalRef>(null)
|
const aiPromptVariableModalRef = useRef<AiPromptVariableModalRef>(null)
|
||||||
const currentPromptRef = useRef<any>(null)
|
const editorRef = useRef<any>(null)
|
||||||
|
const currentPromptValueRef = useRef<string>('')
|
||||||
|
|
||||||
const values = Form.useWatch([], form)
|
const values = Form.useWatch([], form)
|
||||||
|
|
||||||
@@ -78,16 +81,45 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
setChatList(prev => {
|
setChatList(prev => {
|
||||||
return [...prev, { role: 'user', content: messageContent}]
|
return [...prev, { role: 'user', content: messageContent}]
|
||||||
})
|
})
|
||||||
form.setFieldsValue({ message: undefined })
|
form.setFieldsValue({ message: undefined, current_prompt: undefined })
|
||||||
updatePromptMessages(promptSession, values)
|
|
||||||
.then(res => {
|
const handleStreamMessage = (data: SSEMessage[]) => {
|
||||||
const response = res as { prompt: string; desc: string; variables: string[] }
|
data.map(item => {
|
||||||
form.setFieldsValue({ current_prompt: response.prompt })
|
const { content, desc, variables } = item.data as { content: string; desc: string; variables: string[] };
|
||||||
setChatList(prev => {
|
|
||||||
return [...prev, { role: 'assistant', content: response.desc }]
|
switch (item.event) {
|
||||||
})
|
case 'start':
|
||||||
setVariables(response.variables)
|
currentPromptValueRef.current = ''
|
||||||
|
break;
|
||||||
|
case 'message':
|
||||||
|
if (content) {
|
||||||
|
currentPromptValueRef.current += content;
|
||||||
|
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
||||||
|
}
|
||||||
|
if (desc) {
|
||||||
|
setChatList(prev => {
|
||||||
|
return [...prev, { role: 'assistant', content: desc }]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (variables) {
|
||||||
|
setVariables(variables)
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
setLoading(false)
|
||||||
|
break
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
};
|
||||||
|
updatePromptMessages(promptSession, values, handleStreamMessage)
|
||||||
|
// .then(res => {
|
||||||
|
// const response = res as { prompt: string; desc: string; variables: string[] }
|
||||||
|
// form.setFieldsValue({ current_prompt: response.prompt })
|
||||||
|
// setChatList(prev => {
|
||||||
|
// return [...prev, { role: 'assistant', content: response.desc }]
|
||||||
|
// })
|
||||||
|
// setVariables(response.variables)
|
||||||
|
// })
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
@@ -101,18 +133,8 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
aiPromptVariableModalRef.current?.handleOpen()
|
aiPromptVariableModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
const handleVariableApply = (value: string) => {
|
const handleVariableApply = (value: string) => {
|
||||||
const textArea = currentPromptRef.current?.resizableTextArea?.textArea
|
if (editorRef.current?.insertText) {
|
||||||
if (textArea) {
|
editorRef.current.insertText(value)
|
||||||
const cursorPosition = textArea.selectionStart
|
|
||||||
const currentValue = values.current_prompt || ''
|
|
||||||
const newValue = currentValue.slice(0, cursorPosition) + value + currentValue.slice(cursorPosition)
|
|
||||||
form.setFieldValue('current_prompt', newValue)
|
|
||||||
|
|
||||||
// 设置新的光标位置
|
|
||||||
setTimeout(() => {
|
|
||||||
textArea.focus()
|
|
||||||
textArea.setSelectionRange(cursorPosition + value.length, cursorPosition + value.length)
|
|
||||||
}, 0)
|
|
||||||
} else {
|
} else {
|
||||||
form.setFieldValue('current_prompt', (values.current_prompt || '') + value)
|
form.setFieldValue('current_prompt', (values.current_prompt || '') + value)
|
||||||
}
|
}
|
||||||
@@ -191,7 +213,11 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Form.Item name="current_prompt">
|
<Form.Item name="current_prompt">
|
||||||
<Input.TextArea ref={currentPromptRef} className="rb:bg-[#FBFDFF]! rb:h-100.5!" />
|
<Editor
|
||||||
|
ref={editorRef}
|
||||||
|
className="rb:h-100.5 "
|
||||||
|
onChange={(value) => form.setFieldValue('current_prompt', value)}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<div className="rb:grid rb:grid-cols-2 rb:gap-4 rb:mt-6">
|
<div className="rb:grid rb:grid-cols-2 rb:gap-4 rb:mt-6">
|
||||||
<Button block disabled={!values?.current_prompt} onClick={handleCopy}>{t('common.copy')}</Button>
|
<Button block disabled={!values?.current_prompt} onClick={handleCopy}>{t('common.copy')}</Button>
|
||||||
|
|||||||
91
web/src/views/ApplicationConfig/components/Editor/index.tsx
Normal file
91
web/src/views/ApplicationConfig/components/Editor/index.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import {forwardRef, useImperativeHandle } from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
||||||
|
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
||||||
|
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
|
||||||
|
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
|
||||||
|
import { $getSelection } from 'lexical';
|
||||||
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
|
import InitialValuePlugin from './plugin/InitialValuePlugin'
|
||||||
|
import LineBreakPlugin from './plugin/LineBreakPlugin';
|
||||||
|
import InsertTextPlugin from './plugin/InsertTextPlugin';
|
||||||
|
|
||||||
|
export interface EditorRef {
|
||||||
|
insertText: (text: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LexicalEditorProps {
|
||||||
|
className?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
value?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
height?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = {
|
||||||
|
paragraph: 'editor-paragraph',
|
||||||
|
text: {
|
||||||
|
bold: 'editor-text-bold',
|
||||||
|
italic: 'editor-text-italic',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
|
||||||
|
className = '',
|
||||||
|
value,
|
||||||
|
placeholder = "请输入内容...",
|
||||||
|
onChange,
|
||||||
|
}, ref) => {
|
||||||
|
const [editor] = useLexicalComposerContext();
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
insertText: (text: string) => {
|
||||||
|
editor.update(() => {
|
||||||
|
const selection = $getSelection();
|
||||||
|
if (selection) {
|
||||||
|
selection.insertText(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}), [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
<RichTextPlugin
|
||||||
|
contentEditable={
|
||||||
|
<ContentEditable
|
||||||
|
className={clsx("rb:outline-none rb:resize-none rb:text-[14px] rb:leading-5 rb:px-4 rb:py-5 rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:overflow-auto", className)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placeholder={
|
||||||
|
<div className="rb:absolute rb:px-4 rb:py-5 rb:text-[14px] rb:text-[#5B6167] rb:leading-5 rb:pointer-none">
|
||||||
|
{placeholder}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
ErrorBoundary={LexicalErrorBoundary}
|
||||||
|
/>
|
||||||
|
<LineBreakPlugin onChange={onChange} />
|
||||||
|
<InitialValuePlugin value={value} />
|
||||||
|
<InsertTextPlugin />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const Editor = forwardRef<EditorRef, LexicalEditorProps>((props, ref) => {
|
||||||
|
const initialConfig = {
|
||||||
|
namespace: 'Editor',
|
||||||
|
theme,
|
||||||
|
nodes: [],
|
||||||
|
onError: (error: Error) => {
|
||||||
|
console.error(error);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LexicalComposer initialConfig={initialConfig}>
|
||||||
|
<EditorContent {...props} ref={ref} />
|
||||||
|
</LexicalComposer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Editor;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { type FC, useEffect } from 'react';
|
||||||
|
import { $getRoot, $createParagraphNode, $createTextNode } from 'lexical';
|
||||||
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
|
|
||||||
|
// 设置初始值的插件
|
||||||
|
const InitialValuePlugin: FC<{ value?: string }> = ({ value }) => {
|
||||||
|
const [editor] = useLexicalComposerContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value) {
|
||||||
|
editor.update(() => {
|
||||||
|
const root = $getRoot();
|
||||||
|
root.clear();
|
||||||
|
const paragraph = $createParagraphNode();
|
||||||
|
const textNode = $createTextNode(value);
|
||||||
|
paragraph.append(textNode);
|
||||||
|
root.append(paragraph);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [editor, value]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InitialValuePlugin
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { forwardRef, useImperativeHandle } from 'react';
|
||||||
|
import { $getSelection } from 'lexical';
|
||||||
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
|
import type { EditorRef } from '../index'
|
||||||
|
|
||||||
|
// 插入文本的插件
|
||||||
|
const InsertTextPlugin = forwardRef<EditorRef>((_, ref) => {
|
||||||
|
const [editor] = useLexicalComposerContext();
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
insertText: (text: string) => {
|
||||||
|
editor.update(() => {
|
||||||
|
const selection = $getSelection();
|
||||||
|
if (selection) {
|
||||||
|
selection.insertText(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}), [editor]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InsertTextPlugin;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { type FC, useEffect } from 'react';
|
||||||
|
import { $getRoot } from 'lexical';
|
||||||
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
|
|
||||||
|
// 处理换行的插件
|
||||||
|
const LineBreakPlugin: FC<{ onChange?: (value: string) => void }> = ({ onChange }) => {
|
||||||
|
const [editor] = useLexicalComposerContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return editor.registerUpdateListener(({ editorState }) => {
|
||||||
|
editorState.read(() => {
|
||||||
|
const root = $getRoot();
|
||||||
|
const textContent = root.getTextContent();
|
||||||
|
// 将\n转换为实际换行
|
||||||
|
const processedContent = textContent.replace(/\\n/g, '\n');
|
||||||
|
onChange?.(processedContent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [editor, onChange]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LineBreakPlugin;
|
||||||
@@ -5,16 +5,25 @@ import { Skeleton } from 'antd';
|
|||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import Empty from '@/components/Empty';
|
import Empty from '@/components/Empty';
|
||||||
|
import RbAlert from '@/components/RbAlert';
|
||||||
import {
|
import {
|
||||||
getUserSummary,
|
getUserSummary,
|
||||||
} from '@/api/memory'
|
} from '@/api/memory'
|
||||||
import type { AboutMeRef } from '../types'
|
import type { AboutMeRef } from '../types'
|
||||||
|
|
||||||
|
|
||||||
|
interface Data {
|
||||||
|
user_summary: string;
|
||||||
|
personality: string;
|
||||||
|
core_values: string;
|
||||||
|
one_sentence: string;
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
const AboutMe = forwardRef<AboutMeRef>((_props, ref) => {
|
const AboutMe = forwardRef<AboutMeRef>((_props, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [data, setData] = useState<string | null>(null)
|
const [data, setData] = useState<Data>({} as Data)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
@@ -27,7 +36,7 @@ const AboutMe = forwardRef<AboutMeRef>((_props, ref) => {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
getUserSummary(id)
|
getUserSummary(id)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setData((res as { summary?: string }).summary || null)
|
setData((res as Data) || null)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -44,10 +53,29 @@ const AboutMe = forwardRef<AboutMeRef>((_props, ref) => {
|
|||||||
>
|
>
|
||||||
{loading
|
{loading
|
||||||
? <Skeleton className="rb:mt-4" />
|
? <Skeleton className="rb:mt-4" />
|
||||||
: data
|
: Object.keys(data).filter(key => data[key] !== null).length > 0
|
||||||
? <div className="rb:font-regular rb:leading-5 rb:text-[#5B6167]">
|
? <>
|
||||||
{data || '-'}
|
{data.user_summary &&
|
||||||
</div>
|
<div className="rb:font-regular rb:leading-5 rb:text-[#5B6167]">
|
||||||
|
{data.user_summary}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{data.personality && <>
|
||||||
|
<div className="rb:pt-4 rb:font-medium rb:leading-5 rb:mb-2">{t('userMemory.personality')}</div>
|
||||||
|
<div className="rb:font-regular rb:leading-5 rb:text-[#5B6167]">
|
||||||
|
{data.personality}
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
{data.core_values && <>
|
||||||
|
<div className="rb:pt-4 rb:font-medium rb:leading-5 rb:mb-2">{t('userMemory.core_values')}</div>
|
||||||
|
<div className="rb:font-regular rb:leading-5 rb:text-[#5B6167]">
|
||||||
|
{data.core_values}
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
{data.one_sentence &&
|
||||||
|
<RbAlert className="rb:mt-4">{data.one_sentence}</RbAlert>
|
||||||
|
}
|
||||||
|
</>
|
||||||
: <Empty size={88} className="rb:mt-12 rb:mb-20.25" />
|
: <Empty size={88} className="rb:mt-12 rb:mb-20.25" />
|
||||||
}
|
}
|
||||||
</RbCard>
|
</RbCard>
|
||||||
|
|||||||
@@ -394,7 +394,8 @@ export const nodeLibrary: NodeLibrary[] = [
|
|||||||
defaultValue: {}
|
defaultValue: {}
|
||||||
},
|
},
|
||||||
retry: {
|
retry: {
|
||||||
type: 'define',
|
type: 'switch',
|
||||||
|
defaultValue: false
|
||||||
},
|
},
|
||||||
error_handle: {
|
error_handle: {
|
||||||
type: 'define',
|
type: 'define',
|
||||||
|
|||||||
Reference in New Issue
Block a user