0.6.1
This commit is contained in:
2
.npmrc
2
.npmrc
@@ -1 +1 @@
|
|||||||
registry=https://registry.npmmirror.com
|
# registry=https://registry.npmmirror.com
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroui/react": "2.8.3",
|
"@heroui/react": "2.8.3",
|
||||||
"@heroui/theme": "2.4.21",
|
"@heroui/theme": "2.4.21",
|
||||||
"@mujian/js-sdk": "0.0.6-beta.65",
|
"@mujian/js-sdk": "0.0.6-beta.mjv.68",
|
||||||
"@tailwindcss/vite": "4.1.12",
|
"@tailwindcss/vite": "4.1.12",
|
||||||
"ahooks": "3.9.5",
|
"ahooks": "3.9.5",
|
||||||
"axios": "1.11.0",
|
"axios": "1.11.0",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -15,8 +15,8 @@ importers:
|
|||||||
specifier: 2.4.21
|
specifier: 2.4.21
|
||||||
version: 2.4.21(tailwindcss@4.1.12)
|
version: 2.4.21(tailwindcss@4.1.12)
|
||||||
'@mujian/js-sdk':
|
'@mujian/js-sdk':
|
||||||
specifier: 0.0.6-beta.65
|
specifier: 0.0.6-beta.mjv.68
|
||||||
version: 0.0.6-beta.65(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
version: 0.0.6-beta.mjv.68(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: 4.1.12
|
specifier: 4.1.12
|
||||||
version: 4.1.12(vite@7.1.2(@types/node@22.13.9)(jiti@2.5.1)(lightningcss@1.30.1))
|
version: 4.1.12(vite@7.1.2(@types/node@22.13.9)(jiti@2.5.1)(lightningcss@1.30.1))
|
||||||
@@ -996,8 +996,8 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.30':
|
'@jridgewell/trace-mapping@0.3.30':
|
||||||
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
|
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
|
||||||
|
|
||||||
'@mujian/js-sdk@0.0.6-beta.65':
|
'@mujian/js-sdk@0.0.6-beta.mjv.68':
|
||||||
resolution: {integrity: sha512-QMAYn6wx4uLlGSBjkAH8O9gR4x0vgHUpIKupIJSlgIKfjIqgYOZEvvx3/4anH2l6ESXGTR521x2ri9uQj5cuyw==}
|
resolution: {integrity: sha512-bsuev++lvRmS+fD36XljHIw6DnUTFzVrqV69IXKMTb7Vr8W70qQ1rMrn9gN8Lva996JZrXKQ39f3jFrRo1o53g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ~19.1.1
|
react: ~19.1.1
|
||||||
react-dom: ~19.1.1
|
react-dom: ~19.1.1
|
||||||
@@ -3942,7 +3942,7 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
'@mujian/js-sdk@0.0.6-beta.65(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
'@mujian/js-sdk@0.0.6-beta.mjv.68(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@adobe/css-tools': 4.4.4
|
'@adobe/css-tools': 4.4.4
|
||||||
ahooks: 3.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
ahooks: 3.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export type LastMessageActionsProps = {
|
|||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
swipes: string[];
|
swipes: string[];
|
||||||
activeSwipeId: number;
|
activeSwipeId: number;
|
||||||
|
panicked?: boolean;
|
||||||
onRegenerate: (() => Promise<void>) | undefined;
|
onRegenerate: (() => Promise<void>) | undefined;
|
||||||
onContinue: (() => Promise<void>) | undefined;
|
onContinue: (() => Promise<void>) | undefined;
|
||||||
onEditButtonClick: () => void;
|
onEditButtonClick: () => void;
|
||||||
@@ -116,6 +117,7 @@ export const MessageActions = ({
|
|||||||
item.className || '',
|
item.className || '',
|
||||||
)}
|
)}
|
||||||
onPress={item.onPress}
|
onPress={item.onPress}
|
||||||
|
isDisabled={item.disabled}
|
||||||
classNames={{
|
classNames={{
|
||||||
wrapper: mjChatCls(
|
wrapper: mjChatCls(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export type MessageBubbleProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MessageBubble = ({
|
export const MessageBubble = ({
|
||||||
// message,
|
message,
|
||||||
isUser,
|
isUser,
|
||||||
isEditing,
|
isEditing,
|
||||||
editedMessage,
|
editedMessage,
|
||||||
@@ -79,7 +79,13 @@ export const MessageBubble = ({
|
|||||||
)}
|
)}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
<MdRenderer content={currentMessage} />
|
<MdRenderer
|
||||||
|
content={currentMessage}
|
||||||
|
extra={{
|
||||||
|
messageId: message.id,
|
||||||
|
swipeId: message.activeSwipeId,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DropdownTrigger>
|
</DropdownTrigger>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
@@ -103,6 +109,7 @@ export const MessageBubble = ({
|
|||||||
item.className || '',
|
item.className || '',
|
||||||
)}
|
)}
|
||||||
onPress={item.onPress}
|
onPress={item.onPress}
|
||||||
|
isDisabled={item.disabled}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
@@ -111,7 +118,13 @@ export const MessageBubble = ({
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
) : (
|
) : (
|
||||||
<div className={mjChatCls(['msg-content'], 'py-4')}>
|
<div className={mjChatCls(['msg-content'], 'py-4')}>
|
||||||
<MdRenderer content={currentMessage} />
|
<MdRenderer
|
||||||
|
content={currentMessage}
|
||||||
|
extra={{
|
||||||
|
messageId: message.id,
|
||||||
|
swipeId: message.activeSwipeId,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { PanicContext } from '../../PanicContext';
|
||||||
|
|
||||||
export type DropdownItem = {
|
export type DropdownItem = {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -10,6 +13,7 @@ export type DropdownItem = {
|
|||||||
| 'warning'
|
| 'warning'
|
||||||
| 'danger';
|
| 'danger';
|
||||||
className?: string;
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UseDropdownItemsProps = {
|
export type UseDropdownItemsProps = {
|
||||||
@@ -29,11 +33,14 @@ export const useMessageActions = ({
|
|||||||
onContinue,
|
onContinue,
|
||||||
onDelete,
|
onDelete,
|
||||||
}: UseDropdownItemsProps): DropdownItem[] => {
|
}: UseDropdownItemsProps): DropdownItem[] => {
|
||||||
|
const { panicked } = useContext(PanicContext);
|
||||||
|
|
||||||
const dropdownItems: DropdownItem[] = [
|
const dropdownItems: DropdownItem[] = [
|
||||||
{
|
{
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
label: '编辑',
|
label: '编辑',
|
||||||
onPress: onEditButtonClick,
|
onPress: onEditButtonClick,
|
||||||
|
disabled: panicked,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
@@ -46,11 +53,13 @@ export const useMessageActions = ({
|
|||||||
key: 'regenerate',
|
key: 'regenerate',
|
||||||
label: '重说',
|
label: '重说',
|
||||||
onPress: async () => await onRegenerate?.(),
|
onPress: async () => await onRegenerate?.(),
|
||||||
|
disabled: panicked,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'continue',
|
key: 'continue',
|
||||||
label: '继续',
|
label: '继续',
|
||||||
onPress: async () => await onContinue?.(),
|
onPress: async () => await onContinue?.(),
|
||||||
|
disabled: panicked,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
@@ -60,6 +69,7 @@ export const useMessageActions = ({
|
|||||||
onPress: onDelete,
|
onPress: onDelete,
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
className: 'text-danger',
|
className: 'text-danger',
|
||||||
|
disabled: panicked,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ export const MessageItem = React.memo((props: MessageItemProps) => {
|
|||||||
swipes = [],
|
swipes = [],
|
||||||
activeSwipeId,
|
activeSwipeId,
|
||||||
|
|
||||||
|
mjv,
|
||||||
|
|
||||||
// sendAt,
|
// sendAt,
|
||||||
} = message;
|
} = message;
|
||||||
const isUser = role === 'user';
|
const isUser = role === 'user';
|
||||||
@@ -183,7 +185,7 @@ export const MessageItem = React.memo((props: MessageItemProps) => {
|
|||||||
// 按ctrl点击时打印ID
|
// 按ctrl点击时打印ID
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log('Official Chat Message ID:', id);
|
console.log('Official Chat Message ID:', id, mjv);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Button, Textarea } from '@heroui/react';
|
import { Button, Textarea } from '@heroui/react';
|
||||||
import { CircleStopIcon, SendIcon } from 'lucide-react';
|
import { CircleStopIcon, SendIcon } from 'lucide-react';
|
||||||
import { mjChatCls } from '@/utils/cls';
|
import { mjChatCls } from '@/utils/cls';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { PanicContext } from './PanicContext';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onSend: (query: string) => void;
|
onSend: (query: string) => void;
|
||||||
@@ -28,6 +30,8 @@ export const MsgSend = ({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { panicked } = useContext(PanicContext);
|
||||||
|
|
||||||
const isEmptyInput = value.trim().length === 0;
|
const isEmptyInput = value.trim().length === 0;
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
@@ -64,7 +68,7 @@ export const MsgSend = ({
|
|||||||
),
|
),
|
||||||
mainWrapper: mjChatCls(['input-textarea-mainWrapper'], 'px-2'),
|
mainWrapper: mjChatCls(['input-textarea-mainWrapper'], 'px-2'),
|
||||||
}}
|
}}
|
||||||
disabled={running}
|
disabled={running || panicked}
|
||||||
endContent={
|
endContent={
|
||||||
running ? (
|
running ? (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
3
src/pages/chat/PanicContext.tsx
Normal file
3
src/pages/chat/PanicContext.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export const PanicContext = createContext({ panicked: false });
|
||||||
@@ -1,46 +1,108 @@
|
|||||||
import { Alert, ScrollShadow, Spinner, ToastProvider } from '@heroui/react';
|
import { Alert, ScrollShadow, Spinner, ToastProvider } from '@heroui/react';
|
||||||
import { useChat, useMujian } from '@mujian/js-sdk/react';
|
import { useChat, useMujian } from '@mujian/js-sdk/react';
|
||||||
import type { PersonaInfo, ProjectInfo } from '@mujian/js-sdk/types';
|
import type { PersonaInfo, ProjectInfo } from '@mujian/js-sdk/types';
|
||||||
|
import * as _ from 'lodash-es';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useGlobalStore } from '@/store/global';
|
import { useGlobalStore } from '@/store/global';
|
||||||
import { mjChatCls } from '@/utils/cls';
|
import { mjChatCls } from '@/utils/cls';
|
||||||
import { MessageList } from './MessageList';
|
import { MessageList } from './MessageList';
|
||||||
import { MsgSend } from './MsgSend';
|
import { MsgSend } from './MsgSend';
|
||||||
|
import { PanicContext } from './PanicContext';
|
||||||
import { QuickReply } from './QuickReply';
|
import { QuickReply } from './QuickReply';
|
||||||
|
|
||||||
|
let $mj_ai_chat_complete: ((message: string) => Promise<void>) | undefined;
|
||||||
|
let $mj_ai_chat_project: ProjectInfo | undefined;
|
||||||
|
let $mj_ai_chat_settings_persona: PersonaInfo | undefined;
|
||||||
|
let $mj_ai_chat_setFirstMesIndex:
|
||||||
|
| ((index: number) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
|
let $mj_ai_chat_mjv_get:
|
||||||
|
| ((messageId: string, swipeId: number, path: string) => unknown)
|
||||||
|
| undefined;
|
||||||
|
let $mj_ai_chat_mjv_getAll:
|
||||||
|
| ((messageId: string, swipeId: number) => unknown)
|
||||||
|
| undefined;
|
||||||
|
|
||||||
// 扩展Window接口以包含chat对象
|
// 扩展Window接口以包含chat对象
|
||||||
declare global {
|
declare global {
|
||||||
|
interface MjEngineAiChat {
|
||||||
|
complete?: (message: string) => Promise<void>;
|
||||||
|
project?: ProjectInfo;
|
||||||
|
settings: {
|
||||||
|
persona?: PersonaInfo;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @param index 下标从1开始
|
||||||
|
*/
|
||||||
|
setFirstMesIndex?: (index: number) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MjEngineAi {
|
||||||
|
chat: MjEngineAiChat;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MjEngine {
|
||||||
|
ai: MjEngineAi;
|
||||||
|
}
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
$mj_engine: {
|
$mj_engine: MjEngine & {
|
||||||
ai: {
|
bind: (extra: { messageId: string; swipeId: number }) => MjEngine & {
|
||||||
chat: {
|
ai: {
|
||||||
complete?: (message: string) => Promise<void>;
|
chat: {
|
||||||
project?: ProjectInfo;
|
mjv: {
|
||||||
settings: {
|
get: (path: string) => unknown;
|
||||||
persona?: PersonaInfo;
|
getAll: () => unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* @param index 下标从1开始
|
|
||||||
*/
|
|
||||||
setFirstMesIndex?: (index: number) => Promise<void>;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.$mj_engine = {
|
const $mj_engine: MjEngine = {
|
||||||
ai: {
|
ai: {
|
||||||
chat: {
|
chat: {
|
||||||
complete: undefined,
|
get complete() {
|
||||||
project: undefined,
|
return $mj_ai_chat_complete;
|
||||||
|
},
|
||||||
|
get project() {
|
||||||
|
return $mj_ai_chat_project;
|
||||||
|
},
|
||||||
settings: {
|
settings: {
|
||||||
persona: undefined,
|
get persona() {
|
||||||
|
return $mj_ai_chat_settings_persona;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
get setFirstMesIndex() {
|
||||||
|
return $mj_ai_chat_setFirstMesIndex;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.$mj_engine = {
|
||||||
|
...$mj_engine,
|
||||||
|
bind({ messageId, swipeId }) {
|
||||||
|
return {
|
||||||
|
ai: {
|
||||||
|
chat: {
|
||||||
|
...$mj_engine.ai.chat,
|
||||||
|
mjv: {
|
||||||
|
get(path) {
|
||||||
|
return $mj_ai_chat_mjv_get?.(messageId, swipeId, path);
|
||||||
|
},
|
||||||
|
getAll() {
|
||||||
|
return $mj_ai_chat_mjv_getAll?.(messageId, swipeId) ?? {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const Chat = () => {
|
export const Chat = () => {
|
||||||
const { init, projectInfo, activePersona } = useGlobalStore();
|
const { init, projectInfo, activePersona } = useGlobalStore();
|
||||||
const mujian = useMujian();
|
const mujian = useMujian();
|
||||||
@@ -65,8 +127,8 @@ export const Chat = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.$mj_engine.ai.chat.project = projectInfo ?? undefined;
|
$mj_ai_chat_project = projectInfo ?? undefined;
|
||||||
window.$mj_engine.ai.chat.settings.persona = activePersona ?? undefined;
|
$mj_ai_chat_settings_persona = activePersona ?? undefined;
|
||||||
}, [projectInfo, activePersona]);
|
}, [projectInfo, activePersona]);
|
||||||
|
|
||||||
// 自定义删除消息函数
|
// 自定义删除消息函数
|
||||||
@@ -130,10 +192,10 @@ export const Chat = () => {
|
|||||||
|
|
||||||
// 将chat对象挂载到window上
|
// 将chat对象挂载到window上
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.$mj_engine.ai.chat.complete = async (message) => {
|
$mj_ai_chat_complete = async (message) => {
|
||||||
await onSend(message);
|
await onSend(message);
|
||||||
};
|
};
|
||||||
window.$mj_engine.ai.chat.setFirstMesIndex = async (oneBasedIndex) => {
|
$mj_ai_chat_setFirstMesIndex = async (oneBasedIndex) => {
|
||||||
if (messages.length > 1) {
|
if (messages.length > 1) {
|
||||||
throw new Error('已有新消息,不允许切换开场白');
|
throw new Error('已有新消息,不允许切换开场白');
|
||||||
}
|
}
|
||||||
@@ -147,6 +209,15 @@ export const Chat = () => {
|
|||||||
}
|
}
|
||||||
setSwipe(messages[0].id, zeroBasedIndex);
|
setSwipe(messages[0].id, zeroBasedIndex);
|
||||||
};
|
};
|
||||||
|
$mj_ai_chat_mjv_getAll = (messageId, swipeId) =>
|
||||||
|
messages.find((m) => m.id === messageId)?.swipeInfo[swipeId]?.mjv?.value;
|
||||||
|
$mj_ai_chat_mjv_get = (messageId, swipeId, path) => {
|
||||||
|
const mjv = $mj_ai_chat_mjv_getAll?.(messageId, swipeId);
|
||||||
|
if (!mjv) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return _.get(mjv, path);
|
||||||
|
};
|
||||||
}, [onSend, messages, setSwipe]);
|
}, [onSend, messages, setSwipe]);
|
||||||
|
|
||||||
const handleInputChange = (value: string) => {
|
const handleInputChange = (value: string) => {
|
||||||
@@ -175,61 +246,67 @@ export const Chat = () => {
|
|||||||
margin-top: ${paddingTop}px;
|
margin-top: ${paddingTop}px;
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const saveFailed =
|
||||||
|
status !== 'streaming' &&
|
||||||
|
messages.some((m) => m.id.startsWith('not_saved'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={mjChatCls(['container'], 'size-full relative')}>
|
<PanicContext.Provider
|
||||||
<style> {toastStyle} </style>
|
value={{ panicked: saveFailed || Boolean(errorMessage) }}
|
||||||
<ToastProvider
|
>
|
||||||
placement="top-center"
|
<div className={mjChatCls(['container'], 'size-full relative')}>
|
||||||
toastProps={{ color: 'danger' }}
|
<style> {toastStyle} </style>
|
||||||
disableAnimation={true}
|
<ToastProvider
|
||||||
regionProps={{
|
placement="top-center"
|
||||||
classNames: {
|
toastProps={{ color: 'danger' }}
|
||||||
base: 'toast-region',
|
disableAnimation={true}
|
||||||
},
|
regionProps={{
|
||||||
}}
|
classNames: {
|
||||||
/>
|
base: 'toast-region',
|
||||||
<div
|
},
|
||||||
className={mjChatCls(
|
}}
|
||||||
['container-background'],
|
/>
|
||||||
'h-full opacity-30 blur-xs object-cover absolute top-0 left-1/2 w-[min(640px,100%)] -translate-x-1/2 bg-cover bg-top bg-no-repeat [mask-image:linear-gradient(to_right,transparent,black_100px,black_calc(100%-100px),transparent)]',
|
<div
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
backgroundImage: projectInfo
|
|
||||||
? `url(${projectInfo.coverImageUrl})`
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={mjChatCls(
|
|
||||||
['container-content'],
|
|
||||||
'size-full z-10 p-3 flex flex-col',
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
paddingTop,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ScrollShadow
|
|
||||||
hideScrollBar
|
|
||||||
className={mjChatCls(
|
className={mjChatCls(
|
||||||
['container-content-scroll-shadow'],
|
['container-background'],
|
||||||
'h-full pb-2',
|
'h-full opacity-30 blur-xs object-cover absolute top-0 left-1/2 w-[min(640px,100%)] -translate-x-1/2 bg-cover bg-top bg-no-repeat [mask-image:linear-gradient(to_right,transparent,black_100px,black_calc(100%-100px),transparent)]',
|
||||||
)}
|
)}
|
||||||
size={20}
|
style={{
|
||||||
|
backgroundImage: projectInfo
|
||||||
|
? `url(${projectInfo.coverImageUrl})`
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={mjChatCls(
|
||||||
|
['container-content'],
|
||||||
|
'size-full z-10 p-3 flex flex-col',
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
paddingTop,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MessageList
|
<ScrollShadow
|
||||||
data={messages}
|
hideScrollBar
|
||||||
sendMessage={async (q) => {
|
className={mjChatCls(
|
||||||
await onSend(q);
|
['container-content-scroll-shadow'],
|
||||||
}}
|
'h-full pb-2',
|
||||||
onContinue={continueGenerate}
|
)}
|
||||||
onDelete={handleDeleteMessage}
|
size={20}
|
||||||
onEdit={editMessage}
|
>
|
||||||
onSwipe={setSwipe}
|
<MessageList
|
||||||
onRegenerate={regenerate}
|
data={messages}
|
||||||
onNeedMore={loadMoreMessage}
|
sendMessage={async (q) => {
|
||||||
/>
|
await onSend(q);
|
||||||
{status !== 'streaming' &&
|
}}
|
||||||
messages.some((m) => m.id.startsWith('not_saved')) && (
|
onContinue={continueGenerate}
|
||||||
|
onDelete={handleDeleteMessage}
|
||||||
|
onEdit={editMessage}
|
||||||
|
onSwipe={setSwipe}
|
||||||
|
onRegenerate={regenerate}
|
||||||
|
onNeedMore={loadMoreMessage}
|
||||||
|
/>
|
||||||
|
{saveFailed && (
|
||||||
<Alert
|
<Alert
|
||||||
hideIcon
|
hideIcon
|
||||||
title="最近一次保存消息失败,请刷新页面后重试"
|
title="最近一次保存消息失败,请刷新页面后重试"
|
||||||
@@ -241,41 +318,47 @@ export const Chat = () => {
|
|||||||
variant="bordered"
|
variant="bordered"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<Alert
|
<Alert
|
||||||
hideIcon
|
hideIcon
|
||||||
className={mjChatCls(['error-message'], 'mt-2 mb-6')}
|
className={mjChatCls(['error-message'], 'mt-2 mb-6')}
|
||||||
classNames={{
|
classNames={{
|
||||||
mainWrapper: mjChatCls(
|
mainWrapper: mjChatCls(
|
||||||
['error-message-mainWrapper'],
|
['error-message-mainWrapper'],
|
||||||
'min-h-4 ml-0',
|
'min-h-4 ml-0',
|
||||||
),
|
),
|
||||||
}}
|
description: 'opacity-80 text-xs',
|
||||||
color="danger"
|
}}
|
||||||
title={errorMessage}
|
color="danger"
|
||||||
variant="bordered"
|
title="出错啦,请刷新页面后重试"
|
||||||
/>
|
description={errorMessage}
|
||||||
)}
|
variant="bordered"
|
||||||
</ScrollShadow>
|
|
||||||
<div
|
|
||||||
className={mjChatCls(['input-container'], 'flex flex-col gap-4 mt-2')}
|
|
||||||
>
|
|
||||||
{projectInfo?.config?.qrConfig?.qrList &&
|
|
||||||
projectInfo?.config?.qrConfig?.qrList.length > 0 && (
|
|
||||||
<QuickReply
|
|
||||||
quickReplies={projectInfo?.config?.qrConfig.qrList}
|
|
||||||
onSend={onSend}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<MsgSend
|
</ScrollShadow>
|
||||||
running={status === 'streaming'}
|
<div
|
||||||
onSend={onSend}
|
className={mjChatCls(
|
||||||
onStop={stop}
|
['input-container'],
|
||||||
value={inputValue}
|
'flex flex-col gap-4 mt-2',
|
||||||
onChange={handleInputChange}
|
)}
|
||||||
/>
|
>
|
||||||
|
{projectInfo?.config?.qrConfig?.qrList &&
|
||||||
|
projectInfo?.config?.qrConfig?.qrList.length > 0 && (
|
||||||
|
<QuickReply
|
||||||
|
quickReplies={projectInfo?.config?.qrConfig.qrList}
|
||||||
|
onSend={onSend}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<MsgSend
|
||||||
|
running={status === 'streaming'}
|
||||||
|
onSend={onSend}
|
||||||
|
onStop={stop}
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PanicContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user