فهرست منبع

Merge pull request #2528 from mcowger/mcowger/timestamps

Chat View Timestamps
Kevin van Dijk 4 ماه پیش
والد
کامیت
aeaeaf9961
35فایلهای تغییر یافته به همراه391 افزوده شده و 11 حذف شده
  1. 5 0
      .changeset/blue-suits-peel.md
  2. 1 4
      apps/kilocode-docs/docs/basic-usage/autocomplete.md
  3. 1 0
      packages/types/src/global-settings.ts
  4. 3 0
      src/core/webview/ClineProvider.ts
  5. 4 0
      src/core/webview/webviewMessageHandler.ts
  6. 2 0
      src/shared/ExtensionMessage.ts
  7. 1 0
      src/shared/WebviewMessage.ts
  8. 27 5
      webview-ui/src/components/chat/ChatRow.tsx
  9. 18 0
      webview-ui/src/components/chat/ChatTimestamps.tsx
  10. 20 2
      webview-ui/src/components/settings/DisplaySettings.tsx
  11. 3 0
      webview-ui/src/components/settings/SettingsView.tsx
  12. 214 0
      webview-ui/src/components/settings/__tests__/DisplaySettings.spec.tsx
  13. 4 0
      webview-ui/src/context/ExtensionStateContext.tsx
  14. 4 0
      webview-ui/src/i18n/locales/ar/settings.json
  15. 4 0
      webview-ui/src/i18n/locales/ca/settings.json
  16. 4 0
      webview-ui/src/i18n/locales/cs/settings.json
  17. 4 0
      webview-ui/src/i18n/locales/de/settings.json
  18. 4 0
      webview-ui/src/i18n/locales/en/settings.json
  19. 4 0
      webview-ui/src/i18n/locales/es/settings.json
  20. 4 0
      webview-ui/src/i18n/locales/fr/settings.json
  21. 4 0
      webview-ui/src/i18n/locales/hi/settings.json
  22. 4 0
      webview-ui/src/i18n/locales/id/settings.json
  23. 4 0
      webview-ui/src/i18n/locales/it/settings.json
  24. 4 0
      webview-ui/src/i18n/locales/ja/settings.json
  25. 4 0
      webview-ui/src/i18n/locales/ko/settings.json
  26. 4 0
      webview-ui/src/i18n/locales/nl/settings.json
  27. 4 0
      webview-ui/src/i18n/locales/pl/settings.json
  28. 4 0
      webview-ui/src/i18n/locales/pt-BR/settings.json
  29. 4 0
      webview-ui/src/i18n/locales/ru/settings.json
  30. 4 0
      webview-ui/src/i18n/locales/th/settings.json
  31. 4 0
      webview-ui/src/i18n/locales/tr/settings.json
  32. 4 0
      webview-ui/src/i18n/locales/uk/settings.json
  33. 4 0
      webview-ui/src/i18n/locales/vi/settings.json
  34. 4 0
      webview-ui/src/i18n/locales/zh-CN/settings.json
  35. 4 0
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 5 - 0
.changeset/blue-suits-peel.md

@@ -0,0 +1,5 @@
+---
+"kilo-code": minor
+---
+
+Add timestamps to Chat view.

+ 1 - 4
apps/kilocode-docs/docs/basic-usage/autocomplete.md

@@ -66,7 +66,7 @@ You can customize this keyboard shortcut as well in your VS Code settings.
 
 ## Disable Rival Autocomplete
 
-We recommend disabling rival autocompletes to optimize your experience with Kilo Code. To disable GitHub Copilot autocomplete in VSCode, go to **Settings** and navigate to **GitHub** > **Copilot: Advanced** (or search for 'copilot'). 
+We recommend disabling rival autocompletes to optimize your experience with Kilo Code. To disable GitHub Copilot autocomplete in VSCode, go to **Settings** and navigate to **GitHub** > **Copilot: Advanced** (or search for 'copilot').
 
 Then, toggle to 'disabled':
 
@@ -84,9 +84,6 @@ If using Cursor, go to **Settings** > **Cursor Settings** > **Tab**, and toggle
   width="800"
 />
 
-
-
-
 ## Best Practices
 
 1. **Balance speed and quality**: Faster models provide quicker suggestions but may be less accurate

+ 1 - 0
packages/types/src/global-settings.ts

