继续处理会话区多语言

This commit is contained in:
xintaofei
2026-03-07 14:33:47 +08:00
parent 89c91ac1eb
commit 17a03ef95c
9 changed files with 248 additions and 69 deletions

View File

@@ -5,6 +5,7 @@ import type { ComponentProps } from "react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { ArrowDownIcon, DownloadIcon } from "lucide-react" import { ArrowDownIcon, DownloadIcon } from "lucide-react"
import { useTranslations } from "next-intl"
import { useCallback } from "react" import { useCallback } from "react"
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom" import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom"
@@ -42,12 +43,14 @@ export type MessageThreadEmptyStateProps = ComponentProps<"div"> & {
export const MessageThreadEmptyState = ({ export const MessageThreadEmptyState = ({
className, className,
title = "No messages yet", title,
description = "Start a conversation to see messages here", description,
icon, icon,
children, children,
...props ...props
}: MessageThreadEmptyStateProps) => ( }: MessageThreadEmptyStateProps) => {
const t = useTranslations("Folder.chat.messageThread")
return (
<div <div
className={cn( className={cn(
"flex size-full flex-col items-center justify-center gap-3 p-8 text-center", "flex size-full flex-col items-center justify-center gap-3 p-8 text-center",
@@ -59,15 +62,18 @@ export const MessageThreadEmptyState = ({
<> <>
{icon && <div className="text-muted-foreground">{icon}</div>} {icon && <div className="text-muted-foreground">{icon}</div>}
<div className="space-y-1"> <div className="space-y-1">
<h3 className="font-medium text-sm">{title}</h3> <h3 className="font-medium text-sm">{title ?? t("emptyTitle")}</h3>
{description && ( {(description ?? t("emptyDescription")) && (
<p className="text-muted-foreground text-sm">{description}</p> <p className="text-muted-foreground text-sm">
{description ?? t("emptyDescription")}
</p>
)} )}
</div> </div>
</> </>
)} )}
</div> </div>
) )
}
export type MessageThreadScrollButtonProps = ComponentProps<typeof Button> export type MessageThreadScrollButtonProps = ComponentProps<typeof Button>

View File

@@ -323,6 +323,7 @@ export const ToolOutput = ({
errorText, errorText,
...props ...props
}: ToolOutputProps) => { }: ToolOutputProps) => {
const t = useTranslations("Folder.chat.tool")
if (!(output || errorText)) { if (!(output || errorText)) {
return null return null
} }
@@ -348,7 +349,7 @@ export const ToolOutput = ({
return ( return (
<div className={cn("space-y-2", className)} {...props}> <div className={cn("space-y-2", className)} {...props}>
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide"> <h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
{errorText ? "Error" : "Result"} {errorText ? t("error") : t("result")}
</h4> </h4>
<div <div
className={cn( className={cn(

View File

@@ -1084,6 +1084,7 @@ function sanitizeLiveTitle(title: string | null | undefined): string | null {
/** Edit tool: file path + unified diff view */ /** Edit tool: file path + unified diff view */
function EditToolInput({ input }: { input: Record<string, unknown> }) { function EditToolInput({ input }: { input: Record<string, unknown> }) {
const t = useTranslations("Folder.chat.contentParts")
const filePath = str(input, "file_path") const filePath = str(input, "file_path")
const oldString = str(input, "old_string") ?? "" const oldString = str(input, "old_string") ?? ""
const newString = str(input, "new_string") ?? "" const newString = str(input, "new_string") ?? ""
@@ -1109,11 +1110,11 @@ function EditToolInput({ input }: { input: Record<string, unknown> }) {
<div className="flex items-center gap-2 text-xs"> <div className="flex items-center gap-2 text-xs">
<FilePenLineIcon className="size-3.5 shrink-0 text-muted-foreground" /> <FilePenLineIcon className="size-3.5 shrink-0 text-muted-foreground" />
<span className="break-all font-mono text-foreground"> <span className="break-all font-mono text-foreground">
{filePath ?? "unknown"} {filePath ?? t("unknown")}
</span> </span>
{replaceAll && ( {replaceAll && (
<span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground"> <span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
REPLACE ALL {t("replaceAll")}
</span> </span>
)} )}
</div> </div>
@@ -1124,6 +1125,7 @@ function EditToolInput({ input }: { input: Record<string, unknown> }) {
/** Edit tool (changes payload): file list + summary + combined diff view */ /** Edit tool (changes payload): file list + summary + combined diff view */
function EditChangesToolInput({ changes }: { changes: EditChangePreview[] }) { function EditChangesToolInput({ changes }: { changes: EditChangePreview[] }) {
const t = useTranslations("Folder.chat.contentParts")
const { additions, deletions, diffCode } = useMemo(() => { const { additions, deletions, diffCode } = useMemo(() => {
let additions = 0 let additions = 0
let deletions = 0 let deletions = 0
@@ -1167,7 +1169,7 @@ function EditChangesToolInput({ changes }: { changes: EditChangePreview[] }) {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex flex-wrap gap-3 text-xs text-muted-foreground"> <div className="flex flex-wrap gap-3 text-xs text-muted-foreground">
<span>Files: {changes.length}</span> <span>{t("filesCount", { count: changes.length })}</span>
{additions > 0 && <span>+{additions}</span>} {additions > 0 && <span>+{additions}</span>}
{deletions > 0 && <span>-{deletions}</span>} {deletions > 0 && <span>-{deletions}</span>}
</div> </div>
@@ -1175,7 +1177,7 @@ function EditChangesToolInput({ changes }: { changes: EditChangePreview[] }) {
{changes.slice(0, 8).map((change, index) => ( {changes.slice(0, 8).map((change, index) => (
<div key={`${change.path}-${index}`} className="flex gap-2 text-xs"> <div key={`${change.path}-${index}`} className="flex gap-2 text-xs">
<span className="shrink-0 rounded bg-blue-500/15 px-1.5 py-0.5 font-medium uppercase text-blue-600"> <span className="shrink-0 rounded bg-blue-500/15 px-1.5 py-0.5 font-medium uppercase text-blue-600">
update {t("update")}
</span> </span>
<span className="break-all font-mono text-foreground"> <span className="break-all font-mono text-foreground">
{change.path} {change.path}
@@ -1184,7 +1186,7 @@ function EditChangesToolInput({ changes }: { changes: EditChangePreview[] }) {
))} ))}
{changes.length > 8 && ( {changes.length > 8 && (
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
+{changes.length - 8} more files {t("moreFiles", { count: changes.length - 8 })}
</div> </div>
)} )}
</div> </div>
@@ -1195,6 +1197,7 @@ function EditChangesToolInput({ changes }: { changes: EditChangePreview[] }) {
/** Bash / exec_command: terminal-style command display */ /** Bash / exec_command: terminal-style command display */
function BashToolInput({ input }: { input: Record<string, unknown> }) { function BashToolInput({ input }: { input: Record<string, unknown> }) {
const t = useTranslations("Folder.chat.contentParts")
const command = const command =
commandFromUnknownValue(input) ?? commandFromUnknownValue(input) ??
str(input, "command") ?? str(input, "command") ??
@@ -1216,8 +1219,8 @@ function BashToolInput({ input }: { input: Record<string, unknown> }) {
{displayCommand && <CodeBlock code={displayCommand} language="bash" />} {displayCommand && <CodeBlock code={displayCommand} language="bash" />}
{(timeout || background) && ( {(timeout || background) && (
<div className="flex flex-wrap gap-3 text-xs text-muted-foreground"> <div className="flex flex-wrap gap-3 text-xs text-muted-foreground">
{timeout && <span>Timeout: {timeout}ms</span>} {timeout && <span>{t("timeoutMs", { timeout })}</span>}
{background && <span>Background: true</span>} {background && <span>{t("backgroundTrue")}</span>}
</div> </div>
)} )}
</div> </div>
@@ -1232,6 +1235,7 @@ function FileToolInput({
toolName: string toolName: string
input: Record<string, unknown> input: Record<string, unknown>
}) { }) {
const t = useTranslations("Folder.chat.contentParts")
const name = toolName.toLowerCase() const name = toolName.toLowerCase()
const filePath = const filePath =
str(input, "file_path") ?? str(input, "path") ?? str(input, "notebook_path") str(input, "file_path") ?? str(input, "path") ?? str(input, "notebook_path")
@@ -1263,15 +1267,15 @@ function FileToolInput({
)} )}
{(offset != null || limit != null || pages) && ( {(offset != null || limit != null || pages) && (
<div className="flex flex-wrap gap-3 text-xs text-muted-foreground"> <div className="flex flex-wrap gap-3 text-xs text-muted-foreground">
{offset != null && <span>Offset: {offset}</span>} {offset != null && <span>{t("offset", { offset })}</span>}
{limit != null && <span>Limit: {limit}</span>} {limit != null && <span>{t("limit", { limit })}</span>}
{pages && <span>Pages: {pages}</span>} {pages && <span>{t("pages", { pages })}</span>}
</div> </div>
)} )}
{(cellType || editMode) && ( {(cellType || editMode) && (
<div className="flex flex-wrap gap-3 text-xs text-muted-foreground"> <div className="flex flex-wrap gap-3 text-xs text-muted-foreground">
{editMode && <span>Mode: {editMode}</span>} {editMode && <span>{t("mode", { mode: editMode })}</span>}
{cellType && <span>Cell: {cellType}</span>} {cellType && <span>{t("cell", { cell: cellType })}</span>}
</div> </div>
)} )}
{(name === "write" || name === "notebookedit") && {(name === "write" || name === "notebookedit") &&
@@ -1295,6 +1299,7 @@ function SearchToolInput({
toolName: string toolName: string
input: Record<string, unknown> input: Record<string, unknown>
}) { }) {
const t = useTranslations("Folder.chat.contentParts")
const name = toolName.toLowerCase() const name = toolName.toLowerCase()
const pattern = str(input, "pattern") const pattern = str(input, "pattern")
const path = str(input, "path") const path = str(input, "path")
@@ -1315,27 +1320,30 @@ function SearchToolInput({
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground"> <div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
{path && ( {path && (
<span> <span>
Path: <span className="font-mono text-foreground">{path}</span> {t("pathLabel")}{" "}
<span className="font-mono text-foreground">{path}</span>
</span> </span>
)} )}
{glob && ( {glob && (
<span> <span>
Glob: <span className="font-mono text-foreground">{glob}</span> {t("globLabel")}{" "}
<span className="font-mono text-foreground">{glob}</span>
</span> </span>
)} )}
{fileType && ( {fileType && (
<span> <span>
Type: <span className="font-mono text-foreground">{fileType}</span> {t("typeLabel")}{" "}
<span className="font-mono text-foreground">{fileType}</span>
</span> </span>
)} )}
{name === "grep" && outputMode && ( {name === "grep" && outputMode && (
<span> <span>
Output:{" "} {t("outputLabel")}{" "}
<span className="font-mono text-foreground">{outputMode}</span> <span className="font-mono text-foreground">{outputMode}</span>
</span> </span>
)} )}
{caseInsensitive && <span>Case insensitive</span>} {caseInsensitive && <span>{t("caseInsensitive")}</span>}
{multiline && <span>Multiline</span>} {multiline && <span>{t("multiline")}</span>}
</div> </div>
</div> </div>
) )
@@ -1349,6 +1357,7 @@ function WebToolInput({
toolName: string toolName: string
input: Record<string, unknown> input: Record<string, unknown>
}) { }) {
const t = useTranslations("Folder.chat.contentParts")
const name = toolName.toLowerCase() const name = toolName.toLowerCase()
const url = str(input, "url") const url = str(input, "url")
const query = str(input, "query") const query = str(input, "query")
@@ -1375,7 +1384,7 @@ function WebToolInput({
{prompt && ( {prompt && (
<div className="space-y-1"> <div className="space-y-1">
<span className="text-xs font-medium text-muted-foreground"> <span className="text-xs font-medium text-muted-foreground">
Prompt {t("promptLabel")}
</span> </span>
<div className="rounded-md bg-muted/50 p-3 text-xs prose prose-sm dark:prose-invert max-w-none [&_ul]:list-inside [&_ol]:list-inside"> <div className="rounded-md bg-muted/50 p-3 text-xs prose prose-sm dark:prose-invert max-w-none [&_ul]:list-inside [&_ol]:list-inside">
<MessageResponse>{prompt}</MessageResponse> <MessageResponse>{prompt}</MessageResponse>
@@ -1388,6 +1397,7 @@ function WebToolInput({
/** Task tools: description / subject focused */ /** Task tools: description / subject focused */
function TaskToolInput({ input }: { input: Record<string, unknown> }) { function TaskToolInput({ input }: { input: Record<string, unknown> }) {
const t = useTranslations("Folder.chat.contentParts")
const subject = str(input, "subject") const subject = str(input, "subject")
const taskId = str(input, "taskId") const taskId = str(input, "taskId")
const status = str(input, "status") const status = str(input, "status")
@@ -1401,7 +1411,7 @@ function TaskToolInput({ input }: { input: Record<string, unknown> }) {
{subject && ( {subject && (
<div className="flex items-baseline gap-2 text-xs"> <div className="flex items-baseline gap-2 text-xs">
<span className="shrink-0 font-medium text-muted-foreground"> <span className="shrink-0 font-medium text-muted-foreground">
Subject {t("subjectLabel")}
</span> </span>
<span className="text-foreground">{subject}</span> <span className="text-foreground">{subject}</span>
</div> </div>
@@ -1409,7 +1419,7 @@ function TaskToolInput({ input }: { input: Record<string, unknown> }) {
{taskId && ( {taskId && (
<div className="flex items-baseline gap-2 text-xs"> <div className="flex items-baseline gap-2 text-xs">
<span className="shrink-0 font-medium text-muted-foreground"> <span className="shrink-0 font-medium text-muted-foreground">
Task {t("taskLabel")}
</span> </span>
<span className="font-mono text-foreground"> <span className="font-mono text-foreground">
#{taskId} #{taskId}
@@ -1419,7 +1429,8 @@ function TaskToolInput({ input }: { input: Record<string, unknown> }) {
)} )}
{agentName && ( {agentName && (
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
Name: <span className="font-mono text-foreground">{agentName}</span> {t("nameLabel")}{" "}
<span className="font-mono text-foreground">{agentName}</span>
</div> </div>
)} )}
</div> </div>
@@ -1487,6 +1498,7 @@ function TodoWriteToolInput({ input }: { input: Record<string, unknown> }) {
} }
function ApplyPatchToolInput({ input }: { input: string }) { function ApplyPatchToolInput({ input }: { input: string }) {
const t = useTranslations("Folder.chat.contentParts")
const { files, additions, deletions } = useMemo( const { files, additions, deletions } = useMemo(
() => parseApplyPatchInput(input), () => parseApplyPatchInput(input),
[input] [input]
@@ -1501,7 +1513,7 @@ function ApplyPatchToolInput({ input }: { input: string }) {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex flex-wrap gap-3 text-xs text-muted-foreground"> <div className="flex flex-wrap gap-3 text-xs text-muted-foreground">
<span>Files: {files.length}</span> <span>{t("filesCount", { count: files.length })}</span>
{additions > 0 && <span>+{additions}</span>} {additions > 0 && <span>+{additions}</span>}
{deletions > 0 && <span>-{deletions}</span>} {deletions > 0 && <span>-{deletions}</span>}
</div> </div>
@@ -1521,7 +1533,7 @@ function ApplyPatchToolInput({ input }: { input: string }) {
))} ))}
{files.length > 8 && ( {files.length > 8 && (
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
+{files.length - 8} more files {t("moreFiles", { count: files.length - 8 })}
</div> </div>
)} )}
</div> </div>

View File

@@ -2,6 +2,7 @@
import { useState } from "react" import { useState } from "react"
import { ChevronRight, ChevronDown, Wrench, AlertCircle } from "lucide-react" import { ChevronRight, ChevronDown, Wrench, AlertCircle } from "lucide-react"
import { useTranslations } from "next-intl"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
interface ToolCallBlockProps { interface ToolCallBlockProps {
@@ -17,6 +18,7 @@ export function ToolCallBlock({
content, content,
isError = false, isError = false,
}: ToolCallBlockProps) { }: ToolCallBlockProps) {
const t = useTranslations("Folder.chat.toolCallBlock")
const [expanded, setExpanded] = useState(false) const [expanded, setExpanded] = useState(false)
return ( return (
@@ -40,7 +42,7 @@ export function ToolCallBlock({
{type === "tool_use" ? ( {type === "tool_use" ? (
<> <>
<Wrench className="h-3 w-3 shrink-0 text-muted-foreground" /> <Wrench className="h-3 w-3 shrink-0 text-muted-foreground" />
<span className="font-medium">{toolName || "Tool"}</span> <span className="font-medium">{toolName || t("tool")}</span>
</> </>
) : ( ) : (
<> <>
@@ -49,7 +51,9 @@ export function ToolCallBlock({
) : ( ) : (
<Wrench className="h-3 w-3 shrink-0 text-muted-foreground" /> <Wrench className="h-3 w-3 shrink-0 text-muted-foreground" />
)} )}
<span className="font-medium">{isError ? "Error" : "Result"}</span> <span className="font-medium">
{isError ? t("error") : t("result")}
</span>
</> </>
)} )}
</button> </button>

View File

@@ -10,6 +10,7 @@ import {
useMemo, useMemo,
type ReactNode, type ReactNode,
} from "react" } from "react"
import { useTranslations } from "next-intl"
import { useFolderContext } from "@/contexts/folder-context" import { useFolderContext } from "@/contexts/folder-context"
import { useWorkspaceContext } from "@/contexts/workspace-context" import { useWorkspaceContext } from "@/contexts/workspace-context"
import { saveFolderOpenedConversations } from "@/lib/tauri" import { saveFolderOpenedConversations } from "@/lib/tauri"
@@ -107,6 +108,7 @@ interface TabProviderProps {
} }
export function TabProvider({ children }: TabProviderProps) { export function TabProvider({ children }: TabProviderProps) {
const t = useTranslations("Folder.tabContext")
const { activateConversationPane } = useWorkspaceContext() const { activateConversationPane } = useWorkspaceContext()
const { const {
folder, folder,
@@ -131,7 +133,7 @@ export function TabProvider({ children }: TabProviderProps) {
kind: "conversation" as const, kind: "conversation" as const,
conversationId: selectedConversation.id, conversationId: selectedConversation.id,
agentType: selectedConversation.agentType, agentType: selectedConversation.agentType,
title: "Loading...", title: t("loadingConversation"),
isPinned: true, isPinned: true,
}, },
] ]
@@ -188,7 +190,7 @@ export function TabProvider({ children }: TabProviderProps) {
kind: "conversation" as const, kind: "conversation" as const,
conversationId: oc.conversation_id, conversationId: oc.conversation_id,
agentType: oc.agent_type, agentType: oc.agent_type,
title: "Loading...", title: t("loadingConversation"),
isPinned: oc.is_pinned, isPinned: oc.is_pinned,
})) }))
@@ -204,7 +206,7 @@ export function TabProvider({ children }: TabProviderProps) {
return () => { return () => {
cancelled = true cancelled = true
} }
}, [folder, restoredFolderId]) }, [folder, restoredFolderId, t])
// Sync restored active tab to FolderProvider (deferred to avoid // Sync restored active tab to FolderProvider (deferred to avoid
// updating parent during child render) // updating parent during child render)
@@ -280,7 +282,7 @@ export function TabProvider({ children }: TabProviderProps) {
`${tab.agentType}-${tab.conversationId}` `${tab.agentType}-${tab.conversationId}`
) )
if (conv) { if (conv) {
const newTitle = conv.title || "Untitled conversation" const newTitle = conv.title || t("untitledConversation")
const newStatus = conv.status as ConversationStatus | undefined const newStatus = conv.status as ConversationStatus | undefined
if (tab.title !== newTitle || tab.status !== newStatus) { if (tab.title !== newTitle || tab.status !== newStatus) {
return { ...tab, title: newTitle, status: newStatus } return { ...tab, title: newTitle, status: newStatus }
@@ -289,7 +291,7 @@ export function TabProvider({ children }: TabProviderProps) {
} }
return tab return tab
}) })
}, [rawTabs, conversationMap]) }, [rawTabs, conversationMap, t])
const syncFolderContext = useCallback( const syncFolderContext = useCallback(
(tab: TabItem | null) => { (tab: TabItem | null) => {
@@ -347,7 +349,7 @@ export function TabProvider({ children }: TabProviderProps) {
conversationsRef.current.find( conversationsRef.current.find(
(c) => c.id === conversationId && c.agent_type === agentType (c) => c.id === conversationId && c.agent_type === agentType
)?.title ?? )?.title ??
"Untitled conversation" t("untitledConversation")
const tabId = makeConversationTabId(agentType, conversationId) const tabId = makeConversationTabId(agentType, conversationId)
activateTabId = tabId activateTabId = tabId
@@ -381,7 +383,7 @@ export function TabProvider({ children }: TabProviderProps) {
selectConversation(conversationId, agentType) selectConversation(conversationId, agentType)
activateConversationPane() activateConversationPane()
}, },
[activateConversationPane, selectConversation] [activateConversationPane, selectConversation, t]
) )
const makeReplacementNewConversationTab = useCallback( const makeReplacementNewConversationTab = useCallback(
@@ -389,11 +391,11 @@ export function TabProvider({ children }: TabProviderProps) {
id: makeNewConversationTabId(), id: makeNewConversationTabId(),
kind: "new_conversation", kind: "new_conversation",
agentType: preferred?.agentType ?? "codex", agentType: preferred?.agentType ?? "codex",
title: "New Conversation", title: t("newConversation"),
isPinned: true, isPinned: true,
workingDir: preferred?.workingDir ?? folder?.path, workingDir: preferred?.workingDir ?? folder?.path,
}), }),
[folder?.path] [folder?.path, t]
) )
const closeTab = useCallback( const closeTab = useCallback(
@@ -517,7 +519,7 @@ export function TabProvider({ children }: TabProviderProps) {
id: tabId, id: tabId,
kind: "new_conversation", kind: "new_conversation",
agentType, agentType,
title: "New Conversation", title: t("newConversation"),
isPinned: true, isPinned: true,
workingDir, workingDir,
} }
@@ -527,7 +529,7 @@ export function TabProvider({ children }: TabProviderProps) {
startNewConversation(agentType, workingDir) startNewConversation(agentType, workingDir)
activateConversationPane() activateConversationPane()
}, },
[activateConversationPane, startNewConversation, syncFolderContext] [activateConversationPane, startNewConversation, syncFolderContext, t]
) )
const linkTabConversation = useCallback( const linkTabConversation = useCallback(

View File

@@ -1,6 +1,7 @@
"use client" "use client"
import { useCallback, useEffect, useRef, useState } from "react" import { useCallback, useEffect, useRef, useState } from "react"
import { useTranslations } from "next-intl"
import { useAcpActions } from "@/contexts/acp-connections-context" import { useAcpActions } from "@/contexts/acp-connections-context"
import { useTaskContext } from "@/contexts/task-context" import { useTaskContext } from "@/contexts/task-context"
import { useConnection, type UseConnectionReturn } from "@/hooks/use-connection" import { useConnection, type UseConnectionReturn } from "@/hooks/use-connection"
@@ -48,6 +49,7 @@ export function useConnectionLifecycle({
workingDir, workingDir,
sessionId, sessionId,
}: UseConnectionLifecycleOptions): UseConnectionLifecycleReturn { }: UseConnectionLifecycleOptions): UseConnectionLifecycleReturn {
const t = useTranslations("Folder.chat.connectionLifecycle")
const { setActiveKey, touchActivity } = useAcpActions() const { setActiveKey, touchActivity } = useAcpActions()
const { addTask, updateTask, removeTask } = useTaskContext() const { addTask, updateTask, removeTask } = useTaskContext()
const conn = useConnection(contextKey) const conn = useConnection(contextKey)
@@ -170,7 +172,11 @@ export function useConnectionLifecycle({
const id = `acp-connect-${Date.now()}` const id = `acp-connect-${Date.now()}`
taskIdRef.current = id taskIdRef.current = id
const agent = AGENT_LABELS[agentType] const agent = AGENT_LABELS[agentType]
addTask(id, `Connecting to ${agent}`, `Establishing connection`) addTask(
id,
t("tasks.connectingTitle", { agent }),
t("tasks.connectingDescription")
)
} }
updateTask(taskIdRef.current, { status: "running" }) updateTask(taskIdRef.current, { status: "running" })
} else if (status === "connected" || status === "prompting") { } else if (status === "connected" || status === "prompting") {
@@ -182,7 +188,7 @@ export function useConnectionLifecycle({
if (taskIdRef.current) { if (taskIdRef.current) {
updateTask(taskIdRef.current, { updateTask(taskIdRef.current, {
status: "failed", status: "failed",
error: "Connection failed", error: t("errors.connectionFailed"),
}) })
taskIdRef.current = null taskIdRef.current = null
} }
@@ -192,7 +198,7 @@ export function useConnectionLifecycle({
taskIdRef.current = null taskIdRef.current = null
} }
} }
}, [status, addTask, updateTask, removeTask, agentType]) }, [status, addTask, updateTask, removeTask, agentType, t])
useEffect(() => { useEffect(() => {
if (status === "prompting") return if (status === "prompting") return
@@ -235,8 +241,8 @@ export function useConnectionLifecycle({
const agent = AGENT_LABELS[agentType] const agent = AGENT_LABELS[agentType]
addTask( addTask(
id, id,
`Loading ${agent} selectors`, t("tasks.loadingSelectorsTitle", { agent }),
"Fetching mode and session config options" t("tasks.loadingSelectorsDescription")
) )
updateTask(id, { status: "running" }) updateTask(id, { status: "running" })
} }
@@ -257,6 +263,7 @@ export function useConnectionLifecycle({
addTask, addTask,
updateTask, updateTask,
clearSelectorTask, clearSelectorTask,
t,
]) ])
// Clean up lingering task on unmount (e.g. tab closed while connecting) // Clean up lingering task on unmount (e.g. tab closed while connecting)

View File

@@ -919,6 +919,11 @@
"kindFile": "file" "kindFile": "file"
} }
}, },
"tabContext": {
"loadingConversation": "Loading...",
"untitledConversation": "Untitled conversation",
"newConversation": "New Conversation"
},
"fileTreeTab": { "fileTreeTab": {
"workspace": "Workspace", "workspace": "Workspace",
"retry": "Retry", "retry": "Retry",
@@ -1056,6 +1061,21 @@
"diffDescriptionConflict": "{path} · disk vs unsaved" "diffDescriptionConflict": "{path} · disk vs unsaved"
}, },
"chat": { "chat": {
"connectionLifecycle": {
"tasks": {
"connectingTitle": "Connecting to {agent}",
"connectingDescription": "Establishing connection",
"loadingSelectorsTitle": "Loading {agent} selectors",
"loadingSelectorsDescription": "Fetching mode and session config options"
},
"errors": {
"connectionFailed": "Connection failed"
}
},
"messageThread": {
"emptyTitle": "No messages yet",
"emptyDescription": "Start a conversation to see messages here"
},
"chatInput": { "chatInput": {
"connecting": "Connecting...", "connecting": "Connecting...",
"agentResponding": "Agent is responding...", "agentResponding": "Agent is responding...",
@@ -1140,6 +1160,8 @@
}, },
"tool": { "tool": {
"parameters": "Parameters", "parameters": "Parameters",
"error": "Error",
"result": "Result",
"status": { "status": {
"approvalRequested": "Awaiting Approval", "approvalRequested": "Awaiting Approval",
"approvalResponded": "Responded", "approvalResponded": "Responded",
@@ -1150,9 +1172,36 @@
"outputError": "Error" "outputError": "Error"
} }
}, },
"toolCallBlock": {
"tool": "Tool",
"error": "Error",
"result": "Result"
},
"contentParts": { "contentParts": {
"showingTailOutput": "Showing tail output while streaming for performance.", "showingTailOutput": "Showing tail output while streaming for performance.",
"result": "Result" "result": "Result",
"unknown": "unknown",
"replaceAll": "REPLACE ALL",
"filesCount": "Files: {count}",
"update": "update",
"moreFiles": "+{count} more files",
"timeoutMs": "Timeout: {timeout}ms",
"backgroundTrue": "Background: true",
"offset": "Offset: {offset}",
"limit": "Limit: {limit}",
"pages": "Pages: {pages}",
"mode": "Mode: {mode}",
"cell": "Cell: {cell}",
"pathLabel": "Path:",
"globLabel": "Glob:",
"typeLabel": "Type:",
"outputLabel": "Output:",
"caseInsensitive": "Case insensitive",
"multiline": "Multiline",
"promptLabel": "Prompt",
"subjectLabel": "Subject",
"taskLabel": "Task",
"nameLabel": "Name:"
} }
}, },
"diffPreview": { "diffPreview": {

View File

@@ -919,6 +919,11 @@
"kindFile": "文件" "kindFile": "文件"
} }
}, },
"tabContext": {
"loadingConversation": "加载中...",
"untitledConversation": "未命名会话",
"newConversation": "新建会话"
},
"fileTreeTab": { "fileTreeTab": {
"workspace": "工作区", "workspace": "工作区",
"retry": "重试", "retry": "重试",
@@ -1056,6 +1061,21 @@
"diffDescriptionConflict": "{path} · 磁盘与未保存内容" "diffDescriptionConflict": "{path} · 磁盘与未保存内容"
}, },
"chat": { "chat": {
"connectionLifecycle": {
"tasks": {
"connectingTitle": "正在连接 {agent}",
"connectingDescription": "正在建立连接",
"loadingSelectorsTitle": "正在加载 {agent} 选择项",
"loadingSelectorsDescription": "正在获取模式和会话配置选项"
},
"errors": {
"connectionFailed": "连接失败"
}
},
"messageThread": {
"emptyTitle": "暂无消息",
"emptyDescription": "开始一个会话后,消息会显示在这里"
},
"chatInput": { "chatInput": {
"connecting": "连接中...", "connecting": "连接中...",
"agentResponding": "Agent 正在响应...", "agentResponding": "Agent 正在响应...",
@@ -1140,6 +1160,8 @@
}, },
"tool": { "tool": {
"parameters": "参数", "parameters": "参数",
"error": "错误",
"result": "结果",
"status": { "status": {
"approvalRequested": "等待授权", "approvalRequested": "等待授权",
"approvalResponded": "已响应", "approvalResponded": "已响应",
@@ -1150,9 +1172,36 @@
"outputError": "错误" "outputError": "错误"
} }
}, },
"toolCallBlock": {
"tool": "工具",
"error": "错误",
"result": "结果"
},
"contentParts": { "contentParts": {
"showingTailOutput": "为保证性能,流式输出时仅显示尾部内容。", "showingTailOutput": "为保证性能,流式输出时仅显示尾部内容。",
"result": "结果" "result": "结果",
"unknown": "未知",
"replaceAll": "全部替换",
"filesCount": "文件:{count}",
"update": "更新",
"moreFiles": "+{count} 个更多文件",
"timeoutMs": "超时:{timeout}ms",
"backgroundTrue": "后台true",
"offset": "偏移:{offset}",
"limit": "限制:{limit}",
"pages": "页码:{pages}",
"mode": "模式:{mode}",
"cell": "单元:{cell}",
"pathLabel": "路径:",
"globLabel": "Glob",
"typeLabel": "类型:",
"outputLabel": "输出:",
"caseInsensitive": "忽略大小写",
"multiline": "多行",
"promptLabel": "提示词",
"subjectLabel": "主题",
"taskLabel": "任务",
"nameLabel": "名称:"
} }
}, },
"diffPreview": { "diffPreview": {

View File

@@ -919,6 +919,11 @@
"kindFile": "檔案" "kindFile": "檔案"
} }
}, },
"tabContext": {
"loadingConversation": "載入中...",
"untitledConversation": "未命名會話",
"newConversation": "新增會話"
},
"fileTreeTab": { "fileTreeTab": {
"workspace": "工作區", "workspace": "工作區",
"retry": "重試", "retry": "重試",
@@ -1056,6 +1061,21 @@
"diffDescriptionConflict": "{path} · 磁碟與未儲存內容" "diffDescriptionConflict": "{path} · 磁碟與未儲存內容"
}, },
"chat": { "chat": {
"connectionLifecycle": {
"tasks": {
"connectingTitle": "正在連線 {agent}",
"connectingDescription": "正在建立連線",
"loadingSelectorsTitle": "正在載入 {agent} 選擇項",
"loadingSelectorsDescription": "正在取得模式與會話設定選項"
},
"errors": {
"connectionFailed": "連線失敗"
}
},
"messageThread": {
"emptyTitle": "暫無訊息",
"emptyDescription": "開始一個會話後,訊息會顯示在這裡"
},
"chatInput": { "chatInput": {
"connecting": "連線中...", "connecting": "連線中...",
"agentResponding": "Agent 正在回應...", "agentResponding": "Agent 正在回應...",
@@ -1140,6 +1160,8 @@
}, },
"tool": { "tool": {
"parameters": "參數", "parameters": "參數",
"error": "錯誤",
"result": "結果",
"status": { "status": {
"approvalRequested": "等待授權", "approvalRequested": "等待授權",
"approvalResponded": "已回應", "approvalResponded": "已回應",
@@ -1150,9 +1172,36 @@
"outputError": "錯誤" "outputError": "錯誤"
} }
}, },
"toolCallBlock": {
"tool": "工具",
"error": "錯誤",
"result": "結果"
},
"contentParts": { "contentParts": {
"showingTailOutput": "為確保效能,串流輸出時僅顯示尾端內容。", "showingTailOutput": "為確保效能,串流輸出時僅顯示尾端內容。",
"result": "結果" "result": "結果",
"unknown": "未知",
"replaceAll": "全部替換",
"filesCount": "檔案:{count}",
"update": "更新",
"moreFiles": "+{count} 個更多檔案",
"timeoutMs": "逾時:{timeout}ms",
"backgroundTrue": "背景true",
"offset": "位移:{offset}",
"limit": "限制:{limit}",
"pages": "頁碼:{pages}",
"mode": "模式:{mode}",
"cell": "儲存格:{cell}",
"pathLabel": "路徑:",
"globLabel": "Glob",
"typeLabel": "類型:",
"outputLabel": "輸出:",
"caseInsensitive": "不區分大小寫",
"multiline": "多行",
"promptLabel": "提示詞",
"subjectLabel": "主題",
"taskLabel": "任務",
"nameLabel": "名稱:"
} }
}, },
"diffPreview": { "diffPreview": {