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
4 changes: 2 additions & 2 deletions apps/desktop/renderer-architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -706,8 +706,8 @@
"@maka/ui": 1,
"react": 1
},
"importSpecifiers": 11,
"nonTriviaTokens": 1395
"importSpecifiers": 10,
"nonTriviaTokens": 1273
},
"src/renderer/app-shell.tsx": {
"importDeclarations": 70,
Expand Down
10 changes: 0 additions & 10 deletions apps/desktop/src/renderer/app-shell-turn-view-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import type { UiLocale } from '@maka/core/ui-locale';
import {
deriveTurnLineageMap,
finalAssistantReplyText,
formatTurnDuration,
isSandboxDeniedTool,
type TurnFooterActionMeta,
type TurnLineageBadge,
Expand Down Expand Up @@ -189,14 +188,6 @@ function deriveTurnPresentationEntry(input: {
uiLocale: UiLocale;
}): TurnPresentationEntry {
const { turn, lineageEntry, pendingForTurn, uiLocale } = input;
const metaParts: string[] = [];
if (turn.modelId) metaParts.push(turn.modelId);
// Below a second there is nothing to report: a turn's duration counts whole
// seconds, so a 300ms turn would read「0s」— a number that says less than no
// number at all.
if (turn.durationMs && turn.durationMs >= 1_000) metaParts.push(formatTurnDuration(turn.durationMs));
if (turn.tokens?.costUsd && turn.tokens.costUsd > 0) metaParts.push(`$${turn.tokens.costUsd.toFixed(4)}`);
const metaSummary = metaParts.length > 0 ? metaParts.join(' · ') : undefined;
const footerActions = deriveTurnFooterActions({
status: turn.status,
locale: uiLocale,
Expand All @@ -207,7 +198,6 @@ function deriveTurnPresentationEntry(input: {
? { alreadyRegenerated: true }
: {}),
...(pendingForTurn.size > 0 ? { pendingActions: pendingForTurn } : {}),
...(metaSummary ? { metaSummary } : {}),
});

const entry: TurnPresentationEntry = { footerActions };
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/renderer/locales/conversation-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export interface DesktopConversationCopy {
configurationReason: Record<ChatConfigurationReason, string>;
};
footer: {
labels: Record<'regenerate' | 'branch' | 'copy' | 'info', string>;
labels: Record<'regenerate' | 'branch' | 'copy', string>;
pending: string;
regenerateRunning: string;
regenerateAgain: string;
Expand Down Expand Up @@ -464,7 +464,7 @@ const COPY = {
provider_retired: '当前任务绑定的连接,其登录方式已从 Maka 移除,无法用于发送。请到 设置 · 模型 改用其他连接后新建任务。',
},
},
footer: { labels: { regenerate: '重新生成', branch: '分支', copy: '复制', info: '详情' }, pending: '正在处理…', regenerateRunning: '当前回答仍在进行中,结束后再重新生成', regenerateAgain: '已重新生成过,再次点击将创建新的并行回答', regenerate: '让模型重新生成本轮回答', requestRegenerate: '请求所有者批准重新生成本轮回答', branchRunning: '当前回答仍在进行中,结束后再分支', branchAborted: '从中断前的上下文分支出新任务', branch: '基于此回答的上下文分支出新任务', copy: '复制回答到剪贴板', copyEmpty: '此回答尚无可复制的内容' },
footer: { labels: { regenerate: '重新生成', branch: '分支', copy: '复制' }, pending: '正在处理…', regenerateRunning: '当前回答仍在进行中,结束后再重新生成', regenerateAgain: '已重新生成过,再次点击将创建新的并行回答', regenerate: '让模型重新生成本轮回答', requestRegenerate: '请求所有者批准重新生成本轮回答', branchRunning: '当前回答仍在进行中,结束后再分支', branchAborted: '从中断前的上下文分支出新任务', branch: '基于此回答的上下文分支出新任务', copy: '复制回答到剪贴板', copyEmpty: '此回答尚无可复制的内容' },
lineage: { regeneratedFrom: '重新生成自旧回答', regeneratedFromTooltip: '这是重新生成的并行回答,点击查看被保留的旧回答', regeneratedTo: '已重新生成 → 新回答', regeneratedToTooltip: '点击跳转到重新生成的新回答' },
workbar: {
ariaLabel: '任务工作栏',
Expand Down Expand Up @@ -703,7 +703,7 @@ const COPY = {
provider_retired: '目前任務繫結的連線,其登入方式已從 Maka 移除,無法用於傳送。請到 設定 · 模型 改用其他連線後建立任務。',
},
},
footer: { labels: { regenerate: '重新生成', branch: '分支', copy: '複製', info: '詳情' }, pending: '正在處理…', regenerateRunning: '目前回答仍在進行中,結束後再重新生成', regenerateAgain: '已重新生成過,再次點選將建立新的並行回答', requestRegenerate: '請求擁有者核准重新生成本輪回答', regenerate: '讓模型重新生成本輪迴答', branchRunning: '目前回答仍在進行中,結束後再分支', branchAborted: '從中斷前的上下文分支出新任務', branch: '基於此回答的上下文分支出新任務', copy: '複製回答到剪貼簿', copyEmpty: '此回答尚無可複製的內容' },
footer: { labels: { regenerate: '重新生成', branch: '分支', copy: '複製' }, pending: '正在處理…', regenerateRunning: '目前回答仍在進行中,結束後再重新生成', regenerateAgain: '已重新生成過,再次點選將建立新的並行回答', requestRegenerate: '請求擁有者核准重新生成本輪回答', regenerate: '讓模型重新生成本輪迴答', branchRunning: '目前回答仍在進行中,結束後再分支', branchAborted: '從中斷前的上下文分支出新任務', branch: '基於此回答的上下文分支出新任務', copy: '複製回答到剪貼簿', copyEmpty: '此回答尚無可複製的內容' },
lineage: { regeneratedFrom: '重新生成自舊回答', regeneratedFromTooltip: '這是重新生成的並行回答,點選檢視被保留的舊回答', regeneratedTo: '已重新生成 → 新回答', regeneratedToTooltip: '點選跳轉到重新生成的新回答' },
workbar: {
ariaLabel: '任務工作欄',
Expand Down Expand Up @@ -933,7 +933,7 @@ const COPY = {
provider_retired: 'The sign-in this task\u2019s connection uses was removed from Maka, so it cannot send. Switch to another connection in Settings · Models, then start a new task.',
},
},
footer: { labels: { regenerate: 'Regenerate', branch: 'Branch', copy: 'Copy', info: 'Details' }, pending: 'Working…', regenerateRunning: 'Wait for the current response to finish before regenerating', regenerateAgain: 'A regenerated response already exists; click again to create another parallel response', regenerate: 'Generate another response to this turn', requestRegenerate: 'Ask the Owner to approve regenerating this response', branchRunning: 'Wait for the current response to finish before branching', branchAborted: 'Branch from the context before the interruption', branch: 'Branch a new task from this response', copy: 'Copy response to clipboard', copyEmpty: 'This response has no content to copy' },
footer: { labels: { regenerate: 'Regenerate', branch: 'Branch', copy: 'Copy' }, pending: 'Working…', regenerateRunning: 'Wait for the current response to finish before regenerating', regenerateAgain: 'A regenerated response already exists; click again to create another parallel response', regenerate: 'Generate another response to this turn', requestRegenerate: 'Ask the Owner to approve regenerating this response', branchRunning: 'Wait for the current response to finish before branching', branchAborted: 'Branch from the context before the interruption', branch: 'Branch a new task from this response', copy: 'Copy response to clipboard', copyEmpty: 'This response has no content to copy' },
lineage: { regeneratedFrom: 'Regenerated from previous response', regeneratedFromTooltip: 'This is a parallel regenerated response; click to view the retained previous response', regeneratedTo: 'Regenerated → New response', regeneratedToTooltip: 'Jump to the regenerated response' },
workbar: {
ariaLabel: 'Task workbar',
Expand Down
21 changes: 3 additions & 18 deletions apps/desktop/src/renderer/turn-footer-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import type { TurnStatus } from '@maka/core/session';
import type { UiLocale } from '@maka/core/ui-locale';
import { getDesktopConversationCopy } from './locales/conversation-copy.js';

export type TurnFooterActionId = 'regenerate' | 'branch' | 'copy' | 'info';
export type TurnFooterActionId = 'regenerate' | 'branch' | 'copy';

export interface TurnFooterAction {
id: TurnFooterActionId;
Expand Down Expand Up @@ -83,14 +83,6 @@ export interface TurnFooterContext {
* understands a parallel answer already exists.
*/
alreadyRegenerated?: boolean;
/**
* Optional one-line summary of the turn's meta (model · duration ·
* cost). When present, the footer renders an `info` action
* whose tooltip carries this text — the single home for turn meta
* now that the top summary row is gone (#546). Absent on turns with
* no meta (fake backend, not-yet-streamed).
*/
metaSummary?: string;
/**
* Per @kenji review: prevent double-click duplicate sibling turns.
* The renderer marks an action `pending` from click time until
Expand All @@ -112,7 +104,7 @@ export interface TurnFooterContext {
* optimistic guesses.
*/
export function deriveTurnFooterActions(input: TurnFooterContext): TurnFooterAction[] {
const { status, hasContent, alreadyRegenerated, pendingActions, metaSummary } = input;
const { status, hasContent, alreadyRegenerated, pendingActions } = input;
const copyText = getDesktopConversationCopy(input.locale).footer;
const actionLabel = copyText.labels;
const isPending = (id: TurnFooterActionId) => pendingActions?.has(id) ?? false;
Expand Down Expand Up @@ -151,12 +143,5 @@ export function deriveTurnFooterActions(input: TurnFooterContext): TurnFooterAct
tooltip: hasContent ? copyText.copy : copyText.copyEmpty,
};

// info is informational, not an operation: no pending state, always
// enabled, and its tooltip carries the turn meta summary. Rendered
// only when there is meta to show (#546).
const info: TurnFooterAction | undefined = metaSummary
? { id: 'info', label: actionLabel.info, enabled: true, tooltip: metaSummary }
: undefined;

return [regenerate, branch, copy, ...(info ? [info] : [])];
return [regenerate, branch, copy];
}
2 changes: 1 addition & 1 deletion docs/astryx-surface-file-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ Wiki bar: Design Conventions · API Use-the-System · Theming · Container Paddi
| `packages/ui/src/chat-empty-hero.tsx` | shell-chrome-or-panel | Item | aligned — uses Astryx (Item) | aligned |
| `packages/ui/src/chat-model-switcher.tsx` | shell-chrome-or-panel | Button, DropdownMenu, DropdownMenuRadioGroup, DropdownMenuRadioItem | aligned — uses Astryx (Button, DropdownMenu, DropdownMenuRadioGroup, DropdownMenuRadioItem) | aligned |
| `packages/ui/src/chat-surface-layout.tsx` | shell-chrome-or-panel | ChatLayout | aligned — uses Astryx (ChatLayout) | aligned |
| `packages/ui/src/chat-turn.tsx` | shell-chrome-or-panel | Badge, Banner, Button, ChatMessage, ChatMessageBubble, ChatMessageMetadata, ChatSystemMessage, ChatTokenizedText, HStack, IconButton, Spinner, Thumbnail, Timestamp, Token, Tooltip | aligned — uses Astryx (Badge, Banner, Button, ChatMessage, ChatMessageBubble, ChatMessageMetadata, ChatSystemMessage, ChatTokenizedText) | aligned |
| `packages/ui/src/chat-turn.tsx` | shell-chrome-or-panel | Badge, Banner, Button, ChatMessage, ChatMessageBubble, ChatMessageMetadata, ChatSystemMessage, ChatTokenizedText, HStack, Icon, IconButton, Spinner, Thumbnail, Timestamp, Token, Tooltip | aligned — uses Astryx (Badge, Banner, Button, ChatMessage, ChatMessageBubble, ChatMessageMetadata, ChatSystemMessage, ChatTokenizedText) | aligned |
| `packages/ui/src/chat-view.tsx` | shell-chrome-or-panel | Button, ButtonGroup, ChatMessageList, EmptyState, HStack, Spinner, Text | aligned — uses Astryx (Button, ButtonGroup, ChatMessageList, EmptyState, HStack, Spinner, Text) | aligned |
| `packages/ui/src/client-capability-prompt.tsx` | ui-composition | Button | aligned — uses Astryx (Button) | aligned |
| `packages/ui/src/components.tsx` | ui-composition | none | aligned — no raw controls; no Astryx JSX usage | aligned |
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/__tests__/chat-turn-answer-identity.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -441,11 +441,11 @@ test('footer copy preserves raw text, blocks overlapping writes and resets succe
assert.equal(writeText.mock.callCount(), 1);
assert.equal(writeText.mock.calls[0]?.arguments[0], text);
assert.equal(button.getAttribute('data-copy-feedback'), 'pending');
assert.equal(button.getAttribute('data-pending'), 'true');
assert.equal(button.getAttribute('aria-busy'), 'true');

await act(async () => pending.resolve());
assert.equal(button.getAttribute('data-copy-feedback'), 'copied');
assert.equal(button.hasAttribute('data-pending'), false);
assert.notEqual(button.getAttribute('aria-busy'), 'true');
await act(async () => t.mock.timers.tick(1399));
assert.equal(button.getAttribute('data-copy-feedback'), 'copied');
await act(async () => t.mock.timers.tick(1));
Expand Down
Loading