@@ -96,6 +96,7 @@ export const globalSettingsSchema = z.object({
 	browserViewportSize: z.string().optional(),
 	showAutoApproveMenu: z.boolean().optional(), // kilocode_change
 	showTaskTimeline: z.boolean().optional(), // kilocode_change
+	showTimestamps: z.boolean().optional(), // kilocode_change
 	hideCostBelowThreshold: z.number().min(0).optional(), // kilocode_change
 	localWorkflowToggles: z.record(z.string(), z.boolean()).optional(), // kilocode_change
 	globalWorkflowToggles: z.record(z.string(), z.boolean()).optional(), // kilocode_change

+ 3 - 0
src/core/webview/ClineProvider.ts

@@ -1866,6 +1866,7 @@ export class ClineProvider
 			language,
 			showAutoApproveMenu, // kilocode_change
 			showTaskTimeline, // kilocode_change
+			showTimestamps, // kilocode_change
 			hideCostBelowThreshold, // kilocode_change
 			maxReadFileLine,
 			maxImageFileSize,
@@ -2021,6 +2022,7 @@ export class ClineProvider
 			showRooIgnoredFiles: showRooIgnoredFiles ?? false,
 			showAutoApproveMenu: showAutoApproveMenu ?? false, // kilocode_change
 			showTaskTimeline: showTaskTimeline ?? true, // kilocode_change
+			showTimestamps: showTimestamps ?? true, // kilocode_change
 			hideCostBelowThreshold, // kilocode_change
 			language, // kilocode_change
 			renderContext: this.renderContext,
@@ -2275,6 +2277,7 @@ export class ClineProvider
 			showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? false,
 			showAutoApproveMenu: stateValues.showAutoApproveMenu ?? false, // kilocode_change
 			showTaskTimeline: stateValues.showTaskTimeline ?? true, // kilocode_change
+			showTimestamps: stateValues.showTimestamps ?? true, // kilocode_change
 			hideCostBelowThreshold: stateValues.hideCostBelowThreshold ?? 0, // kilocode_change
 			maxReadFileLine: stateValues.maxReadFileLine ?? -1,
 			maxImageFileSize: stateValues.maxImageFileSize ?? 5,

+ 4 - 0
src/core/webview/webviewMessageHandler.ts

@@ -1740,6 +1740,10 @@ export const webviewMessageHandler = async (
 			await updateGlobalState("showTaskTimeline", message.bool ?? false)
 			await provider.postStateToWebview()
 			break
+		case "showTimestamps":
+			await updateGlobalState("showTimestamps", message.bool ?? false)
+			await provider.postStateToWebview()
+			break
 		case "hideCostBelowThreshold":
 			await updateGlobalState("hideCostBelowThreshold", message.value)
 			await provider.postStateToWebview()

+ 2 - 0
src/shared/ExtensionMessage.ts

@@ -152,6 +152,7 @@ export interface ExtensionMessage {
 		| "commands"
 		| "insertTextIntoTextarea"
 		| "dismissedUpsells"
+		| "showTimestamps" // kilocode_change
 		| "organizationSwitchResult"
 	text?: string
 	// kilocode_change start
@@ -438,6 +439,7 @@ export type ExtensionState = Pick<
 	remoteControlEnabled: boolean
 	taskSyncEnabled: boolean
 	featureRoomoteControlEnabled: boolean
+	showTimestamps?: boolean // kilocode_change: Show timestamps in chat messages
 }
 
 export interface ClineSayTool {

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -242,6 +242,7 @@ export interface WebviewMessage {
 		| "getUsageData" // kilocode_change
 		| "usageDataResponse" // kilocode_change
 		| "showTaskTimeline" // kilocode_change
+		| "showTimestamps" // kilocode_change
 		| "hideCostBelowThreshold" // kilocode_change
 		| "toggleTaskFavorite" // kilocode_change
 		| "fixMermaidSyntax" // kilocode_change

+ 27 - 5
webview-ui/src/components/chat/ChatRow.tsx

@@ -65,6 +65,7 @@ import {
 } from "lucide-react"
 import { cn } from "@/lib/utils"
 import { SeeNewChangesButtons } from "./kilocode/SeeNewChangesButtons"
+import ChatTimestamps from "./ChatTimestamps" // kilocode_change
 
 interface ChatRowProps {
 	message: ClineMessage
@@ -146,7 +147,8 @@ export const ChatRowContent = ({
 }: ChatRowContentProps) => {
 	const { t } = useTranslation()
 
-	const { mcpServers, alwaysAllowMcp, currentCheckpoint } = useExtensionState()
+	// kilocode_change: add showTimestamps
+	const { mcpServers, alwaysAllowMcp, currentCheckpoint, showTimestamps } = useExtensionState()
 
 	// Memoized callback to prevent re-renders caused by inline arrow functions.
 	const handleToggleExpand = useCallback(() => {
@@ -1064,7 +1066,12 @@ export const ChatRowContent = ({
 								onClick={handleToggleExpand}>
 								<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
 									{icon}
-									{title}
+									{/* kilocode_change start */}
+									<div style={{ display: "flex", alignItems: "center", gap: "8px", flexGrow: 1 }}>
+										{title}
+										{showTimestamps && <ChatTimestamps ts={message.ts} />}
+									</div>
+									{/* kilocode_change end */}
 								</div>
 								<div
 									className="text-xs text-vscode-dropdown-foreground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
@@ -1171,7 +1178,12 @@ export const ChatRowContent = ({
 						<>
 							<div style={headerStyle}>
 								{icon}
-								{title}
+								{/* kilocode_change start */}
+								<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
+									{title}
+									{showTimestamps && <ChatTimestamps ts={message.ts} />}
+								</div>
+								{/* kilocode_change end */}
 							</div>
 							<div className="border-l border-green-600/30 ml-2 pl-4 pb-1">
 								<Markdown markdown={message.text} />
@@ -1366,7 +1378,12 @@ export const ChatRowContent = ({
 							{title && (
 								<div style={headerStyle}>
 									{icon}
-									{title}
+									{/* kilocode_change start */}
+									<div style={{ display: "flex", alignItems: "center", gap: "8px", flexGrow: 1 }}>
+										{title}
+										{showTimestamps && <ChatTimestamps ts={message.ts} />}
+									</div>
+									{/* kilocode_change end */}
 								</div>
 							)}
 							<div style={{ paddingTop: 10 }}>
@@ -1469,7 +1486,12 @@ export const ChatRowContent = ({
 							{title && (
 								<div style={headerStyle}>
 									{icon}
-									{title}
+									{/* kilocode_change start */}
+									<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
+										{title}
+										{showTimestamps && <ChatTimestamps ts={message.ts} />}
+									</div>
+									{/* kilocode_change start */}
 								</div>
 							)}
 							<div className="flex flex-col gap-2 ml-6">

+ 18 - 0
webview-ui/src/components/chat/ChatTimestamps.tsx

@@ -0,0 +1,18 @@
+import React from "react"
+
+interface ChatTimestampsProps {
+	ts: number
+}
+
+const ChatTimestamps: React.FC<ChatTimestampsProps> = ({ ts }) => {
+	return (
+		<span className="inline-flex items-end text-vscode-descriptionForeground font-normal">
+			{new Date(ts).toLocaleTimeString([], {
+				hour: "2-digit",
+				minute: "2-digit",
+			})}
+		</span>
+	)
+}
+
+export default ChatTimestamps

+ 20 - 2
webview-ui/src/components/settings/DisplaySettings.tsx

@@ -14,16 +14,22 @@ import { Slider } from "../ui"
 
 type DisplaySettingsProps = HTMLAttributes<HTMLDivElement> & {
 	showTaskTimeline?: boolean
+	showTimestamps?: boolean
 	ghostServiceSettings?: any
 	reasoningBlockCollapsed: boolean
 	setCachedStateField: SetCachedStateField<
-		"showTaskTimeline" | "ghostServiceSettings" | "reasoningBlockCollapsed" | "hideCostBelowThreshold"
+		| "showTaskTimeline"
+		| "ghostServiceSettings"
+		| "reasoningBlockCollapsed"
+		| "hideCostBelowThreshold"
+		| "showTimestamps"
 	>
 	hideCostBelowThreshold?: number
 }
 
 export const DisplaySettings = ({
 	showTaskTimeline,
+	showTimestamps,
 	ghostServiceSettings,
 	setCachedStateField,
 	reasoningBlockCollapsed,
@@ -97,7 +103,19 @@ export const DisplaySettings = ({
 						</div>
 					</div>
 				</div>
-
+				{/* Show Timestamps checkbox */}
+				<div className="mt-3">
+					<VSCodeCheckbox
+						checked={showTimestamps}
+						onChange={(e: any) => {
+							setCachedStateField("showTimestamps", e.target.checked)
+						}}>
+						<span className="font-medium">{t("settings:display.showTimestamps.label")}</span>
+					</VSCodeCheckbox>
+					<div className="text-vscode-descriptionForeground text-sm mt-1">
+						{t("settings:display.showTimestamps.description")}
+					</div>
+				</div>
 				{/* Gutter Animation Setting */}
 				<div className="mt-6 pt-6 border-t border-vscode-panel-border">
 					<div className="flex flex-col gap-1">

+ 3 - 0
webview-ui/src/components/settings/SettingsView.tsx

@@ -210,6 +210,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 		maxReadFileLine,
 		showAutoApproveMenu, // kilocode_change
 		showTaskTimeline, // kilocode_change
+		showTimestamps, // kilocode_change
 		hideCostBelowThreshold, // kilocode_change
 		maxImageFileSize,
 		maxTotalImageSize,
@@ -455,6 +456,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 			vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch })
 			vscode.postMessage({ type: "alwaysAllowSubtasks", bool: alwaysAllowSubtasks })
 			vscode.postMessage({ type: "showTaskTimeline", bool: showTaskTimeline }) // kilocode_change
+			vscode.postMessage({ type: "showTimestamps", bool: showTimestamps }) // kilocode_change
 			vscode.postMessage({ type: "hideCostBelowThreshold", value: hideCostBelowThreshold }) // kilocode_change
 			vscode.postMessage({ type: "alwaysAllowFollowupQuestions", bool: alwaysAllowFollowupQuestions })
 			vscode.postMessage({ type: "alwaysAllowUpdateTodoList", bool: alwaysAllowUpdateTodoList })
@@ -844,6 +846,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 						<DisplaySettings
 							reasoningBlockCollapsed={reasoningBlockCollapsed ?? true}
 							showTaskTimeline={showTaskTimeline}
+							showTimestamps={cachedState.showTimestamps} // kilocode_change
 							ghostServiceSettings={ghostServiceSettings}
 							hideCostBelowThreshold={hideCostBelowThreshold}
 							setCachedStateField={setCachedStateField}

+ 214 - 0
webview-ui/src/components/settings/__tests__/DisplaySettings.spec.tsx

@@ -0,0 +1,214 @@
+// npx vitest run src/components/settings/__tests__/DisplaySettings.spec.tsx
+
+import { render, screen, fireEvent } from "@/utils/test-utils"
+import { DisplaySettings } from "../DisplaySettings"
+
+// Mock the translation context
+vi.mock("../../../i18n/TranslationContext", () => ({
+	useAppTranslation: () => ({
+		t: (key: string) => {
+			// Return fixed English strings for tests
+			const translations: { [key: string]: string } = {
+				"settings:sections.display": "Display",
+				"settings:display.taskTimeline.label": "Show task timeline",
+				"settings:display.taskTimeline.description": "Display task messages as a color-coded visual timeline",
+				"settings:display.showTimestamps.label": "Show event timestamps",
+				"settings:display.showTimestamps.description": "Display timestamps for chat messages and events",
+			}
+			return translations[key] || key
+		},
+	}),
+}))
+
+// Mock VSCodeCheckbox to render as regular HTML checkbox for testing
+vi.mock("@vscode/webview-ui-toolkit/react", () => ({
+	VSCodeCheckbox: ({ checked, onChange, children }: any) => (
+		<label>
+			<input
+				type="checkbox"
+				checked={checked}
+				onChange={(e) => onChange({ target: { checked: e.target.checked } })}
+				data-testid="vscode-checkbox"
+			/>
+			{children}
+		</label>
+	),
+}))
+
+// Mock the TaskTimeline component
+vi.mock("../../chat/TaskTimeline", () => ({
+	TaskTimeline: ({ groupedMessages, isTaskActive }: any) => (
+		<div data-testid="task-timeline-preview">
+			TaskTimeline Preview ({groupedMessages?.length || 0} messages, active: {isTaskActive ? "yes" : "no"})
+		</div>
+	),
+}))
+
+// Mock the timeline mockData utility
+vi.mock("../../../utils/timeline/mockData", () => ({
+	generateSampleTimelineData: () => [
+		{ ts: 1000, type: "ask", ask: "command" },
+		{ ts: 2000, type: "say", say: "text" },
+		{ ts: 3000, type: "ask", ask: "tool" },
+	],
+}))
+
+describe("DisplaySettings", () => {
+	const mockSetCachedStateField = vi.fn()
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+	})
+
+	it("renders all display settings components", () => {
+		render(
+			<DisplaySettings
+				showTaskTimeline={true}
+				showTimestamps={false}
+				reasoningBlockCollapsed={false} // kilocode_change
+				setCachedStateField={mockSetCachedStateField}
+			/>,
+		)
+
+		expect(screen.getByText("Display")).toBeInTheDocument()
+		expect(screen.getByText("Show task timeline")).toBeInTheDocument()
+		expect(screen.getByText("Show event timestamps")).toBeInTheDocument()
+		expect(screen.getByTestId("task-timeline-preview")).toBeInTheDocument()
+	})
+
+	it("renders task timeline checkbox with correct initial state", () => {
+		render(
+			<DisplaySettings
+				showTaskTimeline={true}
+				showTimestamps={false}
+				reasoningBlockCollapsed={false} // kilocode_change
+				setCachedStateField={mockSetCachedStateField}
+			/>,
+		)
+
+		const taskTimelineCheckbox = screen.getByText("Show task timeline").closest("label")
+		const checkbox = taskTimelineCheckbox?.querySelector('input[type="checkbox"]') as HTMLInputElement
+
+		expect(checkbox).toBeChecked()
+	})
+
+	it("renders showTimestamps checkbox with correct initial state", () => {
+		render(
+			<DisplaySettings
+				showTaskTimeline={true}
+				showTimestamps={false}
+				reasoningBlockCollapsed={false} // kilocode_change
+				setCachedStateField={mockSetCachedStateField}
+			/>,
+		)
+
+		const showTimestampsCheckbox = screen.getByText("Show event timestamps").closest("label")
+		const checkbox = showTimestampsCheckbox?.querySelector('input[type="checkbox"]') as HTMLInputElement
+
+		expect(checkbox).not.toBeChecked()
+	})
+
+	it("calls setCachedStateField when task timeline checkbox is toggled", () => {
+		render(
+			<DisplaySettings
+				showTaskTimeline={true}
+				showTimestamps={false}
+				reasoningBlockCollapsed={false} // kilocode_change
+				setCachedStateField={mockSetCachedStateField}
+			/>,
+		)
+
+		const taskTimelineCheckbox = screen.getByText("Show task timeline").closest("label")
+		const checkbox = taskTimelineCheckbox?.querySelector('input[type="checkbox"]') as HTMLInputElement
+
+		fireEvent.click(checkbox)
+
+		expect(mockSetCachedStateField).toHaveBeenCalledWith("showTaskTimeline", false)
+	})
+
+	it("calls setCachedStateField when showTimestamps checkbox is toggled", () => {
+		render(
+			<DisplaySettings
+				showTaskTimeline={true}
+				showTimestamps={false}
+				reasoningBlockCollapsed={false} // kilocode_change
+				setCachedStateField={mockSetCachedStateField}
+			/>,
+		)
+
+		const showTimestampsCheckbox = screen.getByText("Show event timestamps").closest("label")
+		const checkbox = showTimestampsCheckbox?.querySelector('input[type="checkbox"]') as HTMLInputElement
+
+		fireEvent.click(checkbox)
+
+		expect(mockSetCachedStateField).toHaveBeenCalledWith("showTimestamps", true)
+	})
+
+	it("renders task timeline preview with sample data", () => {
+		render(
+			<DisplaySettings
+				showTaskTimeline={true}
+				showTimestamps={false}
+				reasoningBlockCollapsed={false} // kilocode_change
+				setCachedStateField={mockSetCachedStateField}
+			/>,
+		)
+
+		const preview = screen.getByTestId("task-timeline-preview")
+		expect(preview).toHaveTextContent("TaskTimeline Preview (3 messages, active: no)")
+	})
+
+	it("renders descriptions for both settings", () => {
+		render(
+			<DisplaySettings
+				showTaskTimeline={true}
+				showTimestamps={false}
+				reasoningBlockCollapsed={false} // kilocode_change
+				setCachedStateField={mockSetCachedStateField}
+			/>,
+		)
+
+		expect(screen.getByText("Display task messages as a color-coded visual timeline")).toBeInTheDocument()
+		expect(screen.getByText("Display timestamps for chat messages and events")).toBeInTheDocument()
+	})
+
+	it("handles both checkboxes being checked", () => {
+		render(
+			<DisplaySettings
+				showTaskTimeline={true}
+				showTimestamps={true}
+				reasoningBlockCollapsed={false} // kilocode_change
+				setCachedStateField={mockSetCachedStateField}
+			/>,
+		)
+
+		const taskTimelineCheckbox = screen.getByText("Show task timeline").closest("label")
+		const showTimestampsCheckbox = screen.getByText("Show event timestamps").closest("label")
+
+		const taskTimelineInput = taskTimelineCheckbox?.querySelector('input[type="checkbox"]') as HTMLInputElement
+		const showTimestampsInput = showTimestampsCheckbox?.querySelector('input[type="checkbox"]') as HTMLInputElement
+
+		expect(taskTimelineInput).toBeChecked()
+		expect(showTimestampsInput).toBeChecked()
+	})
+
+	it("handles both checkboxes being unchecked", () => {
+		render(
+			<DisplaySettings
+				showTaskTimeline={false}
+				showTimestamps={false}
+				reasoningBlockCollapsed={false} // kilocode_change
+				setCachedStateField={mockSetCachedStateField}
+			/>,
+		)
+
+		const taskTimelineCheckbox = screen.getByText("Show task timeline").closest("label")
+		const showTimestampsCheckbox = screen.getByText("Show event timestamps").closest("label")
+
+		const taskTimelineInput = taskTimelineCheckbox?.querySelector('input[type="checkbox"]') as HTMLInputElement
+		const showTimestampsInput = showTimestampsCheckbox?.querySelector('input[type="checkbox"]') as HTMLInputElement
+
+		expect(taskTimelineInput).not.toBeChecked()
+		expect(showTimestampsInput).not.toBeChecked()
+	})
+})

+ 4 - 0
webview-ui/src/context/ExtensionStateContext.tsx

@@ -33,6 +33,8 @@ export interface ExtensionStateContextType extends ExtensionState {
 	historyPreviewCollapsed?: boolean
 	showTaskTimeline?: boolean // kilocode_change
 	setShowTaskTimeline: (value: boolean) => void // kilocode_change
+	showTimestamps?: boolean // kilocode_change
+	setShowTimestamps: (value: boolean) => void // kilocode_change
 	hideCostBelowThreshold?: number // kilocode_change
 	setHideCostBelowThreshold: (value: number) => void // kilocode_change
 	hoveringTaskTimeline?: boolean // kilocode_change
@@ -277,6 +279,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		terminalCompressProgressBar: true, // Default to compress progress bar output
 		historyPreviewCollapsed: false, // Initialize the new state (default to expanded)
 		showTaskTimeline: true, // kilocode_change
+		showTimestamps: true, // kilocode_change
 		kilocodeDefaultModel: openRouterDefaultModelId,
 		reasoningBlockCollapsed: true, // Default to collapsed
 		cloudUserInfo: null,
@@ -587,6 +590,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		setHideCostBelowThreshold: (value) =>
 			setState((prevState) => ({ ...prevState, hideCostBelowThreshold: value })),
 		setHoveringTaskTimeline: (value) => setState((prevState) => ({ ...prevState, hoveringTaskTimeline: value })),
+		setShowTimestamps: (value) => setState((prevState) => ({ ...prevState, showTimestamps: value })),
 		// kilocode_change end
 		setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })),
 		setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })),

+ 4 - 0
webview-ui/src/i18n/locales/ar/settings.json

@@ -606,6 +606,10 @@
 			"label": "إخفاء التكلفة أقل من الحد الأدنى",
 			"currentValue": "الحد الحالي: ${{value}}",
 			"description": "إظهار تكاليف الطلب فقط عندما تتجاوز هذا المبلغ. التكاليف تكون مرئية دائمًا عندما تكون لوحة التفاصيل مفتوحة."
+		},
+		"showTimestamps": {
+			"label": "إظهار الطوابع الزمنية",
+			"description": "إظهار الطابع الزمني لكل رسالة في عرض المحادثة"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/ca/settings.json

@@ -931,6 +931,10 @@
 			"label": "Amaga el cost per sota del llindar",
 			"description": "Mostra només els costos de sol·licituds superiors a aquesta quantitat. Els costos sempre són visibles quan el panell de detalls està obert.",
 			"currentValue": "Llindar actual: ${{value}}"
+		},
+		"showTimestamps": {
+			"label": "Mostrar marca de temps",
+			"description": "Mostrar la marca de temps per a cada missatge a la vista del xat"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/cs/settings.json

@@ -606,6 +606,10 @@
 			"label": "Skrýt cenu pod prahem",
 			"currentValue": "Aktuální práh: ${{value}}",
 			"description": "Zobrazovat pouze náklady na požadavky převyšující tuto částku. Náklady jsou vždy viditelné, když je panel podrobností otevřený."
+		},
+		"showTimestamps": {
+			"label": "Zobrazit časová razítka",
+			"description": "Zobrazit časové razítko pro každou zprávu v zobrazení chatu"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/de/settings.json

@@ -927,6 +927,10 @@
 			"label": "Kosten unter Schwellenwert ausblenden",
 			"description": "Nur Anforderungskosten über diesem Betrag anzeigen. Kosten sind immer sichtbar, wenn der Detailbereich geöffnet ist.",
 			"currentValue": "Aktueller Schwellenwert: ${{value}}"
+		},
+		"showTimestamps": {
+			"label": "Zeitstempel anzeigen",
+			"description": "Zeitstempel für jede Nachricht in der Chat-Ansicht anzeigen"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/en/settings.json

@@ -565,6 +565,10 @@
 			"label": "Hide cost below threshold",
 			"description": "Only show request costs above this amount. Costs are always visible when the details pane is open.",
 			"currentValue": "Current threshold: ${{value}}"
+		},
+		"showTimestamps": {
+			"label": "Show timestamps",
+			"description": "Show timestamp for every message in the chat view"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/es/settings.json

@@ -931,6 +931,10 @@
 			"label": "Ocultar costo por debajo del umbral",
 			"description": "Solo mostrar los costos de solicitud superiores a este monto. Los costos siempre son visibles cuando el panel de detalles está abierto.",
 			"currentValue": "Umbral actual: ${{value}}"
+		},
+		"showTimestamps": {
+			"label": "Mostrar marcas de tiempo",
+			"description": "Mostrar la marca de tiempo para cada mensaje en la vista del chat"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/fr/settings.json

@@ -931,6 +931,10 @@
 			"label": "Masquer le coût en dessous du seuil",
 			"currentValue": "Seuil actuel : ${{value}}",
 			"description": "N'afficher que les coûts de requête supérieurs à ce montant. Les coûts sont toujours visibles lorsque le volet de détails est ouvert."
+		},
+		"showTimestamps": {
+			"label": "Afficher les horodatages",
+			"description": "Afficher l'horodatage pour chaque message dans la vue de discussion"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/hi/settings.json

@@ -932,6 +932,10 @@
 			"label": "थ्रेशोल्ड से नीचे की लागत छिपाएं",
 			"currentValue": "वर्तमान थ्रेशोल्ड: ${{value}}",
 			"description": "केवल इस राशि से अधिक के अनुरोध की लागतों को दिखाएं। जब विवरण पेन खुला होता है, तो लागतें हमेशा दिखाई देती हैं।"
+		},
+		"showTimestamps": {
+			"label": "टाइमस्टैम्प दिखाएं",
+			"description": "चैट दृश्य में प्रत्येक संदेश के लिए टाइमस्टैम्प दिखाएं"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/id/settings.json

@@ -953,6 +953,10 @@
 			"label": "Sembunyikan biaya di bawah ambang batas",
 			"currentValue": "Ambang batas saat ini: ${{value}}",
 			"description": "Hanya tampilkan biaya permintaan di atas jumlah ini. Biaya selalu terlihat saat panel detail terbuka."
+		},
+		"showTimestamps": {
+			"label": "Tampilkan stempel waktu",
+			"description": "Tampilkan stempel waktu untuk setiap pesan di tampilan obrolan"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/it/settings.json

@@ -933,6 +933,10 @@
 			"label": "Nascondi costo sotto la soglia",
 			"currentValue": "Soglia attuale: ${{value}}",
 			"description": "Mostra solo i costi delle richieste superiori a questo importo. I costi sono sempre visibili quando il riquadro dei dettagli è aperto."
+		},
+		"showTimestamps": {
+			"label": "Mostra timestamp",
+			"description": "Mostra il timestamp per ogni messaggio nella vista chat"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/ja/settings.json

@@ -933,6 +933,10 @@
 			"label": "しきい値以下のコストを非表示にする",
 			"currentValue": "現在のしきい値: ${{value}}",
 			"description": "この金額を超えるリクエストコストのみを表示します。詳細パネルが開いている場合、コストは常に表示されます。"
+		},
+		"showTimestamps": {
+			"label": "タイムスタンプを表示",
+			"description": "チャット表示で各メッセージにタイムスタンプを表示する"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/ko/settings.json

@@ -932,6 +932,10 @@
 			"description": "해당 금액 이상의 요청비용만 표시합니다. 세부정보 창이 열려있을 때는 항상 비용이 표시됩니다.",
 			"label": "임계값 이하 비용 숨기기",
 			"currentValue": "현재 임계값: ${{value}}"
+		},
+		"showTimestamps": {
+			"label": "타임스탬프 표시",
+			"description": "채팅 화면에서 각 메시지의 타임스탬프 표시"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/nl/settings.json

@@ -932,6 +932,10 @@
 			"label": "Kosten onder drempel verbergen",
 			"currentValue": "Huidige drempelwaarde: ${{value}}",
 			"description": "Toon alleen aanvraagkosten hoger dan dit bedrag. Kosten zijn altijd zichtbaar wanneer het detailpaneel is geopend."
+		},
+		"showTimestamps": {
+			"label": "Tijdstempels weergeven",
+			"description": "Toon tijdstempel voor elk bericht in de chatweergave"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/pl/settings.json

@@ -932,6 +932,10 @@
 			"currentValue": "Aktualny próg: ${{value}}",
 			"label": "Ukryj koszt poniżej progu",
 			"description": "Pokaż koszty zapytań tylko powyżej tej kwoty. Koszty są zawsze widoczne, gdy panel szczegółów jest otwarty."
+		},
+		"showTimestamps": {
+			"label": "Pokaż znaczniki czasu",
+			"description": "Pokaż znacznik czasu dla każdej wiadomości w widoku czatu"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/pt-BR/settings.json

@@ -932,6 +932,10 @@
 			"label": "Ocultar custo abaixo do limite",
 			"currentValue": "Limiar atual: ${{value}}",
 			"description": "Apenas mostrar custos de solicitação acima deste valor. Os custos estão sempre visíveis quando o painel de detalhes está aberto."
+		},
+		"showTimestamps": {
+			"label": "Mostrar carimbos de tempo",
+			"description": "Mostrar carimbo de tempo para cada mensagem na visualização do chat"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/ru/settings.json

@@ -932,6 +932,10 @@
 			"label": "Скрыть стоимость ниже порога",
 			"description": "Показывать только расходы на запросы выше этой суммы. Расходы всегда видны, когда открыта панель подробностей.",
 			"currentValue": "Текущее пороговое значение: ${{value}}"
+		},
+		"showTimestamps": {
+			"label": "Показывать временные метки",
+			"description": "Показывать временную метку для каждого сообщения в представлении чата"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/th/settings.json

@@ -591,6 +591,10 @@
 			"label": "ซ่อนต้นทุนที่ต่ำกว่าเกณฑ์",
 			"currentValue": "เกณฑ์ปัจจุบัน: ${{value}}",
 			"description": "แสดงค่าใช้จ่ายคำขอเฉพาะที่เกินกว่าจำนวนนี้ ค่าใช้จ่ายจะมองเห็นได้เสมอเมื่อเปิดบานหน้าต่างรายละเอียด"
+		},
+		"showTimestamps": {
+			"label": "แสดงตราเวลา",
+			"description": "แสดงตราเวลาสำหรับข้อความแต่ละข้อในมุมมองแชต"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/tr/settings.json

@@ -933,6 +933,10 @@
 			"label": "Eşik değerin altındaki maliyeti gizle",
 			"description": "Sadece bu miktarın üzerindeki istek maliyetlerini göster. Ayrıntılar paneli açıkken maliyetler her zaman görünür.",
 			"currentValue": "Mevcut eşik: ${{value}}"
+		},
+		"showTimestamps": {
+			"label": "Zaman damgalarını göster",
+			"description": "Sohbet görünümündeki her mesaj için zaman damgası göster"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/uk/settings.json

@@ -608,6 +608,10 @@
 			"label": "Сховати вартість нижче порогового значення",
 			"currentValue": "Поточний поріг: ${{value}}",
 			"description": "Показувати лише витрати на запити, що перевищують цю суму. Витрати завжди видно, коли відкрита панель деталей."
+		},
+		"showTimestamps": {
+			"label": "Показувати часові мітки",
+			"description": "Показувати часову мітку для кожного повідомлення у вікні чату"
 		}
 	},
 	"notifications": {

+ 4 - 0
webview-ui/src/i18n/locales/vi/settings.json

@@ -932,6 +932,10 @@
 			"label": "Ẩn chi phí dưới ngưỡng",
 			"description": "Chỉ hiển thị chi phí yêu cầu trên mức này. Chi phí luôn được hiển thị khi khung thông tin chi tiết được mở.",
 			"currentValue": "Ngưỡng hiện tại: ${{value}}"
+		},
+		"showTimestamps": {
+			"label": "Hiển thị dấu thời gian",
+			"description": "Hiển thị dấu thời gian cho mỗi tin nhắn trong giao diện chat"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/zh-CN/settings.json

@@ -937,6 +937,10 @@
 			"label": "隐藏低于阈值的费用",
 			"description": "仅显示超过此金额的请求成本。当详情面板打开时,成本始终可见。",
 			"currentValue": "当前阈值:${{value}}"
+		},
+		"showTimestamps": {
+			"label": "显示时间戳",
+			"description": "在聊天视图中为每条消息显示时间戳"
 		}
 	},
 	"ghost": {

+ 4 - 0
webview-ui/src/i18n/locales/zh-TW/settings.json

@@ -933,6 +933,10 @@
 			"description": "仅显示超过此金额的请求费用。当详细信息面板打开时,费用始终可见。",
 			"label": "隐藏低于阈值的费用",
 			"currentValue": "当前阈值:${{value}}"
+		},
+		"showTimestamps": {
+			"label": "顯示時間戳記",
+			"description": "在聊天檢視中顯示每則訊息的時間戳記"
 		}
 	},
 	"ghost": {