继续处理会话区多语言

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

View File

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

View File

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

View File

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