feat: gtd create plan support streaming render (#11034)

feat: add the gtd stream render
This commit is contained in:
Shinji-Li
2025-12-29 21:05:24 +08:00
committed by GitHub
parent 34a6312668
commit 74d35554f2
5 changed files with 151 additions and 14 deletions
@@ -1,6 +1,7 @@
'use client';
import { Block, Flexbox, Icon, Text } from '@lobehub/ui';
import { Block, Flexbox, Icon, Markdown, Text } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { ListChecksIcon } from 'lucide-react';
import { memo } from 'react';
@@ -8,37 +9,76 @@ import { useChatStore } from '@/store/chat';
import type { Plan } from '../../types';
const MAX_CONTENT_HEIGHT = 100;
const useStyles = createStyles(({ css, token }) => ({
content: css`
overflow: hidden auto;
max-height: ${MAX_CONTENT_HEIGHT}px;
padding: 12px;
border-radius: ${token.borderRadius}px;
background: ${token.colorFillQuaternary};
`,
header: css`
cursor: pointer;
padding-block: 4px;
padding-inline: 0;
transition: opacity 0.2s;
&:hover {
opacity: 0.8;
}
`,
}));
interface PlanCardProps {
plan: Plan;
}
const PlanCard = memo<PlanCardProps>(({ plan }) => {
const { styles } = useStyles();
const openDocument = useChatStore((s) => s.openDocument);
const handleClick = () => {
const handleHeaderClick = () => {
openDocument(plan.id);
};
const hasContext = !!plan.context;
return (
<Block
clickable
gap={8}
onClick={handleClick}
padding={12}
style={{ overflow: 'hidden' }}
variant={'outlined'}
>
<Flexbox align={'center'} gap={8} horizontal style={{ overflow: 'hidden' }}>
<Block gap={8} padding={12} style={{ overflow: 'hidden' }} variant={'outlined'}>
{/* Header - clickable to open document */}
<Flexbox
align={'center'}
className={styles.header}
gap={8}
horizontal
onClick={handleHeaderClick}
style={{ overflow: 'hidden' }}
>
<Icon icon={ListChecksIcon} size={18} />
<Text ellipsis fontSize={16} weight={500}>
{plan.goal}
</Text>
</Flexbox>
{/* Description */}
{plan.description && (
<Text ellipsis={{ rows: 2 }} fontSize={14} type={'secondary'}>
{plan.description}
</Text>
)}
{/* Context content */}
{hasContext && (
<div className={styles.content}>
<Markdown fontSize={13} variant={'chat'}>
{plan.context!}
</Markdown>
</div>
)}
</Block>
);
});
@@ -0,0 +1,86 @@
'use client';
import type { BuiltinStreamingProps } from '@lobechat/types';
import { Flexbox, Icon, Markdown, Text } from '@lobehub/ui';
import { createStaticStyles } from 'antd-style';
import { ListChecksIcon } from 'lucide-react';
import { memo } from 'react';
import type { CreatePlanParams } from '../../../types';
const MAX_CONTENT_HEIGHT = 100;
const styles = createStaticStyles(({ css, cssVar }) => ({
container: css`
overflow: hidden;
padding: 12px;
border: 1px solid ${cssVar.colorBorder};
border-radius: 8px;
`,
content: css`
overflow: hidden auto;
max-height: ${MAX_CONTENT_HEIGHT}px;
padding: 12px;
border-radius: 8px;
background: ${cssVar.colorFillQuaternary};
`,
description: css`
font-size: 14px;
color: ${cssVar.colorTextSecondary};
`,
header: css`
display: flex;
gap: 8px;
align-items: center;
padding-block: 4px;
`,
title: css`
overflow: hidden;
font-size: 16px;
font-weight: 500;
color: ${cssVar.colorText};
text-overflow: ellipsis;
white-space: nowrap;
`,
}));
export const CreatePlanStreaming = memo<BuiltinStreamingProps<CreatePlanParams>>(({ args }) => {
const { goal, description, context } = args || {};
if (!goal) return null;
return (
<Flexbox className={styles.container} gap={8}>
{/* Header */}
<div className={styles.header}>
<Icon icon={ListChecksIcon} size={18} />
<Text className={styles.title} ellipsis>
{goal}
</Text>
</div>
{/* Description */}
{description && (
<Text className={styles.description} ellipsis={{ rows: 2 }}>
{description}
</Text>
)}
{/* Context content - streaming with animation */}
{context && (
<div className={styles.content}>
<Markdown animated fontSize={13} variant={'chat'}>
{context}
</Markdown>
</div>
)}
</Flexbox>
);
});
CreatePlanStreaming.displayName = 'CreatePlanStreaming';
export default CreatePlanStreaming;
@@ -14,7 +14,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
background: ${cssVar.colorFillQuaternary};
`,
description: css`
margin-bottom: 8px;
margin-block-end: 8px;
font-weight: 500;
color: ${cssVar.colorText};
`,
@@ -1,6 +1,7 @@
import type { BuiltinStreaming } from '@lobechat/types';
import { GTDApiName } from '../../types';
import { CreatePlanStreaming } from './CreatePlan';
import { ExecTaskStreaming } from './ExecTask';
import { ExecTasksStreaming } from './ExecTasks';
@@ -11,8 +12,13 @@ import { ExecTasksStreaming } from './ExecTasks';
* still executing, allowing real-time feedback to users.
*/
export const GTDStreamings: Record<string, BuiltinStreaming> = {
[GTDApiName.createPlan]: CreatePlanStreaming as BuiltinStreaming,
[GTDApiName.execTask]: ExecTaskStreaming as BuiltinStreaming,
[GTDApiName.execTasks]: ExecTasksStreaming as BuiltinStreaming,
};
export { ExecTaskStreaming, ExecTasksStreaming };
export {CreatePlanStreaming} from './CreatePlan';
export {ExecTaskStreaming} from './ExecTask';
export {ExecTasksStreaming} from './ExecTasks';
@@ -6,7 +6,12 @@ export type { TodoListRenderState } from './Render';
export { GTDRenders, TodoListRender, TodoListUI } from './Render';
// Streaming components (real-time tool execution feedback)
export { ExecTaskStreaming, ExecTasksStreaming, GTDStreamings } from './Streaming';
export {
CreatePlanStreaming,
ExecTasksStreaming,
ExecTaskStreaming,
GTDStreamings,
} from './Streaming';
// Intervention components (interactive editing)
export { AddTodoIntervention, ClearTodosIntervention, GTDInterventions } from './Intervention';