feat: show changed files after completed turns
Add changed-file pills with per-file diff stats Add a chat setting to disable the feature fully Avoid changed-file projection work when disabled
This commit is contained in:
@@ -685,10 +685,11 @@ const TurnBlock = React.memo(({
|
|||||||
hasTools: turn.hasTools,
|
hasTools: turn.hasTools,
|
||||||
hasReasoning: turn.hasReasoning,
|
hasReasoning: turn.hasReasoning,
|
||||||
diffStats: turn.diffStats,
|
diffStats: turn.diffStats,
|
||||||
|
changedFiles: turn.changedFiles,
|
||||||
userMessageCreatedAt: typeof userCreatedAt === 'number' ? userCreatedAt : undefined,
|
userMessageCreatedAt: typeof userCreatedAt === 'number' ? userCreatedAt : undefined,
|
||||||
userMessageVariant,
|
userMessageVariant,
|
||||||
};
|
};
|
||||||
}, [turn.diffStats, turn.hasReasoning, turn.hasTools, turn.headerMessageId, turn.summaryText, turn.turnId, turn.userMessage.info, visibleActivityParts, visibleActivitySegments]);
|
}, [turn.changedFiles, turn.diffStats, turn.hasReasoning, turn.hasTools, turn.headerMessageId, turn.summaryText, turn.turnId, turn.userMessage.info, visibleActivityParts, visibleActivitySegments]);
|
||||||
|
|
||||||
const renderMessage = React.useCallback(
|
const renderMessage = React.useCallback(
|
||||||
(message: ChatMessageEntry) => {
|
(message: ChatMessageEntry) => {
|
||||||
@@ -736,6 +737,7 @@ const TurnBlock = React.memo(({
|
|||||||
activityGroupSegments: turnGroupingContextBase.activityGroupSegments,
|
activityGroupSegments: turnGroupingContextBase.activityGroupSegments,
|
||||||
headerMessageId: turnGroupingContextBase.headerMessageId,
|
headerMessageId: turnGroupingContextBase.headerMessageId,
|
||||||
diffStats: turnGroupingContextBase.diffStats,
|
diffStats: turnGroupingContextBase.diffStats,
|
||||||
|
changedFiles: turnGroupingContextBase.changedFiles,
|
||||||
userMessageCreatedAt: turnGroupingContextBase.userMessageCreatedAt,
|
userMessageCreatedAt: turnGroupingContextBase.userMessageCreatedAt,
|
||||||
userMessageVariant: turnGroupingContextBase.userMessageVariant,
|
userMessageVariant: turnGroupingContextBase.userMessageVariant,
|
||||||
isGroupExpanded: turnUiState.isExpanded,
|
isGroupExpanded: turnUiState.isExpanded,
|
||||||
@@ -1114,6 +1116,7 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
|||||||
const stickyUserHeader = useUIStore(state => state.stickyUserHeader);
|
const stickyUserHeader = useUIStore(state => state.stickyUserHeader);
|
||||||
const chatRenderMode = useUIStore((state) => state.chatRenderMode);
|
const chatRenderMode = useUIStore((state) => state.chatRenderMode);
|
||||||
const activityRenderMode = useUIStore((state) => state.activityRenderMode);
|
const activityRenderMode = useUIStore((state) => state.activityRenderMode);
|
||||||
|
const showTurnChangedFiles = useUIStore((state) => state.showTurnChangedFiles);
|
||||||
const defaultActivityExpanded = activityRenderMode === 'summary';
|
const defaultActivityExpanded = activityRenderMode === 'summary';
|
||||||
const [turnUiStates, setTurnUiStates] = React.useState<Map<string, TurnUiState>>(() => new Map());
|
const [turnUiStates, setTurnUiStates] = React.useState<Map<string, TurnUiState>>(() => new Map());
|
||||||
const userAnimationRef = React.useRef<{
|
const userAnimationRef = React.useRef<{
|
||||||
@@ -1212,6 +1215,7 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
|||||||
const { projection, staticTurns, streamingTurn } = useTurnRecords(displayMessages, {
|
const { projection, staticTurns, streamingTurn } = useTurnRecords(displayMessages, {
|
||||||
sessionKey,
|
sessionKey,
|
||||||
showTextJustificationActivity: chatRenderMode === 'sorted',
|
showTextJustificationActivity: chatRenderMode === 'sorted',
|
||||||
|
showTurnChangedFiles,
|
||||||
});
|
});
|
||||||
const hasUngroupedStaticEntries = projection.ungroupedMessageIds.size > 0;
|
const hasUngroupedStaticEntries = projection.ungroupedMessageIds.size > 0;
|
||||||
const staticEntryMessages = hasUngroupedStaticEntries ? displayMessages : EMPTY_STATIC_ENTRY_MESSAGES;
|
const staticEntryMessages = hasUngroupedStaticEntries ? displayMessages : EMPTY_STATIC_ENTRY_MESSAGES;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { streamPerfMeasure } from '@/stores/utils/streamDebug';
|
|||||||
interface UseTurnRecordsOptions {
|
interface UseTurnRecordsOptions {
|
||||||
sessionKey?: string;
|
sessionKey?: string;
|
||||||
showTextJustificationActivity: boolean;
|
showTextJustificationActivity: boolean;
|
||||||
|
showTurnChangedFiles: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TurnRecordsResult {
|
export interface TurnRecordsResult {
|
||||||
@@ -23,13 +24,16 @@ export const useTurnRecords = (
|
|||||||
const streamingTurnRef = React.useRef<TurnRecord | undefined>(undefined);
|
const streamingTurnRef = React.useRef<TurnRecord | undefined>(undefined);
|
||||||
const previousSessionKeyRef = React.useRef<string | undefined>(options.sessionKey);
|
const previousSessionKeyRef = React.useRef<string | undefined>(options.sessionKey);
|
||||||
const previousShowTextJustificationActivityRef = React.useRef(options.showTextJustificationActivity);
|
const previousShowTextJustificationActivityRef = React.useRef(options.showTextJustificationActivity);
|
||||||
|
const previousShowTurnChangedFilesRef = React.useRef(options.showTurnChangedFiles);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
previousSessionKeyRef.current !== options.sessionKey
|
previousSessionKeyRef.current !== options.sessionKey
|
||||||
|| previousShowTextJustificationActivityRef.current !== options.showTextJustificationActivity
|
|| previousShowTextJustificationActivityRef.current !== options.showTextJustificationActivity
|
||||||
|
|| previousShowTurnChangedFilesRef.current !== options.showTurnChangedFiles
|
||||||
) {
|
) {
|
||||||
previousSessionKeyRef.current = options.sessionKey;
|
previousSessionKeyRef.current = options.sessionKey;
|
||||||
previousShowTextJustificationActivityRef.current = options.showTextJustificationActivity;
|
previousShowTextJustificationActivityRef.current = options.showTextJustificationActivity;
|
||||||
|
previousShowTurnChangedFilesRef.current = options.showTurnChangedFiles;
|
||||||
previousProjectionRef.current = null;
|
previousProjectionRef.current = null;
|
||||||
staticTurnsRef.current = [];
|
staticTurnsRef.current = [];
|
||||||
streamingTurnRef.current = undefined;
|
streamingTurnRef.current = undefined;
|
||||||
@@ -39,18 +43,19 @@ export const useTurnRecords = (
|
|||||||
previousProjectionRef.current = null;
|
previousProjectionRef.current = null;
|
||||||
staticTurnsRef.current = [];
|
staticTurnsRef.current = [];
|
||||||
streamingTurnRef.current = undefined;
|
streamingTurnRef.current = undefined;
|
||||||
}, [options.sessionKey, options.showTextJustificationActivity]);
|
}, [options.sessionKey, options.showTextJustificationActivity, options.showTurnChangedFiles]);
|
||||||
|
|
||||||
const projection = React.useMemo(() => {
|
const projection = React.useMemo(() => {
|
||||||
return streamPerfMeasure('ui.turns.projection_ms', () => {
|
return streamPerfMeasure('ui.turns.projection_ms', () => {
|
||||||
const nextProjection = projectTurnRecords(messages, {
|
const nextProjection = projectTurnRecords(messages, {
|
||||||
previousProjection: previousProjectionRef.current,
|
previousProjection: previousProjectionRef.current,
|
||||||
showTextJustificationActivity: options.showTextJustificationActivity,
|
showTextJustificationActivity: options.showTextJustificationActivity,
|
||||||
|
showTurnChangedFiles: options.showTurnChangedFiles,
|
||||||
});
|
});
|
||||||
previousProjectionRef.current = nextProjection;
|
previousProjectionRef.current = nextProjection;
|
||||||
return nextProjection;
|
return nextProjection;
|
||||||
});
|
});
|
||||||
}, [messages, options.showTextJustificationActivity]);
|
}, [messages, options.showTextJustificationActivity, options.showTurnChangedFiles]);
|
||||||
|
|
||||||
const staticTurns = React.useMemo(() => {
|
const staticTurns = React.useMemo(() => {
|
||||||
const nextStatic = projection.turns.length <= 1
|
const nextStatic = projection.turns.length <= 1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { projectTurnActivity } from './projectTurnActivity';
|
import { projectTurnActivity } from './projectTurnActivity';
|
||||||
import { projectTurnIndexes } from './projectTurnIndexes';
|
import { projectTurnIndexes } from './projectTurnIndexes';
|
||||||
import { projectTurnDiffStats, projectTurnSummary } from './projectTurnSummary';
|
import { projectTurnChangedFiles, projectTurnDiffStats, projectTurnSummary } from './projectTurnSummary';
|
||||||
import type {
|
import type {
|
||||||
ChatMessageEntry,
|
ChatMessageEntry,
|
||||||
TurnMessageRecord,
|
TurnMessageRecord,
|
||||||
@@ -83,11 +83,13 @@ const buildTurnStreamState = (userMessage: ChatMessageEntry, assistantMessages:
|
|||||||
interface ProjectTurnRecordsOptions {
|
interface ProjectTurnRecordsOptions {
|
||||||
previousProjection?: TurnProjectionResult | null;
|
previousProjection?: TurnProjectionResult | null;
|
||||||
showTextJustificationActivity: boolean;
|
showTextJustificationActivity: boolean;
|
||||||
|
showTurnChangedFiles: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_OPTIONS: ProjectTurnRecordsOptions = {
|
const DEFAULT_OPTIONS: ProjectTurnRecordsOptions = {
|
||||||
previousProjection: null,
|
previousProjection: null,
|
||||||
showTextJustificationActivity: false,
|
showTextJustificationActivity: false,
|
||||||
|
showTurnChangedFiles: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const areSameMessageRefs = (left: ChatMessageEntry[], right: ChatMessageEntry[]): boolean => {
|
const areSameMessageRefs = (left: ChatMessageEntry[], right: ChatMessageEntry[]): boolean => {
|
||||||
@@ -180,6 +182,7 @@ export const projectTurnRecords = (
|
|||||||
hasTools: false,
|
hasTools: false,
|
||||||
hasReasoning: false,
|
hasReasoning: false,
|
||||||
diffStats: undefined,
|
diffStats: undefined,
|
||||||
|
changedFiles: undefined,
|
||||||
stream: {
|
stream: {
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
isRetrying: false,
|
isRetrying: false,
|
||||||
@@ -215,6 +218,9 @@ export const projectTurnRecords = (
|
|||||||
turn.summary = projectTurnSummary(turn.assistantMessages);
|
turn.summary = projectTurnSummary(turn.assistantMessages);
|
||||||
turn.summaryText = turn.summary.text ?? getUserSummaryBody(turn.userMessage);
|
turn.summaryText = turn.summary.text ?? getUserSummaryBody(turn.userMessage);
|
||||||
turn.diffStats = projectTurnDiffStats(turn.userMessage);
|
turn.diffStats = projectTurnDiffStats(turn.userMessage);
|
||||||
|
turn.changedFiles = effectiveOptions.showTurnChangedFiles
|
||||||
|
? projectTurnChangedFiles(turn.userMessage)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const activity = projectTurnActivity({
|
const activity = projectTurnActivity({
|
||||||
turnId: turn.turnId,
|
turnId: turn.turnId,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ChatMessageEntry, TurnDiffStats, TurnSummaryRecord } from './types';
|
import type { ChatMessageEntry, TurnChangedFile, TurnDiffStats, TurnSummaryRecord } from './types';
|
||||||
|
|
||||||
interface SummaryDiff {
|
interface SummaryDiff {
|
||||||
|
file?: string | null;
|
||||||
additions?: number | null;
|
additions?: number | null;
|
||||||
deletions?: number | null;
|
deletions?: number | null;
|
||||||
}
|
}
|
||||||
@@ -102,3 +103,31 @@ export const projectTurnDiffStats = (userMessage: ChatMessageEntry): TurnDiffSta
|
|||||||
files,
|
files,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const projectTurnChangedFiles = (userMessage: ChatMessageEntry): TurnChangedFile[] | undefined => {
|
||||||
|
const summary = (userMessage.info as { summary?: UserSummaryPayload | null }).summary;
|
||||||
|
const diffs = summary?.diffs;
|
||||||
|
if (!Array.isArray(diffs) || diffs.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = diffs
|
||||||
|
.map((diff) => {
|
||||||
|
if (!diff || typeof diff.file !== 'string' || diff.file.trim().length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const additions = typeof diff.additions === 'number' ? diff.additions : 0;
|
||||||
|
const deletions = typeof diff.deletions === 'number' ? diff.deletions : 0;
|
||||||
|
if (additions === 0 && deletions === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
file: diff.file,
|
||||||
|
additions,
|
||||||
|
deletions,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((file): file is TurnChangedFile => file !== null);
|
||||||
|
|
||||||
|
return files.length > 0 ? files : undefined;
|
||||||
|
};
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ export interface TurnDiffStats {
|
|||||||
files: number;
|
files: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TurnChangedFile {
|
||||||
|
file: string;
|
||||||
|
additions: number;
|
||||||
|
deletions: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TurnActivityGroup {
|
export interface TurnActivityGroup {
|
||||||
id: string;
|
id: string;
|
||||||
anchorMessageId: string;
|
anchorMessageId: string;
|
||||||
@@ -70,6 +76,7 @@ export interface TurnRecord {
|
|||||||
hasTools: boolean;
|
hasTools: boolean;
|
||||||
hasReasoning: boolean;
|
hasReasoning: boolean;
|
||||||
diffStats?: TurnDiffStats;
|
diffStats?: TurnDiffStats;
|
||||||
|
changedFiles?: TurnChangedFile[];
|
||||||
stream: TurnStreamState;
|
stream: TurnStreamState;
|
||||||
startedAt?: number;
|
startedAt?: number;
|
||||||
completedAt?: number;
|
completedAt?: number;
|
||||||
@@ -115,6 +122,7 @@ export interface TurnGroupingContext {
|
|||||||
hasTools: boolean;
|
hasTools: boolean;
|
||||||
hasReasoning: boolean;
|
hasReasoning: boolean;
|
||||||
diffStats?: TurnDiffStats;
|
diffStats?: TurnDiffStats;
|
||||||
|
changedFiles?: TurnChangedFile[];
|
||||||
userMessageCreatedAt?: number;
|
userMessageCreatedAt?: number;
|
||||||
userMessageVariant?: string;
|
userMessageVariant?: string;
|
||||||
isWorking: boolean;
|
isWorking: boolean;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { MessageFilesDisplay } from '../FileAttachment';
|
|||||||
import { TurnChangedFilesDropdown } from '../TurnChangedFilesDropdown';
|
import { TurnChangedFilesDropdown } from '../TurnChangedFilesDropdown';
|
||||||
import type { ToolPart as ToolPartType } from '@opencode-ai/sdk/v2';
|
import type { ToolPart as ToolPartType } from '@opencode-ai/sdk/v2';
|
||||||
import type { StreamPhase, ToolPopupContent, AgentMentionInfo } from './types';
|
import type { StreamPhase, ToolPopupContent, AgentMentionInfo } from './types';
|
||||||
import type { TurnGroupingContext } from '../lib/turns/types';
|
import type { TurnChangedFile, TurnGroupingContext } from '../lib/turns/types';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { isEmptyTextPart, extractTextContent } from './partUtils';
|
import { isEmptyTextPart, extractTextContent } from './partUtils';
|
||||||
import { FadeInOnReveal } from './FadeInOnReveal';
|
import { FadeInOnReveal } from './FadeInOnReveal';
|
||||||
@@ -47,12 +47,50 @@ import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
|
|||||||
import { useI18n } from '@/lib/i18n';
|
import { useI18n } from '@/lib/i18n';
|
||||||
import { extractLoopbackUrls } from '@/lib/url';
|
import { extractLoopbackUrls } from '@/lib/url';
|
||||||
import { useDeviceInfo } from '@/lib/device';
|
import { useDeviceInfo } from '@/lib/device';
|
||||||
|
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||||
|
|
||||||
|
|
||||||
const CONTAIN_LAYOUT_STYLE = { contain: 'layout' as const, transform: 'translateZ(0)' };
|
const CONTAIN_LAYOUT_STYLE = { contain: 'layout' as const, transform: 'translateZ(0)' };
|
||||||
const MESSAGE_FOOTER_CONTAINER_STYLE = { containerType: 'inline-size' as const, containerName: 'message-footer' };
|
const MESSAGE_FOOTER_CONTAINER_STYLE = { containerType: 'inline-size' as const, containerName: 'message-footer' };
|
||||||
const INLINE_MESSAGE_ACTIONS_CLASS_NAME = 'mt-2 mb-1 flex items-center justify-start gap-1.5';
|
const INLINE_MESSAGE_ACTIONS_CLASS_NAME = 'mt-2 mb-1 flex items-center justify-start gap-1.5';
|
||||||
|
|
||||||
|
const getDisplayFileName = (file: string): string => {
|
||||||
|
const normalized = file.replace(/\\/g, '/');
|
||||||
|
const segments = normalized.split('/').filter(Boolean);
|
||||||
|
return segments.at(-1) ?? file;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TurnChangedFilePills = React.memo(({ files }: { files?: TurnChangedFile[] }) => {
|
||||||
|
if (!files || files.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{files.map((file) => {
|
||||||
|
return (
|
||||||
|
<Tooltip key={file.file}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="inline-flex h-8 max-w-full items-center">
|
||||||
|
<span className="inline-flex max-w-full items-center gap-1.5 rounded-lg border border-border/30 bg-muted/30 px-2 py-1 text-xs leading-[1.35] text-muted-foreground">
|
||||||
|
<FileTypeIcon filePath={file.file} className="h-3.5 w-3.5 flex-shrink-0" />
|
||||||
|
<span className="max-w-52 truncate text-foreground/80" title={file.file}>{getDisplayFileName(file.file)}</span>
|
||||||
|
<span className="flex-shrink-0 inline-flex items-center gap-0 typography-meta" style={{ fontSize: '0.8rem', lineHeight: '1' }}>
|
||||||
|
<span style={{ color: 'var(--status-success)' }}>+{file.additions}</span>
|
||||||
|
<span className="text-muted-foreground/70">/</span>
|
||||||
|
<span style={{ color: 'var(--status-error)' }}>-{file.deletions}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{file.file}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
type SubtaskPartLike = Part & {
|
type SubtaskPartLike = Part & {
|
||||||
type: 'subtask';
|
type: 'subtask';
|
||||||
description?: unknown;
|
description?: unknown;
|
||||||
@@ -1918,43 +1956,44 @@ const AssistantMessageBody = React.memo(({
|
|||||||
)}
|
)}
|
||||||
{shouldShowTurnFooter && (
|
{shouldShowTurnFooter && (
|
||||||
<div
|
<div
|
||||||
className="mt-2 mb-1 flex items-center justify-start gap-1.5"
|
className="mt-2 mb-1 flex flex-wrap items-center justify-start gap-1.5"
|
||||||
style={MESSAGE_FOOTER_CONTAINER_STYLE}
|
style={MESSAGE_FOOTER_CONTAINER_STYLE}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5" data-message-action-group="true">
|
<div className="flex items-center gap-1.5" data-message-action-group="true">
|
||||||
{messageActionButtons}
|
{messageActionButtons}
|
||||||
{finalTurnActionButtons}
|
{finalTurnActionButtons}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5">
|
{turnDurationText ? (
|
||||||
{turnDurationText ? (
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<span className="text-sm text-muted-foreground/60 tabular-nums flex items-center gap-1">
|
||||||
<span className="text-sm text-muted-foreground/60 tabular-nums flex items-center gap-1">
|
<Icon name="hourglass" className="h-3.5 w-3.5" />
|
||||||
<Icon name="hourglass" className="h-3.5 w-3.5" />
|
<span className="message-footer__label">{turnDurationText}</span>
|
||||||
<span className="message-footer__label">{turnDurationText}</span>
|
</span>
|
||||||
</span>
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
<TooltipContent>{turnDurationText}</TooltipContent>
|
||||||
<TooltipContent>{turnDurationText}</TooltipContent>
|
</Tooltip>
|
||||||
</Tooltip>
|
) : null}
|
||||||
) : null}
|
{footerTimestamp ? (
|
||||||
{footerTimestamp ? (
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<span
|
||||||
<span
|
className={footerTimestampClassName}
|
||||||
className={footerTimestampClassName}
|
aria-label={`Message time: ${footerTimestamp}`}
|
||||||
aria-label={`Message time: ${footerTimestamp}`}
|
>
|
||||||
>
|
<Icon name="time" className="h-3.5 w-3.5" />
|
||||||
<Icon name="time" className="h-3.5 w-3.5" />
|
<span className="message-footer__label">{footerTimestamp}</span>
|
||||||
<span className="message-footer__label">{footerTimestamp}</span>
|
</span>
|
||||||
</span>
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
<TooltipContent>{footerTimestamp}</TooltipContent>
|
||||||
<TooltipContent>{footerTimestamp}</TooltipContent>
|
</Tooltip>
|
||||||
</Tooltip>
|
) : null}
|
||||||
) : null}
|
{!isMiniChatSurface && isLastAssistantInTurn && hasStopFinish ? (
|
||||||
{!isMiniChatSurface && isLastAssistantInTurn && hasStopFinish ? (
|
<TurnChangedFilesDropdown activityParts={turnGroupingContext?.activityParts} />
|
||||||
<TurnChangedFilesDropdown activityParts={turnGroupingContext?.activityParts} />
|
) : null}
|
||||||
) : null}
|
{!isMiniChatSurface && isLastAssistantInTurn && hasStopFinish ? (
|
||||||
</div>
|
<TurnChangedFilePills files={turnGroupingContext?.changedFiles} />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Message, Part } from '@opencode-ai/sdk/v2';
|
import type { Message, Part } from '@opencode-ai/sdk/v2';
|
||||||
import type { TurnActivityGroup, TurnActivityRecord, TurnDiffStats, TurnGroupingContext } from '../lib/turns/types';
|
import type { TurnActivityGroup, TurnActivityRecord, TurnChangedFile, TurnDiffStats, TurnGroupingContext } from '../lib/turns/types';
|
||||||
|
|
||||||
type MessageRecord = {
|
type MessageRecord = {
|
||||||
info: Message;
|
info: Message;
|
||||||
@@ -146,6 +146,24 @@ const areTurnDiffStatsEqual = (left?: TurnDiffStats, right?: TurnDiffStats): boo
|
|||||||
&& left.files === right.files;
|
&& left.files === right.files;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const areTurnChangedFilesEqual = (left?: TurnChangedFile[], right?: TurnChangedFile[]): boolean => {
|
||||||
|
if (left === right) return true;
|
||||||
|
if (!left || !right) return !left && !right;
|
||||||
|
if (left.length !== right.length) return false;
|
||||||
|
for (let index = 0; index < left.length; index += 1) {
|
||||||
|
const leftFile = left[index];
|
||||||
|
const rightFile = right[index];
|
||||||
|
if (
|
||||||
|
leftFile.file !== rightFile.file
|
||||||
|
|| leftFile.additions !== rightFile.additions
|
||||||
|
|| leftFile.deletions !== rightFile.deletions
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const areTurnActivityRecordsEqual = (left: TurnActivityRecord, right: TurnActivityRecord): boolean => {
|
const areTurnActivityRecordsEqual = (left: TurnActivityRecord, right: TurnActivityRecord): boolean => {
|
||||||
return left.id === right.id
|
return left.id === right.id
|
||||||
&& left.messageId === right.messageId
|
&& left.messageId === right.messageId
|
||||||
@@ -301,5 +319,9 @@ export const areRelevantTurnGroupingContextsEqual = (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((ownerRelevant || segmentsRelevant || left.isLastAssistantInTurn || right.isLastAssistantInTurn) && !areTurnChangedFilesEqual(left.changedFiles, right.changedFiles)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ const VisualSectionContent: React.FC = () => {
|
|||||||
|
|
||||||
// Chat section: User message rendering, Diff layout, Mobile status bar, Show reasoning traces, Queue mode, Persist draft
|
// Chat section: User message rendering, Diff layout, Mobile status bar, Show reasoning traces, Queue mode, Persist draft
|
||||||
const ChatSectionContent: React.FC = () => {
|
const ChatSectionContent: React.FC = () => {
|
||||||
return <OpenChamberVisualSettings visibleSettings={['chatRenderMode', 'messageTransport', 'activityRenderMode', 'userMessageRendering', 'mermaidRendering', 'reasoning', 'showToolFileIcons', 'expandedTools', 'stickyUserHeader', 'wideChatLayout', 'splitAssistantMessageActions', 'diffLayout', 'mobileStatusBar', 'dotfiles', 'fileViewerPreview', 'queueMode', 'persistDraft', 'inputSpellcheck']} />;
|
return <OpenChamberVisualSettings visibleSettings={['chatRenderMode', 'messageTransport', 'activityRenderMode', 'userMessageRendering', 'mermaidRendering', 'reasoning', 'showToolFileIcons', 'showTurnChangedFiles', 'expandedTools', 'stickyUserHeader', 'wideChatLayout', 'splitAssistantMessageActions', 'diffLayout', 'mobileStatusBar', 'dotfiles', 'fileViewerPreview', 'queueMode', 'persistDraft', 'inputSpellcheck']} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sessions section: Default model & agent, Session retention
|
// Sessions section: Default model & agent, Session retention
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain'
|
|||||||
return mode === 'markdown' ? 'markdown' : 'plain';
|
return mode === 'markdown' ? 'markdown' : 'plain';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VisibleSetting = 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'stickyUserHeader' | 'wideChatLayout' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage';
|
export type VisibleSetting = 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'stickyUserHeader' | 'wideChatLayout' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage';
|
||||||
|
|
||||||
interface OpenChamberVisualSettingsProps {
|
interface OpenChamberVisualSettingsProps {
|
||||||
/** Which settings to show. If undefined, shows all. */
|
/** Which settings to show. If undefined, shows all. */
|
||||||
@@ -305,6 +305,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
const setInputSpellcheckEnabled = useUIStore(state => state.setInputSpellcheckEnabled);
|
const setInputSpellcheckEnabled = useUIStore(state => state.setInputSpellcheckEnabled);
|
||||||
const showToolFileIcons = useUIStore(state => state.showToolFileIcons);
|
const showToolFileIcons = useUIStore(state => state.showToolFileIcons);
|
||||||
const setShowToolFileIcons = useUIStore(state => state.setShowToolFileIcons);
|
const setShowToolFileIcons = useUIStore(state => state.setShowToolFileIcons);
|
||||||
|
const showTurnChangedFiles = useUIStore(state => state.showTurnChangedFiles);
|
||||||
|
const setShowTurnChangedFiles = useUIStore(state => state.setShowTurnChangedFiles);
|
||||||
const showExpandedBashTools = useUIStore(state => state.showExpandedBashTools);
|
const showExpandedBashTools = useUIStore(state => state.showExpandedBashTools);
|
||||||
const setShowExpandedBashTools = useUIStore(state => state.setShowExpandedBashTools);
|
const setShowExpandedBashTools = useUIStore(state => state.setShowExpandedBashTools);
|
||||||
const showExpandedEditTools = useUIStore(state => state.showExpandedEditTools);
|
const showExpandedEditTools = useUIStore(state => state.showExpandedEditTools);
|
||||||
@@ -441,6 +443,11 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
void updateDesktopSettings({ showToolFileIcons: enabled });
|
void updateDesktopSettings({ showToolFileIcons: enabled });
|
||||||
}, [setShowToolFileIcons]);
|
}, [setShowToolFileIcons]);
|
||||||
|
|
||||||
|
const handleShowTurnChangedFilesChange = React.useCallback((enabled: boolean) => {
|
||||||
|
setShowTurnChangedFiles(enabled);
|
||||||
|
void updateDesktopSettings({ showTurnChangedFiles: enabled });
|
||||||
|
}, [setShowTurnChangedFiles]);
|
||||||
|
|
||||||
const handleFileViewerPreviewChange = React.useCallback((enabled: boolean) => {
|
const handleFileViewerPreviewChange = React.useCallback((enabled: boolean) => {
|
||||||
setSettingsDefaultFileViewerPreview(enabled);
|
setSettingsDefaultFileViewerPreview(enabled);
|
||||||
void updateDesktopSettings({ defaultFileViewerPreview: enabled });
|
void updateDesktopSettings({ defaultFileViewerPreview: enabled });
|
||||||
@@ -1587,7 +1594,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || (shouldShow('mobileStatusBar') && isMobile) || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('queueMode') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && (
|
{(shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || (shouldShow('mobileStatusBar') && isMobile) || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('queueMode') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || shouldShow('showTurnChangedFiles') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && (
|
||||||
<section className="p-2 space-y-0.5">
|
<section className="p-2 space-y-0.5">
|
||||||
{shouldShow('reasoning') && (
|
{shouldShow('reasoning') && (
|
||||||
<div
|
<div
|
||||||
@@ -1737,6 +1744,29 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{shouldShow('showTurnChangedFiles') && (
|
||||||
|
<div
|
||||||
|
className="group flex cursor-pointer items-center gap-2 py-0.5"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-pressed={showTurnChangedFiles}
|
||||||
|
onClick={() => handleShowTurnChangedFilesChange(!showTurnChangedFiles)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === ' ' || event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
handleShowTurnChangedFilesChange(!showTurnChangedFiles);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={showTurnChangedFiles}
|
||||||
|
onChange={handleShowTurnChangedFilesChange}
|
||||||
|
ariaLabel={t('settings.openchamber.visual.field.showTurnChangedFilesAria')}
|
||||||
|
/>
|
||||||
|
<span className="typography-ui-label text-foreground">{t('settings.openchamber.visual.field.showTurnChangedFiles')}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{shouldShow('mobileStatusBar') && isMobile && (
|
{shouldShow('mobileStatusBar') && isMobile && (
|
||||||
<div
|
<div
|
||||||
className="group flex cursor-pointer items-center gap-2 py-0.5"
|
className="group flex cursor-pointer items-center gap-2 py-0.5"
|
||||||
|
|||||||
@@ -651,6 +651,7 @@ export interface SettingsPayload {
|
|||||||
showOpenCodeUpdateNotifications?: boolean;
|
showOpenCodeUpdateNotifications?: boolean;
|
||||||
openCodeUpdateToastDismissedVersion?: string;
|
openCodeUpdateToastDismissedVersion?: string;
|
||||||
showToolFileIcons?: boolean;
|
showToolFileIcons?: boolean;
|
||||||
|
showTurnChangedFiles?: boolean;
|
||||||
showExpandedBashTools?: boolean;
|
showExpandedBashTools?: boolean;
|
||||||
showExpandedEditTools?: boolean;
|
showExpandedEditTools?: boolean;
|
||||||
chatRenderMode?: 'sorted' | 'live';
|
chatRenderMode?: 'sorted' | 'live';
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ export type DesktopSettings = {
|
|||||||
showOpenCodeUpdateNotifications?: boolean;
|
showOpenCodeUpdateNotifications?: boolean;
|
||||||
openCodeUpdateToastDismissedVersion?: string;
|
openCodeUpdateToastDismissedVersion?: string;
|
||||||
showToolFileIcons?: boolean;
|
showToolFileIcons?: boolean;
|
||||||
|
showTurnChangedFiles?: boolean;
|
||||||
showExpandedBashTools?: boolean;
|
showExpandedBashTools?: boolean;
|
||||||
showExpandedEditTools?: boolean;
|
showExpandedEditTools?: boolean;
|
||||||
timeFormatPreference?: 'auto' | '12h' | '24h';
|
timeFormatPreference?: 'auto' | '12h' | '24h';
|
||||||
|
|||||||
@@ -1624,6 +1624,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Show Copy Answer, Save as image, and Read aloud on assistant text blocks that appear before later tool calls in the same response.',
|
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Show Copy Answer, Save as image, and Read aloud on assistant text blocks that appear before later tool calls in the same response.',
|
||||||
'settings.openchamber.visual.field.showToolFileIconsAria': 'Show tool file icons',
|
'settings.openchamber.visual.field.showToolFileIconsAria': 'Show tool file icons',
|
||||||
'settings.openchamber.visual.field.showToolFileIcons': 'Show Tool File Icons',
|
'settings.openchamber.visual.field.showToolFileIcons': 'Show Tool File Icons',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFilesAria': 'Show changed files for completed turns',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFiles': 'Show Changed Files for Completed Turns',
|
||||||
'settings.openchamber.visual.field.showMobileStatusBarAria': 'Show mobile status bar',
|
'settings.openchamber.visual.field.showMobileStatusBarAria': 'Show mobile status bar',
|
||||||
'settings.openchamber.visual.field.showMobileStatusBar': 'Show Mobile Status Bar',
|
'settings.openchamber.visual.field.showMobileStatusBar': 'Show Mobile Status Bar',
|
||||||
'settings.openchamber.visual.field.showDotfilesAria': 'Show dotfiles',
|
'settings.openchamber.visual.field.showDotfilesAria': 'Show dotfiles',
|
||||||
|
|||||||
@@ -1591,6 +1591,8 @@ export const settingsDict = {
|
|||||||
"settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Muestra Copiar respuesta, Guardar como imagen y Leer en voz alta en bloques de texto del asistente que aparecen antes de llamadas a herramientas posteriores dentro de la misma respuesta.",
|
"settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Muestra Copiar respuesta, Guardar como imagen y Leer en voz alta en bloques de texto del asistente que aparecen antes de llamadas a herramientas posteriores dentro de la misma respuesta.",
|
||||||
"settings.openchamber.visual.field.showToolFileIconsAria": "Mostrar iconos de archivos de herramientas",
|
"settings.openchamber.visual.field.showToolFileIconsAria": "Mostrar iconos de archivos de herramientas",
|
||||||
"settings.openchamber.visual.field.showToolFileIcons": "Mostrar iconos de archivos de herramientas",
|
"settings.openchamber.visual.field.showToolFileIcons": "Mostrar iconos de archivos de herramientas",
|
||||||
|
"settings.openchamber.visual.field.showTurnChangedFilesAria": "Mostrar archivos cambiados en turnos completados",
|
||||||
|
"settings.openchamber.visual.field.showTurnChangedFiles": "Mostrar archivos cambiados en turnos completados",
|
||||||
"settings.openchamber.visual.field.showMobileStatusBarAria": "Mostrar barra de estado móvil",
|
"settings.openchamber.visual.field.showMobileStatusBarAria": "Mostrar barra de estado móvil",
|
||||||
"settings.openchamber.visual.field.showMobileStatusBar": "Mostrar barra de estado móvil",
|
"settings.openchamber.visual.field.showMobileStatusBar": "Mostrar barra de estado móvil",
|
||||||
"settings.openchamber.visual.field.showDotfilesAria": "Mostrar archivos ocultos",
|
"settings.openchamber.visual.field.showDotfilesAria": "Mostrar archivos ocultos",
|
||||||
|
|||||||
@@ -1591,6 +1591,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '같은 응답에서 이후 도구 호출 앞에 나타나는 어시스턴트 텍스트 블록에 답변 복사, 이미지로 저장, 소리 내어 읽기 작업을 표시합니다.',
|
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '같은 응답에서 이후 도구 호출 앞에 나타나는 어시스턴트 텍스트 블록에 답변 복사, 이미지로 저장, 소리 내어 읽기 작업을 표시합니다.',
|
||||||
'settings.openchamber.visual.field.showToolFileIconsAria': '도구 파일 아이콘 표시',
|
'settings.openchamber.visual.field.showToolFileIconsAria': '도구 파일 아이콘 표시',
|
||||||
'settings.openchamber.visual.field.showToolFileIcons': '도구 파일 아이콘 표시',
|
'settings.openchamber.visual.field.showToolFileIcons': '도구 파일 아이콘 표시',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFilesAria': '완료된 턴의 변경 파일 표시',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFiles': '완료된 턴의 변경 파일 표시',
|
||||||
'settings.openchamber.visual.field.showMobileStatusBarAria': '모바일 상태 바 표시',
|
'settings.openchamber.visual.field.showMobileStatusBarAria': '모바일 상태 바 표시',
|
||||||
'settings.openchamber.visual.field.showMobileStatusBar': '모바일 상태 바 표시',
|
'settings.openchamber.visual.field.showMobileStatusBar': '모바일 상태 바 표시',
|
||||||
'settings.openchamber.visual.field.showDotfilesAria': 'Dotfiles 표시',
|
'settings.openchamber.visual.field.showDotfilesAria': 'Dotfiles 표시',
|
||||||
|
|||||||
@@ -937,6 +937,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Pokaż Kopiuj odpowiedź, Zapisz jako obraz i Odczytaj na głos w blokach tekstu asystenta, które pojawiają się przed późniejszymi wywołaniami narzędzi w tej samej odpowiedzi.',
|
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Pokaż Kopiuj odpowiedź, Zapisz jako obraz i Odczytaj na głos w blokach tekstu asystenta, które pojawiają się przed późniejszymi wywołaniami narzędzi w tej samej odpowiedzi.',
|
||||||
'settings.openchamber.visual.field.showToolFileIcons': 'Pokaż ikony plików narzędzi',
|
'settings.openchamber.visual.field.showToolFileIcons': 'Pokaż ikony plików narzędzi',
|
||||||
'settings.openchamber.visual.field.showToolFileIconsAria': 'Pokaż ikony plików narzędzi',
|
'settings.openchamber.visual.field.showToolFileIconsAria': 'Pokaż ikony plików narzędzi',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFiles': 'Pokaż zmienione pliki w ukończonych turach',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFilesAria': 'Pokaż zmienione pliki w ukończonych turach',
|
||||||
'settings.openchamber.visual.field.spacingDensity': 'Gęstość odstępów',
|
'settings.openchamber.visual.field.spacingDensity': 'Gęstość odstępów',
|
||||||
'settings.openchamber.visual.field.stickyUserHeader': 'Przyklejony nagłówek użytkownika',
|
'settings.openchamber.visual.field.stickyUserHeader': 'Przyklejony nagłówek użytkownika',
|
||||||
'settings.openchamber.visual.field.stickyUserHeaderAria': 'Przyklejony nagłówek użytkownika',
|
'settings.openchamber.visual.field.stickyUserHeaderAria': 'Przyklejony nagłówek użytkownika',
|
||||||
|
|||||||
@@ -1591,6 +1591,8 @@ export const settingsDict = {
|
|||||||
"settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Mostra Copiar resposta, Salvar como imagem e Ler em voz alta em blocos de texto do assistente que aparecem antes de chamadas de ferramenta posteriores na mesma resposta.",
|
"settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Mostra Copiar resposta, Salvar como imagem e Ler em voz alta em blocos de texto do assistente que aparecem antes de chamadas de ferramenta posteriores na mesma resposta.",
|
||||||
"settings.openchamber.visual.field.showToolFileIconsAria": "Mostrar ícones de arquivos de ferramentas",
|
"settings.openchamber.visual.field.showToolFileIconsAria": "Mostrar ícones de arquivos de ferramentas",
|
||||||
"settings.openchamber.visual.field.showToolFileIcons": "Mostrar ícones de arquivos de ferramentas",
|
"settings.openchamber.visual.field.showToolFileIcons": "Mostrar ícones de arquivos de ferramentas",
|
||||||
|
"settings.openchamber.visual.field.showTurnChangedFilesAria": "Mostrar arquivos alterados em turnos concluídos",
|
||||||
|
"settings.openchamber.visual.field.showTurnChangedFiles": "Mostrar arquivos alterados em turnos concluídos",
|
||||||
"settings.openchamber.visual.field.showMobileStatusBarAria": "Mostrar barra de status móvel",
|
"settings.openchamber.visual.field.showMobileStatusBarAria": "Mostrar barra de status móvel",
|
||||||
"settings.openchamber.visual.field.showMobileStatusBar": "Mostrar barra de status móvel",
|
"settings.openchamber.visual.field.showMobileStatusBar": "Mostrar barra de status móvel",
|
||||||
"settings.openchamber.visual.field.showDotfilesAria": "Mostrar arquivos ocultos",
|
"settings.openchamber.visual.field.showDotfilesAria": "Mostrar arquivos ocultos",
|
||||||
|
|||||||
@@ -1591,6 +1591,8 @@ export const settingsDict = {
|
|||||||
"settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Показує «Скопіювати відповідь», «Зберегти як зображення» та озвучення для текстових блоків асистента, які з’являються перед пізнішими викликами інструментів у тій самій відповіді.",
|
"settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Показує «Скопіювати відповідь», «Зберегти як зображення» та озвучення для текстових блоків асистента, які з’являються перед пізнішими викликами інструментів у тій самій відповіді.",
|
||||||
"settings.openchamber.visual.field.showToolFileIconsAria": "Показати значки файлів інструментів",
|
"settings.openchamber.visual.field.showToolFileIconsAria": "Показати значки файлів інструментів",
|
||||||
"settings.openchamber.visual.field.showToolFileIcons": "Показати значки файлів інструментів",
|
"settings.openchamber.visual.field.showToolFileIcons": "Показати значки файлів інструментів",
|
||||||
|
"settings.openchamber.visual.field.showTurnChangedFilesAria": "Показати змінені файли для завершених ходів",
|
||||||
|
"settings.openchamber.visual.field.showTurnChangedFiles": "Показати змінені файли для завершених ходів",
|
||||||
"settings.openchamber.visual.field.showMobileStatusBarAria": "Показати рядок стану мобільного пристрою",
|
"settings.openchamber.visual.field.showMobileStatusBarAria": "Показати рядок стану мобільного пристрою",
|
||||||
"settings.openchamber.visual.field.showMobileStatusBar": "Показати мобільний рядок стану",
|
"settings.openchamber.visual.field.showMobileStatusBar": "Показати мобільний рядок стану",
|
||||||
"settings.openchamber.visual.field.showDotfilesAria": "Показати dotfiles",
|
"settings.openchamber.visual.field.showDotfilesAria": "Показати dotfiles",
|
||||||
|
|||||||
@@ -1591,6 +1591,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '在同一条回复中,如果助手文本块后面还有工具调用,则显示“复制回答”“保存为图片”和“朗读”操作。',
|
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '在同一条回复中,如果助手文本块后面还有工具调用,则显示“复制回答”“保存为图片”和“朗读”操作。',
|
||||||
'settings.openchamber.visual.field.showToolFileIconsAria': '显示工具文件图标',
|
'settings.openchamber.visual.field.showToolFileIconsAria': '显示工具文件图标',
|
||||||
'settings.openchamber.visual.field.showToolFileIcons': '显示工具文件图标',
|
'settings.openchamber.visual.field.showToolFileIcons': '显示工具文件图标',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFilesAria': '显示已完成回合的更改文件',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFiles': '显示已完成回合的更改文件',
|
||||||
'settings.openchamber.visual.field.showMobileStatusBarAria': '显示移动端状态栏',
|
'settings.openchamber.visual.field.showMobileStatusBarAria': '显示移动端状态栏',
|
||||||
'settings.openchamber.visual.field.showMobileStatusBar': '显示移动端状态栏',
|
'settings.openchamber.visual.field.showMobileStatusBar': '显示移动端状态栏',
|
||||||
'settings.openchamber.visual.field.showDotfilesAria': '显示点文件',
|
'settings.openchamber.visual.field.showDotfilesAria': '显示点文件',
|
||||||
|
|||||||
@@ -1512,6 +1512,8 @@
|
|||||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '在同一則回覆中,如果助理文字區塊後方還有工具呼叫,則顯示「複製回答」「儲存為圖片」和「朗讀」操作。',
|
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '在同一則回覆中,如果助理文字區塊後方還有工具呼叫,則顯示「複製回答」「儲存為圖片」和「朗讀」操作。',
|
||||||
'settings.openchamber.visual.field.showToolFileIconsAria': '顯示工具檔案圖示',
|
'settings.openchamber.visual.field.showToolFileIconsAria': '顯示工具檔案圖示',
|
||||||
'settings.openchamber.visual.field.showToolFileIcons': '顯示工具檔案圖示',
|
'settings.openchamber.visual.field.showToolFileIcons': '顯示工具檔案圖示',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFilesAria': '顯示已完成回合的變更檔案',
|
||||||
|
'settings.openchamber.visual.field.showTurnChangedFiles': '顯示已完成回合的變更檔案',
|
||||||
'settings.openchamber.visual.field.showMobileStatusBarAria': '顯示行動裝置狀態列',
|
'settings.openchamber.visual.field.showMobileStatusBarAria': '顯示行動裝置狀態列',
|
||||||
'settings.openchamber.visual.field.showMobileStatusBar': '顯示行動裝置狀態列',
|
'settings.openchamber.visual.field.showMobileStatusBar': '顯示行動裝置狀態列',
|
||||||
'settings.openchamber.visual.field.showDotfilesAria': '顯示點檔案',
|
'settings.openchamber.visual.field.showDotfilesAria': '顯示點檔案',
|
||||||
|
|||||||
@@ -455,6 +455,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
|||||||
if (typeof settings.showToolFileIcons === 'boolean' && settings.showToolFileIcons !== store.showToolFileIcons) {
|
if (typeof settings.showToolFileIcons === 'boolean' && settings.showToolFileIcons !== store.showToolFileIcons) {
|
||||||
store.setShowToolFileIcons(settings.showToolFileIcons);
|
store.setShowToolFileIcons(settings.showToolFileIcons);
|
||||||
}
|
}
|
||||||
|
if (typeof settings.showTurnChangedFiles === 'boolean' && settings.showTurnChangedFiles !== store.showTurnChangedFiles) {
|
||||||
|
store.setShowTurnChangedFiles(settings.showTurnChangedFiles);
|
||||||
|
}
|
||||||
if (typeof settings.showExpandedBashTools === 'boolean' && settings.showExpandedBashTools !== store.showExpandedBashTools) {
|
if (typeof settings.showExpandedBashTools === 'boolean' && settings.showExpandedBashTools !== store.showExpandedBashTools) {
|
||||||
store.setShowExpandedBashTools(settings.showExpandedBashTools);
|
store.setShowExpandedBashTools(settings.showExpandedBashTools);
|
||||||
}
|
}
|
||||||
@@ -957,6 +960,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
|||||||
if (typeof candidate.showToolFileIcons === 'boolean') {
|
if (typeof candidate.showToolFileIcons === 'boolean') {
|
||||||
result.showToolFileIcons = candidate.showToolFileIcons;
|
result.showToolFileIcons = candidate.showToolFileIcons;
|
||||||
}
|
}
|
||||||
|
if (typeof candidate.showTurnChangedFiles === 'boolean') {
|
||||||
|
result.showTurnChangedFiles = candidate.showTurnChangedFiles;
|
||||||
|
}
|
||||||
if (typeof candidate.showExpandedBashTools === 'boolean') {
|
if (typeof candidate.showExpandedBashTools === 'boolean') {
|
||||||
result.showExpandedBashTools = candidate.showExpandedBashTools;
|
result.showExpandedBashTools = candidate.showExpandedBashTools;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -605,6 +605,7 @@ interface UIStore {
|
|||||||
inputSpellcheckEnabled: boolean;
|
inputSpellcheckEnabled: boolean;
|
||||||
wideChatLayoutEnabled: boolean;
|
wideChatLayoutEnabled: boolean;
|
||||||
showToolFileIcons: boolean;
|
showToolFileIcons: boolean;
|
||||||
|
showTurnChangedFiles: boolean;
|
||||||
showExpandedBashTools: boolean;
|
showExpandedBashTools: boolean;
|
||||||
showExpandedEditTools: boolean;
|
showExpandedEditTools: boolean;
|
||||||
timeFormatPreference: TimeFormatPreference;
|
timeFormatPreference: TimeFormatPreference;
|
||||||
@@ -743,6 +744,7 @@ interface UIStore {
|
|||||||
setInputSpellcheckEnabled: (value: boolean) => void;
|
setInputSpellcheckEnabled: (value: boolean) => void;
|
||||||
setWideChatLayoutEnabled: (value: boolean) => void;
|
setWideChatLayoutEnabled: (value: boolean) => void;
|
||||||
setShowToolFileIcons: (value: boolean) => void;
|
setShowToolFileIcons: (value: boolean) => void;
|
||||||
|
setShowTurnChangedFiles: (value: boolean) => void;
|
||||||
setShowExpandedBashTools: (value: boolean) => void;
|
setShowExpandedBashTools: (value: boolean) => void;
|
||||||
setShowExpandedEditTools: (value: boolean) => void;
|
setShowExpandedEditTools: (value: boolean) => void;
|
||||||
setTimeFormatPreference: (value: TimeFormatPreference) => void;
|
setTimeFormatPreference: (value: TimeFormatPreference) => void;
|
||||||
@@ -876,6 +878,7 @@ export const useUIStore = create<UIStore>()(
|
|||||||
inputSpellcheckEnabled: false,
|
inputSpellcheckEnabled: false,
|
||||||
wideChatLayoutEnabled: false,
|
wideChatLayoutEnabled: false,
|
||||||
showToolFileIcons: true,
|
showToolFileIcons: true,
|
||||||
|
showTurnChangedFiles: false,
|
||||||
showExpandedBashTools: false,
|
showExpandedBashTools: false,
|
||||||
showExpandedEditTools: false,
|
showExpandedEditTools: false,
|
||||||
timeFormatPreference: 'auto',
|
timeFormatPreference: 'auto',
|
||||||
@@ -1942,6 +1945,9 @@ export const useUIStore = create<UIStore>()(
|
|||||||
setShowToolFileIcons: (value) => {
|
setShowToolFileIcons: (value) => {
|
||||||
set({ showToolFileIcons: value });
|
set({ showToolFileIcons: value });
|
||||||
},
|
},
|
||||||
|
setShowTurnChangedFiles: (value) => {
|
||||||
|
set({ showTurnChangedFiles: value });
|
||||||
|
},
|
||||||
setShowExpandedBashTools: (value) => {
|
setShowExpandedBashTools: (value) => {
|
||||||
set({ showExpandedBashTools: value });
|
set({ showExpandedBashTools: value });
|
||||||
},
|
},
|
||||||
@@ -2183,6 +2189,7 @@ export const useUIStore = create<UIStore>()(
|
|||||||
inputSpellcheckEnabled: state.inputSpellcheckEnabled,
|
inputSpellcheckEnabled: state.inputSpellcheckEnabled,
|
||||||
wideChatLayoutEnabled: state.wideChatLayoutEnabled,
|
wideChatLayoutEnabled: state.wideChatLayoutEnabled,
|
||||||
showToolFileIcons: state.showToolFileIcons,
|
showToolFileIcons: state.showToolFileIcons,
|
||||||
|
showTurnChangedFiles: state.showTurnChangedFiles,
|
||||||
showExpandedBashTools: state.showExpandedBashTools,
|
showExpandedBashTools: state.showExpandedBashTools,
|
||||||
showExpandedEditTools: state.showExpandedEditTools,
|
showExpandedEditTools: state.showExpandedEditTools,
|
||||||
timeFormatPreference: state.timeFormatPreference,
|
timeFormatPreference: state.timeFormatPreference,
|
||||||
|
|||||||
@@ -391,6 +391,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|||||||
if (typeof candidate.showToolFileIcons === 'boolean') {
|
if (typeof candidate.showToolFileIcons === 'boolean') {
|
||||||
result.showToolFileIcons = candidate.showToolFileIcons;
|
result.showToolFileIcons = candidate.showToolFileIcons;
|
||||||
}
|
}
|
||||||
|
if (typeof candidate.showTurnChangedFiles === 'boolean') {
|
||||||
|
result.showTurnChangedFiles = candidate.showTurnChangedFiles;
|
||||||
|
}
|
||||||
if (typeof candidate.showExpandedBashTools === 'boolean') {
|
if (typeof candidate.showExpandedBashTools === 'boolean') {
|
||||||
result.showExpandedBashTools = candidate.showExpandedBashTools;
|
result.showExpandedBashTools = candidate.showExpandedBashTools;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user