Skip to content
Draft
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 packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ export const globalSettingsSchema = z.object({
* @default "send"
*/
enterBehavior: z.enum(["send", "newline"]).optional(),
/**
* Whether to show timestamps on chat messages
* @default false
*/
showTimestamps: z.boolean().optional(),
/**
* Format for displaying timestamps on chat messages
* @default "24hour"
*/
timestampFormat: z.enum(["12hour", "24hour"]).optional(),
profileThresholds: z.record(z.string(), z.number()).optional(),
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
Expand Down
2 changes: 2 additions & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ export type ExtensionState = Pick<
| "includeTaskHistoryInEnhance"
| "reasoningBlockCollapsed"
| "enterBehavior"
| "showTimestamps"
| "timestampFormat"
| "includeCurrentTime"
| "includeCurrentCost"
| "maxGitStatusFiles"
Expand Down
22 changes: 21 additions & 1 deletion webview-ui/src/components/chat/ChatRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useExtensionState } from "@src/context/ExtensionStateContext"
import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
import { vscode } from "@src/utils/vscode"
import { formatPathTooltip } from "@src/utils/formatPathTooltip"
import { formatTimestamp, TimestampFormat } from "@src/utils/formatTimestamp"

import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
Expand Down Expand Up @@ -167,7 +168,16 @@ export const ChatRowContent = ({
}: ChatRowContentProps) => {
const { t, i18n } = useTranslation()

const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration, clineMessages } = useExtensionState()
const {
mcpServers,
alwaysAllowMcp,
currentCheckpoint,
mode,
apiConfiguration,
clineMessages,
showTimestamps,
timestampFormat,
} = useExtensionState()
const { info: model } = useSelectedModel(apiConfiguration)
const [isEditing, setIsEditing] = useState(false)
const [editedContent, setEditedContent] = useState("")
Expand Down Expand Up @@ -380,6 +390,13 @@ export const ChatRowContent = ({
wordBreak: "break-word",
}

// Timestamp element to be displayed on the right side of headers
const timestampElement = showTimestamps ? (
<span className="text-vscode-descriptionForeground ml-auto shrink-0" style={{ fontWeight: "normal" }}>
{formatTimestamp(message.ts, (timestampFormat ?? "24hour") as TimestampFormat)}
</span>
) : null

const tool = useMemo(
() => (message.ask === "tool" ? safeJsonParse<ClineSayTool>(message.text) : null),
[message.ask, message.text],
Expand Down Expand Up @@ -1200,6 +1217,7 @@ export const ChatRowContent = ({
<div style={headerStyle}>
<MessageCircle className="w-4 shrink-0" aria-label="Speech bubble icon" />
<span style={{ fontWeight: "bold" }}>{t("chat:text.rooSaid")}</span>
{timestampElement}
</div>
<div className="pl-6">
<Markdown markdown={message.text} partial={message.partial} />
Expand All @@ -1219,6 +1237,7 @@ export const ChatRowContent = ({
<div style={headerStyle}>
<User className="w-4 shrink-0" aria-label="User icon" />
<span style={{ fontWeight: "bold" }}>{t("chat:feedback.youSaid")}</span>
{timestampElement}
</div>
<div
className={cn(
Expand Down Expand Up @@ -1336,6 +1355,7 @@ export const ChatRowContent = ({
<div style={headerStyle}>
{icon}
{title}
{timestampElement}
</div>
<div className="border-l border-green-600/30 ml-2 pl-4 pb-1">
<Markdown markdown={message.text} />
Expand Down
6 changes: 6 additions & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
openRouterImageGenerationSelectedModel,
reasoningBlockCollapsed,
enterBehavior,
showTimestamps,
timestampFormat,
includeCurrentTime,
includeCurrentCost,
maxGitStatusFiles,
Expand Down Expand Up @@ -411,6 +413,8 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true,
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
enterBehavior: enterBehavior ?? "send",
showTimestamps: showTimestamps ?? false,
timestampFormat: timestampFormat ?? "24hour",
includeCurrentTime: includeCurrentTime ?? true,
includeCurrentCost: includeCurrentCost ?? true,
maxGitStatusFiles: maxGitStatusFiles ?? 0,
Expand Down Expand Up @@ -830,6 +834,8 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
<UISettings
reasoningBlockCollapsed={reasoningBlockCollapsed ?? true}
enterBehavior={enterBehavior ?? "send"}
showTimestamps={showTimestamps ?? false}
timestampFormat={timestampFormat ?? "24hour"}
setCachedStateField={setCachedStateField}
/>
)}
Expand Down
62 changes: 61 additions & 1 deletion webview-ui/src/components/settings/UISettings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HTMLAttributes, useMemo } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { Glasses } from "lucide-react"
import { telemetryClient } from "@/utils/TelemetryClient"

Expand All @@ -12,12 +12,16 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext"
interface UISettingsProps extends HTMLAttributes<HTMLDivElement> {
reasoningBlockCollapsed: boolean
enterBehavior: "send" | "newline"
showTimestamps: boolean
timestampFormat: "12hour" | "24hour"
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
}

export const UISettings = ({
reasoningBlockCollapsed,
enterBehavior,
showTimestamps,
timestampFormat,
setCachedStateField,
...props
}: UISettingsProps) => {
Expand Down Expand Up @@ -48,6 +52,24 @@ export const UISettings = ({
})
}

const handleShowTimestampsChange = (value: boolean) => {
setCachedStateField("showTimestamps", value)

// Track telemetry event
telemetryClient.capture("ui_settings_show_timestamps_changed", {
enabled: value,
})
}

const handleTimestampFormatChange = (format: "12hour" | "24hour") => {
setCachedStateField("timestampFormat", format)

// Track telemetry event
telemetryClient.capture("ui_settings_timestamp_format_changed", {
format,
})
}

return (
<div {...props}>
<SectionHeader>
Expand Down Expand Up @@ -86,6 +108,44 @@ export const UISettings = ({
{t("settings:ui.requireCtrlEnterToSend.description", { primaryMod })}
</div>
</div>

{/* Show Timestamps Setting */}
<div className="flex flex-col gap-1">
<VSCodeCheckbox
checked={showTimestamps}
onChange={(e: any) => handleShowTimestampsChange(e.target.checked)}
data-testid="show-timestamps-checkbox">
<span className="font-medium">{t("settings:ui.showTimestamps.label")}</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm ml-5 mt-1">
{t("settings:ui.showTimestamps.description")}
</div>
</div>

{/* Timestamp Format Setting - only visible when timestamps are enabled */}
{showTimestamps && (
<div className="flex flex-col gap-1 ml-5">
<div className="flex items-center gap-2">
<span className="font-medium">{t("settings:ui.timestampFormat.label")}</span>
<VSCodeDropdown
value={timestampFormat}
onChange={(e: any) =>
handleTimestampFormatChange(e.target.value as "12hour" | "24hour")
}
data-testid="timestamp-format-dropdown">
<VSCodeOption value="24hour">
{t("settings:ui.timestampFormat.options.24hour")}
</VSCodeOption>
<VSCodeOption value="12hour">
{t("settings:ui.timestampFormat.options.12hour")}
</VSCodeOption>
</VSCodeDropdown>
</div>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{t("settings:ui.timestampFormat.description")}
</div>
</div>
)}
</div>
</Section>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ describe("UISettings", () => {
const defaultProps = {
reasoningBlockCollapsed: false,
enterBehavior: "send" as const,
showTimestamps: false,
timestampFormat: "24hour" as const,
setCachedStateField: vi.fn(),
}

Expand Down
18 changes: 18 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ export interface ExtensionStateContextType extends ExtensionState {
setIncludeCurrentTime: (value: boolean) => void
includeCurrentCost?: boolean
setIncludeCurrentCost: (value: boolean) => void
showTimestamps?: boolean
setShowTimestamps: (value: boolean) => void
timestampFormat?: "12hour" | "24hour"
setTimestampFormat: (value: "12hour" | "24hour") => void
}

export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
Expand Down Expand Up @@ -297,6 +301,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
const [prevCloudIsAuthenticated, setPrevCloudIsAuthenticated] = useState(false)
const [includeCurrentTime, setIncludeCurrentTime] = useState(true)
const [includeCurrentCost, setIncludeCurrentCost] = useState(true)
const [showTimestamps, setShowTimestamps] = useState(false) // Default to false (timestamps hidden)
const [timestampFormat, setTimestampFormat] = useState<"12hour" | "24hour">("24hour") // Default to 24-hour format

const setListApiConfigMeta = useCallback(
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
Expand Down Expand Up @@ -342,6 +348,14 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
if ((newState as any).includeCurrentCost !== undefined) {
setIncludeCurrentCost((newState as any).includeCurrentCost)
}
// Update showTimestamps if present in state message
if ((newState as any).showTimestamps !== undefined) {
setShowTimestamps((newState as any).showTimestamps)
}
// Update timestampFormat if present in state message
if ((newState as any).timestampFormat !== undefined) {
setTimestampFormat((newState as any).timestampFormat)
}
// Handle marketplace data if present in state message
if (newState.marketplaceItems !== undefined) {
setMarketplaceItems(newState.marketplaceItems)
Expand Down Expand Up @@ -592,6 +606,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setIncludeCurrentTime,
includeCurrentCost,
setIncludeCurrentCost,
showTimestamps,
setShowTimestamps,
timestampFormat,
setTimestampFormat,
}

return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
Expand Down
12 changes: 12 additions & 0 deletions webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@
"requireCtrlEnterToSend": {
"label": "Require {{primaryMod}}+Enter to send messages",
"description": "When enabled, you must press {{primaryMod}}+Enter to send messages instead of just Enter"
},
"showTimestamps": {
"label": "Show timestamps on messages",
"description": "When enabled, timestamps will be displayed on the right side of message headers"
},
"timestampFormat": {
"label": "Time format",
"description": "Choose how timestamps are displayed",
"options": {
"12hour": "12-hour (2:34 PM)",
"24hour": "24-hour (14:34)"
}
}
},
"prompts": {
Expand Down
Loading