Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/12725-copilot-draft-badge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@object-ui/app-shell': minor
---

Studio's 变更 badge and the item header follow the copilot's drafts as they land (objectui#12725, item 5).

- **The badge counts a copilot draft the moment the tool answers.** The copilot's chat host (`ChatPane`) announces each tool result that carries an authoring envelope (a `drafted` result, or a 确认修改 replay's verdict) on the assistant bus as soon as it arrives in the stream. Before, it waited for the whole turn to finish, so the badge kept the old count for as long as the agent went on writing after the tool had answered. Each result is announced once; a result already in the thread when the chat opens is history and is not announced again. No reload and no navigation is involved: the badge's ledger (`_drafts`) re-reads on the pulse, as it already did.
- **The item header shows 「草稿 · N 项未发布」** (en: "Draft · N unpublished") on the Interfaces, Data and Automations pillars while the open item is a pending draft, in place of the "Unpublished draft" chip. It reads the ledger the badge counts, so a draft the copilot writes to the open item shows as it lands, and the line goes when a publish or a discard takes the item off the ledger. N is the badge's number: publishing is package-level, so the line says this item is one of N unpublished changes, and its tooltip says so. Where the ledger could not be read, the header keeps the "Unpublished draft" chip, with no count.

Clause-②: no. No spec key, export, prop or language-pack key changes; the new copy is two rows of the Studio designer table (en and zh).
56 changes: 38 additions & 18 deletions packages/app-shell/src/console/ai/AiChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2170,25 +2170,45 @@ export function ChatPane({
onPackageBound?.(builtPackageId);
}, [isBuildSurface, isLoading, canBind, builtPackageId, onPackageBound]);

// objectui#5801 — when a turn that STAGED or PUBLISHED something finishes,
// announce it on the bus so every pending-drafts surface (Studio topbar,
// home banner, the bar above this pane) converges immediately — previously
// an agent-staged draft lit only the chat bar (its own idle refetch) while
// the Studio topbar count stayed dark until a manual draft save. Precise on
// purpose: only turns whose tools carried an authoring envelope emit, so an
// ordinary Q&A turn does not trigger an env-wide registry refetch.
const lastAuthoringEmitRef = useRef<string | undefined>(undefined);
// objectui#5801 — when a tool STAGES or PUBLISHES something, announce it on
// the bus so every pending-drafts surface (Studio topbar, home banner, the
// bar above this pane) converges immediately — previously an agent-staged
// draft lit only the chat bar (its own idle refetch) while the Studio topbar
// count stayed dark until a manual draft save. Precise on purpose: only tool
// results that carry an authoring envelope emit (a drafted result's
// `draftReview`, or a confirm-replay's `replayOutcome`), so an ordinary Q&A
// turn does not trigger an env-wide registry refetch.
//
// objectui#12725 — announced the moment each result LANDS, not when its turn
// ends. The draft is written before the tool answers (the envelope's own
// `draftState` is read after that write), so the Studio 变更 badge can
// count it while the agent is still narrating; waiting for the stream to
// finish left the badge behind the chat card by the whole narration. Each
// result is announced once, keyed by its message and its position in that
// message: a streamed message is re-mapped on every chunk, and a tool part
// without a `toolCallId` is given a fresh random one each time. The results
// already in the thread when this pane mounts are history, not a landing.
const announcedAuthoringRef = useRef<Set<string> | null>(null);
useEffect(() => {
if (isLoading) return;
const lastAssistant = [...messages].reverse().find((m) => m.role === 'assistant');
if (!lastAssistant || lastAssistant.id === lastAuthoringEmitRef.current) return;
const authored = (lastAssistant.toolInvocations ?? []).some(
(tool) => tool.draftReview || tool.replayOutcome,
);
if (!authored) return;
lastAuthoringEmitRef.current = lastAssistant.id;
emitMetadataRefresh();
}, [isLoading, messages]);
const authored: string[] = [];
for (const message of messages) {
(message.toolInvocations ?? []).forEach((tool, index) => {
if (tool.draftReview || tool.replayOutcome) authored.push(`${message.id}#${index}`);
});
}
const announced = announcedAuthoringRef.current;
if (announced === null) {
announcedAuthoringRef.current = new Set(authored);
return;
}
let landed = false;
for (const key of authored) {
if (announced.has(key)) continue;
announced.add(key);
landed = true;
}
if (landed) emitMetadataRefresh();
}, [messages]);

// objectui#11666 — the launchers (console FAB, ChatDock edge launcher) show
// a marker while a proposed plan awaits the user's approval, and they are on
Expand Down
7 changes: 7 additions & 0 deletions packages/app-shell/src/views/metadata-admin/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2909,6 +2909,11 @@ const ENGINE_STRINGS_EN: Record<string, string> = {
'engine.studio.held.publish': 'Not published: {clause}, so that change is not saved yet. Finish it, then publish.',
'engine.studio.held.inputHint': 'Required. Changes are saved once this has a value.',
'engine.studio.unpublishedDraft': 'Unpublished draft',
// objectui#12725 — the item header's draft line: this item has a pending
// draft, and {count} is the package's pending count (the Changes badge's).
'engine.studio.itemDraftPending': 'Draft · {count} unpublished',
'engine.studio.itemDraftPendingTitle':
'This item has an unpublished draft. The package has {count} unpublished change(s); Publish puts them live together.',
'engine.studio.unpublished': 'Unpublished',
'engine.studio.new': 'New',
'engine.studio.edit': 'Edit',
Expand Down Expand Up @@ -6267,6 +6272,8 @@ const ENGINE_STRINGS_ZH: Record<string, string> = {
'engine.studio.held.publish': '未发布:{clause},该更改尚未保存。请先补全,再发布。',
'engine.studio.held.inputHint': '必填。填写后才会保存更改。',
'engine.studio.unpublishedDraft': '未发布草稿',
'engine.studio.itemDraftPending': '草稿 · {count} 项未发布',
'engine.studio.itemDraftPendingTitle': '此项有未发布的草稿。本包共有 {count} 项未发布的更改,发布时一起生效。',
'engine.studio.unpublished': '未发布',
'engine.studio.new': '新建',
'engine.studio.edit': '编辑',
Expand Down
Loading
Loading