mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-17 13:06:21 +00:00
💄 style: fix approving render and improve Conversation style (#10210)
fix approving render and improve chat layout style
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useTheme } from 'antd-style';
|
||||
import { Suspense, memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
@@ -18,30 +19,38 @@ interface WorkspaceLayoutProps {
|
||||
mobile?: boolean;
|
||||
}
|
||||
|
||||
const DesktopWorkspace = memo(() => (
|
||||
<>
|
||||
<ChatHeaderDesktop />
|
||||
<Flexbox
|
||||
height={'100%'}
|
||||
horizontal
|
||||
style={{ overflow: 'hidden', position: 'relative' }}
|
||||
width={'100%'}
|
||||
>
|
||||
<Flexbox height={'100%'} style={{ overflow: 'hidden', position: 'relative' }} width={'100%'}>
|
||||
<ConversationArea mobile={false} />
|
||||
const DesktopWorkspace = memo(() => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChatHeaderDesktop />
|
||||
<Flexbox
|
||||
height={'100%'}
|
||||
horizontal
|
||||
style={{ overflow: 'hidden', position: 'relative' }}
|
||||
width={'100%'}
|
||||
>
|
||||
<Flexbox
|
||||
height={'100%'}
|
||||
style={{ background: theme.colorBgContainer, overflow: 'hidden', position: 'relative' }}
|
||||
width={'100%'}
|
||||
>
|
||||
<ConversationArea mobile={false} />
|
||||
</Flexbox>
|
||||
<Portal>
|
||||
<Suspense fallback={<BrandTextLoading />}>
|
||||
<PortalPanel mobile={false} />
|
||||
</Suspense>
|
||||
</Portal>
|
||||
<TopicPanel>
|
||||
<TopicSidebar mobile={false} />
|
||||
</TopicPanel>
|
||||
</Flexbox>
|
||||
<Portal>
|
||||
<Suspense fallback={<BrandTextLoading />}>
|
||||
<PortalPanel mobile={false} />
|
||||
</Suspense>
|
||||
</Portal>
|
||||
<TopicPanel>
|
||||
<TopicSidebar mobile={false} />
|
||||
</TopicPanel>
|
||||
</Flexbox>
|
||||
<MainInterfaceTracker />
|
||||
</>
|
||||
));
|
||||
<MainInterfaceTracker />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
DesktopWorkspace.displayName = 'DesktopWorkspace';
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useStyles } from '../style';
|
||||
import { ChatItemProps } from '../type';
|
||||
|
||||
export interface MessageContentProps {
|
||||
className?: string;
|
||||
disabled?: ChatItemProps['disabled'];
|
||||
editing?: ChatItemProps['editing'];
|
||||
id: string;
|
||||
@@ -39,6 +40,7 @@ const MessageContent = memo<MessageContentProps>(
|
||||
onDoubleClick,
|
||||
markdownProps,
|
||||
disabled,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation('common');
|
||||
const { cx, styles } = useStyles({ disabled, editing, placement, primary, variant });
|
||||
@@ -81,7 +83,7 @@ const MessageContent = memo<MessageContentProps>(
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
className={cx(styles.message, editing && styles.editingContainer)}
|
||||
className={cx(styles.message, editing && styles.editingContainer, className)}
|
||||
onDoubleClick={onDoubleClick}
|
||||
>
|
||||
{messageContent}
|
||||
|
||||
+34
-13
@@ -28,10 +28,12 @@ const ApprovalActions = memo<ApprovalActionsProps>(
|
||||
const [approveLoading, setApproveLoading] = useState(false);
|
||||
|
||||
const { assistantGroupId } = useGroupMessage();
|
||||
const [approveToolIntervention, rejectToolIntervention] = useChatStore((s) => [
|
||||
s.approveToolCalling,
|
||||
s.rejectToolCalling,
|
||||
]);
|
||||
const [approveToolIntervention, rejectToolIntervention, rejectAndContinueToolIntervention] =
|
||||
useChatStore((s) => [
|
||||
s.approveToolCalling,
|
||||
s.rejectToolCalling,
|
||||
s.rejectAndContinueToolCalling,
|
||||
]);
|
||||
const addToolToAllowList = useUserStore((s) => s.addToolToAllowList);
|
||||
|
||||
const handleApprove = async (remember?: boolean) => {
|
||||
@@ -58,6 +60,14 @@ const ApprovalActions = memo<ApprovalActionsProps>(
|
||||
setRejectReason('');
|
||||
};
|
||||
|
||||
const handleRejectAndContinue = async (reason?: string) => {
|
||||
setRejectLoading(true);
|
||||
await rejectAndContinueToolIntervention(messageId, reason);
|
||||
setRejectLoading(false);
|
||||
setRejectPopoverOpen(false);
|
||||
setRejectReason('');
|
||||
};
|
||||
|
||||
return (
|
||||
<Flexbox gap={8} horizontal>
|
||||
<Popover
|
||||
@@ -67,14 +77,25 @@ const ApprovalActions = memo<ApprovalActionsProps>(
|
||||
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
||||
<div>{t('tool.intervention.rejectTitle')}</div>
|
||||
|
||||
<Button
|
||||
loading={rejectLoading}
|
||||
onClick={() => handleReject(rejectReason)}
|
||||
size="small"
|
||||
type="primary"
|
||||
>
|
||||
{t('confirm', { ns: 'common' })}
|
||||
</Button>
|
||||
<Space>
|
||||
<Button
|
||||
color={'default'}
|
||||
loading={rejectLoading}
|
||||
onClick={() => handleReject(rejectReason)}
|
||||
size="small"
|
||||
variant={'filled'}
|
||||
>
|
||||
{t('tool.intervention.rejectOnly')}
|
||||
</Button>
|
||||
<Button
|
||||
loading={rejectLoading}
|
||||
onClick={() => handleRejectAndContinue(rejectReason)}
|
||||
size="small"
|
||||
type="primary"
|
||||
>
|
||||
{t('tool.intervention.rejectAndContinue')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Flexbox>
|
||||
<Input.TextArea
|
||||
autoFocus
|
||||
@@ -95,7 +116,7 @@ const ApprovalActions = memo<ApprovalActionsProps>(
|
||||
placement="bottomRight"
|
||||
trigger="click"
|
||||
>
|
||||
<Button size="small" type="default">
|
||||
<Button color={'default'} size="small" variant={'filled'}>
|
||||
{t('tool.intervention.reject')}
|
||||
</Button>
|
||||
</Popover>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UIChatMessage } from '@lobechat/types';
|
||||
import { Tag } from '@lobehub/ui';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import { createStyles, useResponsive } from 'antd-style';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { ReactNode, memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -45,6 +45,13 @@ const remarkPlugins = markdownElements
|
||||
.map((element) => element.remarkPlugin)
|
||||
.filter(Boolean);
|
||||
|
||||
const useUserStyles = createStyles(({ css, token }) => ({
|
||||
messageContainer: css`
|
||||
border: none;
|
||||
background: ${token.colorFillTertiary};
|
||||
`,
|
||||
}));
|
||||
|
||||
const UserMessage = memo<UserMessageProps>(({ id, disableEditing, index }) => {
|
||||
const item = useChatStore(
|
||||
displayMessageSelectors.getDisplayMessageById(id),
|
||||
@@ -56,6 +63,8 @@ const UserMessage = memo<UserMessageProps>(({ id, disableEditing, index }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const { mobile } = useResponsive();
|
||||
const avatar = useUserAvatar();
|
||||
const { styles: userStyles } = useUserStyles();
|
||||
|
||||
const title = useUserStore(userProfileSelectors.displayUserName);
|
||||
|
||||
const displayMode = useAgentStore(agentChatConfigSelectors.displayMode);
|
||||
@@ -165,6 +174,7 @@ const UserMessage = memo<UserMessageProps>(({ id, disableEditing, index }) => {
|
||||
>
|
||||
<Flexbox flex={1} style={{ maxWidth: '100%', minWidth: 0 }}>
|
||||
<MessageContent
|
||||
className={userStyles.messageContainer}
|
||||
editing={editing}
|
||||
id={id}
|
||||
markdownProps={markdownProps}
|
||||
|
||||
@@ -414,6 +414,8 @@ export default {
|
||||
manualDesc: '每次调用都需要手动批准',
|
||||
},
|
||||
reject: '拒绝',
|
||||
rejectAndContinue: '拒绝后重试执行',
|
||||
rejectOnly: '拒绝',
|
||||
rejectReasonPlaceholder: '输入拒绝原因将帮助 Agent 理解并优化后续行动',
|
||||
rejectTitle: '拒绝本次工具调用',
|
||||
rejectedWithReason: '本次工具调用被主动拒绝:{{reason}}',
|
||||
|
||||
@@ -43,6 +43,10 @@ export interface ConversationControlAction {
|
||||
* Reject tool intervention
|
||||
*/
|
||||
rejectToolCalling: (messageId: string, reason?: string) => Promise<void>;
|
||||
/**
|
||||
* Reject tool intervention and continue
|
||||
*/
|
||||
rejectAndContinueToolCalling: (messageId: string, reason?: string) => Promise<void>;
|
||||
/**
|
||||
* Toggle sendMessage operation state
|
||||
*/
|
||||
@@ -206,6 +210,44 @@ export const conversationControl: StateCreator<
|
||||
await get().optimisticUpdateMessageContent(messageId, toolContent);
|
||||
},
|
||||
|
||||
rejectAndContinueToolCalling: async (messageId, reason) => {
|
||||
await get().rejectToolCalling(messageId, reason);
|
||||
|
||||
const toolMessage = dbMessageSelectors.getDbMessageById(messageId)(get());
|
||||
if (!toolMessage) return;
|
||||
|
||||
// Get current messages for state construction
|
||||
const currentMessages = displayMessageSelectors.mainAIChats(get());
|
||||
const { activeThreadId, internal_execAgentRuntime } = get();
|
||||
|
||||
// Create agent state and context to continue from rejected tool message
|
||||
const { state, context: initialContext } = get().internal_createAgentState({
|
||||
messages: currentMessages,
|
||||
parentMessageId: messageId,
|
||||
threadId: activeThreadId,
|
||||
});
|
||||
|
||||
// Override context with 'userInput' phase to continue as if user provided feedback
|
||||
const context: AgentRuntimeContext = {
|
||||
...initialContext,
|
||||
phase: 'user_input',
|
||||
};
|
||||
|
||||
// Execute agent runtime from rejected tool message position to continue
|
||||
try {
|
||||
await internal_execAgentRuntime({
|
||||
messages: currentMessages,
|
||||
parentMessageId: messageId,
|
||||
parentMessageType: 'tool',
|
||||
threadId: activeThreadId,
|
||||
initialState: state,
|
||||
initialContext: context,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[rejectAndContinueToolCalling] Error executing agent runtime:', error);
|
||||
}
|
||||
},
|
||||
|
||||
internal_updateSendMessageOperation: (key, value, actionName) => {
|
||||
const operationKey = typeof key === 'string' ? key : messageMapKey(key.sessionId, key.topicId);
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Highlighter, Text } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { BuiltinInterventionProps } from '@/types/tool';
|
||||
|
||||
const formatTimeout = (ms?: number) => {
|
||||
if (!ms) return null;
|
||||
|
||||
@@ -23,11 +25,8 @@ const formatTimeout = (ms?: number) => {
|
||||
return `${ms}ms`;
|
||||
};
|
||||
|
||||
interface RunCommandProps extends RunCommandParams {
|
||||
messageId: string;
|
||||
}
|
||||
|
||||
const RunCommand = memo<RunCommandProps>(({ description, command, timeout }) => {
|
||||
const RunCommand = memo<BuiltinInterventionProps<RunCommandParams>>(({ args }) => {
|
||||
const { description, command, timeout } = args;
|
||||
return (
|
||||
<Flexbox gap={8}>
|
||||
<Flexbox horizontal justify={'space-between'}>
|
||||
|
||||
@@ -26,9 +26,10 @@ const useStyles = createStyles(({ css, token, cx }) => ({
|
||||
|
||||
height: 64px;
|
||||
padding: 8px;
|
||||
border: 1px solid ${token.colorBorderSecondary};
|
||||
border-radius: ${token.borderRadiusLG}px;
|
||||
|
||||
background: ${token.colorFillQuaternary};
|
||||
|
||||
transition: all 0.2s ${token.motionEaseInOut};
|
||||
|
||||
.local-file-actions {
|
||||
|
||||
@@ -186,6 +186,7 @@ export const LocalSystemManifest: BuiltinToolManifest = {
|
||||
{
|
||||
description:
|
||||
'Write content to a specific file. Input should be the file path and content. Overwrites existing file or creates a new one.',
|
||||
humanIntervention: 'required',
|
||||
name: LocalSystemApiName.writeLocalFile,
|
||||
parameters: {
|
||||
properties: {
|
||||
|
||||
Reference in New Issue
Block a user