继续多语言处理

This commit is contained in:
xintaofei
2026-03-07 15:49:00 +08:00
parent 931f69c421
commit 6e5219cc10
18 changed files with 466 additions and 234 deletions

View File

@@ -15,8 +15,16 @@ export const LiveMessageBlock = memo(function LiveMessageBlock({
message,
}: LiveMessageBlockProps) {
const t = useTranslations("Folder.chat.liveMessageBlock")
const sharedT = useTranslations("Folder.chat.shared")
const hasContent = message.content.length > 0
const adapted = useMemo(() => adaptLiveMessageFromAcp(message), [message])
const adapted = useMemo(
() =>
adaptLiveMessageFromAcp(message, {
toolCallFailedText: sharedT("toolCallFailed"),
planUpdatedText: sharedT("planUpdated"),
}),
[message, sharedT]
)
return (
<Message from="assistant">

View File

@@ -87,6 +87,8 @@ export function WelcomeInputPanel({
isActive = true,
}: WelcomeInputPanelProps) {
const t = useTranslations("Folder.chat.welcomeInputPanel")
const tabT = useTranslations("Folder.tabContext")
const sharedT = useTranslations("Folder.chat.shared")
const fallbackContextId = useMemo(() => crypto.randomUUID(), [])
const contextKey = tabId ?? `new-${fallbackContextId}`
@@ -159,7 +161,10 @@ export function WelcomeInputPanel({
const detail = await getFolderConversation(conversationId)
if (refreshSeq !== statsRefreshSeqRef.current) return
const messages = adaptMessageTurns(detail.turns)
const messages = adaptMessageTurns(detail.turns, {
attachedResources: sharedT("attachedResources"),
toolCallFailed: sharedT("toolCallFailed"),
})
const stats = detail.session_stats ?? null
latestMessages = messages
latestStats = stats
@@ -196,7 +201,7 @@ export function WelcomeInputPanel({
applySessionStats(latestStats)
}
},
[applySessionStats, hasAssistantUsage, hasTokenStats]
[applySessionStats, hasAssistantUsage, hasTokenStats, sharedT]
)
useEffect(() => {
@@ -354,6 +359,8 @@ export function WelcomeInputPanel({
if (conn.liveMessage && conn.liveMessage.content.length > 0) {
const adapted = adaptLiveMessageFromAcp(conn.liveMessage, {
isLiveStreaming: false,
toolCallFailedText: sharedT("toolCallFailed"),
planUpdatedText: sharedT("planUpdated"),
})
setHistory((h) => [...h, adapted])
@@ -376,7 +383,7 @@ export function WelcomeInputPanel({
)
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- conn.liveMessage, lifecycleSend intentionally omitted: effect only fires on status transitions
}, [connStatus, refreshConversations, refreshConversationFromDb])
}, [connStatus, refreshConversations, refreshConversationFromDb, sharedT])
// When connection becomes "connected" and we have a pending prompt, send it
useEffect(() => {
@@ -394,7 +401,7 @@ export function WelcomeInputPanel({
const tid = tabIdRef.current
const convId = dbConvIdRef.current
const agent = selectedAgentRef.current
const title = convTitleRef.current || "Untitled"
const title = convTitleRef.current || tabT("untitledConversation")
const canonicalContextKey = `conv-${agent}-${convId}`
// Keep in-flight stream/state attached when this new-conversation view
@@ -410,6 +417,7 @@ export function WelcomeInputPanel({
refreshConversations,
migrateContextKey,
contextKey,
tabT,
])
// Update conversation status on disconnect/error + promote tab
@@ -466,11 +474,17 @@ export function WelcomeInputPanel({
// Welcome phase: submit first message.
const handleWelcomeSend = useCallback(
(draft: PromptDraft, selectedModeId?: string | null) => {
const displayText = getPromptDraftDisplayText(draft)
const displayText = getPromptDraftDisplayText(
draft,
sharedT("attachedResources")
)
const userMsg: AdaptedMessage = {
id: crypto.randomUUID(),
role: "user",
content: buildUserMessageTextPartsFromDraft(draft),
content: buildUserMessageTextPartsFromDraft(
draft,
sharedT("attachedResources")
),
userResources: extractUserResourcesFromDraft(draft),
timestamp: new Date().toISOString(),
}
@@ -536,6 +550,7 @@ export function WelcomeInputPanel({
trySaveExternalId,
applySessionStats,
newConversationDraftStorageKey,
sharedT,
]
)
@@ -545,7 +560,10 @@ export function WelcomeInputPanel({
const userMsg: AdaptedMessage = {
id: crypto.randomUUID(),
role: "user",
content: buildUserMessageTextPartsFromDraft(draft),
content: buildUserMessageTextPartsFromDraft(
draft,
sharedT("attachedResources")
),
userResources: extractUserResourcesFromDraft(draft),
timestamp: new Date().toISOString(),
}
@@ -562,7 +580,7 @@ export function WelcomeInputPanel({
statusUpdatedRef.current = false
}
},
[lifecycleSend, refreshConversations]
[lifecycleSend, refreshConversations, sharedT]
)
const handleOpenAgentsSettings = useCallback(() => {

View File

@@ -43,6 +43,7 @@ const ExistingConversationView = memo(function ExistingConversationView({
reloadSignal,
}: ExistingConversationViewProps) {
const t = useTranslations("Folder.conversation")
const sharedT = useTranslations("Folder.chat.shared")
const { refreshConversations, folder } = useFolderContext()
const contextKey = `conv-${agentType}-${conversationId}`
@@ -114,7 +115,10 @@ const ExistingConversationView = memo(function ExistingConversationView({
{
id: `pending-${Date.now()}`,
role: "user",
content: buildUserMessageTextPartsFromDraft(draft),
content: buildUserMessageTextPartsFromDraft(
draft,
sharedT("attachedResources")
),
userResources: extractUserResourcesFromDraft(draft),
timestamp: new Date().toISOString(),
},
@@ -125,7 +129,7 @@ const ExistingConversationView = memo(function ExistingConversationView({
statusUpdatedRef.current = false
handleSend(draft, selectedModeId)
},
[conversationId, handleSend, refreshConversations]
[conversationId, handleSend, refreshConversations, sharedT]
)
// Update status on turn complete

View File

@@ -1660,6 +1660,7 @@ const CODE_FIELDS = new Set([
const HIDDEN_FIELDS = new Set(["dangerouslyDisableSandbox"])
function GenericToolInput({ input }: { input: string }) {
const t = useTranslations("Folder.chat.contentParts")
const parsed = tryParseJson(input)
if (!parsed) {
@@ -1677,6 +1678,9 @@ function GenericToolInput({ input }: { input: string }) {
return (
<div className="space-y-3">
{entries.map(([key, value]) => {
const labelKey = fieldLabelKey(key)
const label = labelKey ? t(labelKey) : key
if (CODE_FIELDS.has(key) && typeof value === "string") {
const lang =
key === "command"
@@ -1685,7 +1689,7 @@ function GenericToolInput({ input }: { input: string }) {
? ("log" as const)
: ("log" as const)
return (
<FieldBlock key={key} label={fieldLabel(key)}>
<FieldBlock key={key} label={label}>
<CodeBlock code={value} language={lang} />
</FieldBlock>
)
@@ -1694,29 +1698,23 @@ function GenericToolInput({ input }: { input: string }) {
if (typeof value === "string") {
if (value.length > 200) {
return (
<FieldBlock key={key} label={fieldLabel(key)}>
<FieldBlock key={key} label={label}>
<pre className="whitespace-pre-wrap break-all rounded-md bg-muted/50 p-3 text-xs">
{value}
</pre>
</FieldBlock>
)
}
return <FieldInline key={key} label={fieldLabel(key)} value={value} />
return <FieldInline key={key} label={label} value={value} />
}
if (typeof value === "number" || typeof value === "boolean") {
return (
<FieldInline
key={key}
label={fieldLabel(key)}
value={String(value)}
/>
)
return <FieldInline key={key} label={label} value={String(value)} />
}
if (value !== null && value !== undefined) {
return (
<FieldBlock key={key} label={fieldLabel(key)}>
<FieldBlock key={key} label={label}>
<CodeBlock
code={JSON.stringify(value, null, 2)}
language="json"
@@ -1836,41 +1834,45 @@ function FieldBlock({
)
}
function fieldLabel(key: string): string {
const map: Record<string, string> = {
file_path: "File",
notebook_path: "Notebook",
command: "Command",
cmd: "Command",
old_string: "Old",
new_string: "New",
pattern: "Pattern",
path: "Path",
query: "Query",
url: "URL",
description: "Description",
content: "Content",
new_source: "Source",
prompt: "Prompt",
subject: "Subject",
taskId: "Task ID",
status: "Status",
skill: "Skill",
args: "Args",
offset: "Offset",
limit: "Limit",
glob: "Glob",
type: "Type",
output_mode: "Output",
replace_all: "Replace All",
language: "Language",
timeout: "Timeout",
run_in_background: "Background",
subagent_type: "Agent Type",
libraryName: "Library",
libraryId: "Library ID",
}
return map[key] ?? key
const FIELD_LABEL_KEYS = {
file_path: "field.file",
notebook_path: "field.notebook",
command: "field.command",
cmd: "field.command",
old_string: "field.old",
new_string: "field.new",
pattern: "field.pattern",
path: "field.path",
query: "field.query",
url: "field.url",
description: "field.description",
content: "field.content",
new_source: "field.source",
prompt: "field.prompt",
subject: "field.subject",
taskId: "field.taskId",
status: "field.status",
skill: "field.skill",
args: "field.args",
offset: "field.offset",
limit: "field.limit",
glob: "field.glob",
type: "field.type",
output_mode: "field.output",
replace_all: "field.replaceAll",
language: "field.language",
timeout: "field.timeout",
run_in_background: "field.background",
subagent_type: "field.agentType",
libraryName: "field.library",
libraryId: "field.libraryId",
} as const
function fieldLabelKey(
key: string
): (typeof FIELD_LABEL_KEYS)[keyof typeof FIELD_LABEL_KEYS] | null {
const translationKey = FIELD_LABEL_KEYS[key as keyof typeof FIELD_LABEL_KEYS]
return translationKey ?? null
}
function commandOutputFromJsonString(output: string): string | null {

View File

@@ -169,6 +169,7 @@ export function MessageListView({
isActive = true,
}: MessageListViewProps) {
const t = useTranslations("Folder.chat.messageList")
const sharedT = useTranslations("Folder.chat.shared")
const { detail, loading, error, refetch } = useDbMessageDetail(conversationId)
const turnCount = detail?.turns.length ?? 0
@@ -213,8 +214,14 @@ export function MessageListView({
const shouldUseSmoothResize = !(isActive && !loading && detail)
const messages = useMemo(
() => (detail ? adaptMessageTurns(detail.turns) : []),
[detail]
() =>
detail
? adaptMessageTurns(detail.turns, {
attachedResources: sharedT("attachedResources"),
toolCallFailed: sharedT("toolCallFailed"),
})
: [],
[detail, sharedT]
)
const groups = useMemo(() => groupAdaptedMessages(messages), [messages])
@@ -234,15 +241,17 @@ export function MessageListView({
)
const resolvedGroups = useMemo(
() =>
groups.map((group) => resolveMessageGroup(group, t("attachedResources"))),
[groups, t]
groups.map((group) =>
resolveMessageGroup(group, sharedT("attachedResources"))
),
[groups, sharedT]
)
const resolvedPendingGroups = useMemo(
() =>
pendingGroups.map((group) =>
resolveMessageGroup(group, t("attachedResources"))
resolveMessageGroup(group, sharedT("attachedResources"))
),
[pendingGroups, t]
[pendingGroups, sharedT]
)
const showLiveMessage = Boolean(

View File

@@ -33,6 +33,8 @@ function stripClonePrefix(message: string): string {
function mapCommonCodeToKey(code: string): WelcomeErrorKey {
switch (code) {
case "invalid_input":
case "configuration_missing":
case "configuration_invalid":
return "errors.invalidInput"
case "not_found":
return "errors.notFound"