mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-16 04:05:15 +00:00
* feat(web-shell): mark scheduled task turns in timeline * fix(web-shell): confine locate flash to message content * fix(web-shell): flash parallel agent locate target * fix(web-shell): keep scheduled marker source optional * fix(web-shell): omit default scheduled timeline flag * fix(web-shell): repair scheduled timeline UI conflict * fix(web-shell): remove stale shell output prop --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
64 lines
2 KiB
TypeScript
64 lines
2 KiB
TypeScript
import { memo, useContext, useState } from 'react';
|
|
import type { TodoItem } from '../../adapters/types';
|
|
import { TodoTimelineContext } from '../../App';
|
|
import { TodoEventSummary, TodoFullList } from './TodoView';
|
|
import { useI18n } from '../../i18n';
|
|
import flashStyles from '../MessageLocateFlash.module.css';
|
|
import styles from './PlanMessage.module.css';
|
|
|
|
interface PlanMessageProps {
|
|
id: string;
|
|
todos: TodoItem[];
|
|
isLocateFlashing?: boolean;
|
|
}
|
|
|
|
// Isolating the context read here (mirroring ToolGroup's TodoToolBody) keeps the
|
|
// memo-shielded PlanMessage from re-rendering when the timeline Map reference
|
|
// changes — only this small summary does.
|
|
function PlanEventSummary({ id, todos }: PlanMessageProps) {
|
|
const timeline = useContext(TodoTimelineContext);
|
|
const events = timeline.get(id)?.events ?? [];
|
|
return <TodoEventSummary todos={todos} events={events} />;
|
|
}
|
|
|
|
export const PlanMessage = memo(function PlanMessage({
|
|
id,
|
|
todos,
|
|
isLocateFlashing = false,
|
|
}: PlanMessageProps) {
|
|
const { t } = useI18n();
|
|
const [expanded, setExpanded] = useState(false);
|
|
if (todos.length === 0) return null;
|
|
|
|
const total = todos.length;
|
|
const completed = todos.filter((td) => td.status === 'completed').length;
|
|
|
|
return (
|
|
<div
|
|
className={`${styles.message}${
|
|
isLocateFlashing ? ` ${flashStyles.flash}` : ''
|
|
}`}
|
|
>
|
|
<button
|
|
type="button"
|
|
className={styles.header}
|
|
onClick={() => setExpanded((value) => !value)}
|
|
aria-expanded={expanded}
|
|
title={expanded ? t('todo.collapse') : t('todo.expand')}
|
|
>
|
|
<span className={styles.chevron} aria-hidden="true">
|
|
{expanded ? '▾' : '▸'}
|
|
</span>
|
|
<span className={styles.title}>{t('plan.title')}</span>
|
|
<span className={styles.progress}>
|
|
{completed}/{total}
|
|
</span>
|
|
</button>
|
|
{expanded ? (
|
|
<TodoFullList todos={todos} numbered />
|
|
) : (
|
|
<PlanEventSummary id={id} todos={todos} />
|
|
)}
|
|
</div>
|
|
);
|
|
});
|