继续多语言补充
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { Suspense, useCallback, useEffect, useState } from "react"
|
import { Suspense, useCallback, useEffect, useState } from "react"
|
||||||
import { useSearchParams } from "next/navigation"
|
import { useSearchParams } from "next/navigation"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||||
import { Loader2 } from "lucide-react"
|
import { Loader2 } from "lucide-react"
|
||||||
import { CommitWorkspace } from "@/components/layout/commit-dialog"
|
import { CommitWorkspace } from "@/components/layout/commit-dialog"
|
||||||
@@ -19,6 +20,7 @@ interface FolderLoadState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CommitPageInner() {
|
function CommitPageInner() {
|
||||||
|
const t = useTranslations("CommitPage")
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const [state, setState] = useState<FolderLoadState>({
|
const [state, setState] = useState<FolderLoadState>({
|
||||||
loadedId: null,
|
loadedId: null,
|
||||||
@@ -76,7 +78,8 @@ function CommitPageInner() {
|
|||||||
<AppTitleBar
|
<AppTitleBar
|
||||||
center={
|
center={
|
||||||
<div className="text-sm font-semibold tracking-tight">
|
<div className="text-sm font-semibold tracking-tight">
|
||||||
Git Commit{hasValidFolderId && folder ? ` · ${folder.name}` : ""}
|
{t("title")}
|
||||||
|
{hasValidFolderId && folder ? ` · ${folder.name}` : ""}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -84,12 +87,12 @@ function CommitPageInner() {
|
|||||||
<main className="flex-1 min-h-0 p-3">
|
<main className="flex-1 min-h-0 p-3">
|
||||||
{!hasValidFolderId ? (
|
{!hasValidFolderId ? (
|
||||||
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
缺少有效的 folderId 参数
|
{t("invalidFolderId")}
|
||||||
</div>
|
</div>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
正在加载仓库信息...
|
{t("loadingRepo")}
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip"
|
} from "@/components/ui/tooltip"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
import { cjk } from "@streamdown/cjk"
|
import { cjk } from "@streamdown/cjk"
|
||||||
import { code } from "@streamdown/code"
|
import { code } from "@streamdown/code"
|
||||||
import { math } from "@streamdown/math"
|
import { math } from "@streamdown/math"
|
||||||
@@ -258,11 +259,12 @@ export const MessageBranchPrevious = ({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: MessageBranchPreviousProps) => {
|
}: MessageBranchPreviousProps) => {
|
||||||
|
const t = useTranslations("Folder.chat.messageBranch")
|
||||||
const { goToPrevious, totalBranches } = useMessageBranch()
|
const { goToPrevious, totalBranches } = useMessageBranch()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
aria-label="Previous branch"
|
aria-label={t("previousBranchAria")}
|
||||||
disabled={totalBranches <= 1}
|
disabled={totalBranches <= 1}
|
||||||
onClick={goToPrevious}
|
onClick={goToPrevious}
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
@@ -281,11 +283,12 @@ export const MessageBranchNext = ({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: MessageBranchNextProps) => {
|
}: MessageBranchNextProps) => {
|
||||||
|
const t = useTranslations("Folder.chat.messageBranch")
|
||||||
const { goToNext, totalBranches } = useMessageBranch()
|
const { goToNext, totalBranches } = useMessageBranch()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
aria-label="Next branch"
|
aria-label={t("nextBranchAria")}
|
||||||
disabled={totalBranches <= 1}
|
disabled={totalBranches <= 1}
|
||||||
onClick={goToNext}
|
onClick={goToNext}
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
@@ -304,6 +307,7 @@ export const MessageBranchPage = ({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: MessageBranchPageProps) => {
|
}: MessageBranchPageProps) => {
|
||||||
|
const t = useTranslations("Folder.chat.messageBranch")
|
||||||
const { currentBranch, totalBranches } = useMessageBranch()
|
const { currentBranch, totalBranches } = useMessageBranch()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -314,7 +318,7 @@ export const MessageBranchPage = ({
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{currentBranch + 1} of {totalBranches}
|
{t("pageOf", { current: currentBranch + 1, total: totalBranches })}
|
||||||
</ButtonGroupText>
|
</ButtonGroupText>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import type { ComponentProps, ReactNode } from "react"
|
import type { ComponentProps, ReactNode } from "react"
|
||||||
|
|
||||||
import { useControllableState } from "@radix-ui/react-use-controllable-state"
|
import { useControllableState } from "@radix-ui/react-use-controllable-state"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
@@ -155,24 +156,29 @@ export type ReasoningTriggerProps = ComponentProps<
|
|||||||
getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode
|
getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
|
|
||||||
if (isStreaming || duration === 0) {
|
|
||||||
return <Shimmer duration={1}>Thinking...</Shimmer>
|
|
||||||
}
|
|
||||||
if (duration === undefined) {
|
|
||||||
return <p>Thought for a few seconds</p>
|
|
||||||
}
|
|
||||||
return <p>Thought for {duration} seconds</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ReasoningTrigger = memo(
|
export const ReasoningTrigger = memo(
|
||||||
({
|
({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
getThinkingMessage = defaultGetThinkingMessage,
|
getThinkingMessage,
|
||||||
...props
|
...props
|
||||||
}: ReasoningTriggerProps) => {
|
}: ReasoningTriggerProps) => {
|
||||||
|
const t = useTranslations("Folder.chat.reasoning")
|
||||||
const { isStreaming, isOpen, duration } = useReasoning()
|
const { isStreaming, isOpen, duration } = useReasoning()
|
||||||
|
const defaultGetThinkingMessage = useCallback(
|
||||||
|
(nextIsStreaming: boolean, nextDuration?: number) => {
|
||||||
|
if (nextIsStreaming || nextDuration === 0) {
|
||||||
|
return <Shimmer duration={1}>{t("thinking")}</Shimmer>
|
||||||
|
}
|
||||||
|
if (nextDuration === undefined) {
|
||||||
|
return <p>{t("thoughtForFewSeconds")}</p>
|
||||||
|
}
|
||||||
|
return <p>{t("thoughtForSeconds", { duration: nextDuration })}</p>
|
||||||
|
},
|
||||||
|
[t]
|
||||||
|
)
|
||||||
|
const thinkingMessageBuilder =
|
||||||
|
getThinkingMessage ?? defaultGetThinkingMessage
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
@@ -185,7 +191,7 @@ export const ReasoningTrigger = memo(
|
|||||||
{children ?? (
|
{children ?? (
|
||||||
<>
|
<>
|
||||||
<BrainIcon className="size-4" />
|
<BrainIcon className="size-4" />
|
||||||
{getThinkingMessage(isStreaming, duration)}
|
{thinkingMessageBuilder(isStreaming, duration)}
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-4 transition-transform",
|
"size-4 transition-transform",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import type { ComponentProps, HTMLAttributes } from "react"
|
import type { ComponentProps, HTMLAttributes } from "react"
|
||||||
import Ansi from "ansi-to-react"
|
import Ansi from "ansi-to-react"
|
||||||
import { CheckIcon, CopyIcon, TerminalIcon, Trash2Icon } from "lucide-react"
|
import { CheckIcon, CopyIcon, TerminalIcon, Trash2Icon } from "lucide-react"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -127,6 +128,7 @@ export function TerminalTitle({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: TerminalTitleProps) {
|
}: TerminalTitleProps) {
|
||||||
|
const t = useTranslations("Folder.chat.terminal")
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -136,7 +138,7 @@ export function TerminalTitle({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<TerminalIcon className="size-4" />
|
<TerminalIcon className="size-4" />
|
||||||
{children ?? "Terminal"}
|
{children ?? t("title")}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -148,6 +150,7 @@ export function TerminalStatus({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: TerminalStatusProps) {
|
}: TerminalStatusProps) {
|
||||||
|
const t = useTranslations("Folder.chat.terminal")
|
||||||
const { isStreaming } = useContext(TerminalContext)
|
const { isStreaming } = useContext(TerminalContext)
|
||||||
|
|
||||||
if (!isStreaming) {
|
if (!isStreaming) {
|
||||||
@@ -162,7 +165,7 @@ export function TerminalStatus({
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? <Shimmer>Running</Shimmer>}
|
{children ?? <Shimmer>{t("running")}</Shimmer>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
XCircleIcon,
|
XCircleIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
import { isValidElement } from "react"
|
import { isValidElement } from "react"
|
||||||
|
|
||||||
import { CodeBlock } from "./code-block"
|
import { CodeBlock } from "./code-block"
|
||||||
@@ -47,16 +48,6 @@ export type ToolHeaderProps = {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const statusLabels: Record<ToolPart["state"], string> = {
|
|
||||||
"approval-requested": "Awaiting Approval",
|
|
||||||
"approval-responded": "Responded",
|
|
||||||
"input-available": "Running",
|
|
||||||
"input-streaming": "Pending",
|
|
||||||
"output-available": "Completed",
|
|
||||||
"output-denied": "Denied",
|
|
||||||
"output-error": "Error",
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusIcons: Record<ToolPart["state"], ReactNode> = {
|
const statusIcons: Record<ToolPart["state"], ReactNode> = {
|
||||||
"approval-requested": <ClockIcon className="size-4 text-yellow-600" />,
|
"approval-requested": <ClockIcon className="size-4 text-yellow-600" />,
|
||||||
"approval-responded": <CheckCircleIcon className="size-4 text-blue-600" />,
|
"approval-responded": <CheckCircleIcon className="size-4 text-blue-600" />,
|
||||||
@@ -67,10 +58,10 @@ const statusIcons: Record<ToolPart["state"], ReactNode> = {
|
|||||||
"output-error": <XCircleIcon className="size-4 text-red-600" />,
|
"output-error": <XCircleIcon className="size-4 text-red-600" />,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStatusBadge = (status: ToolPart["state"]) => (
|
export const getStatusBadge = (status: ToolPart["state"], label: string) => (
|
||||||
<Badge className="gap-1.5 rounded-full text-xs" variant="secondary">
|
<Badge className="gap-1.5 rounded-full text-xs" variant="secondary">
|
||||||
{statusIcons[status]}
|
{statusIcons[status]}
|
||||||
{statusLabels[status]}
|
{label}
|
||||||
</Badge>
|
</Badge>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,8 +75,23 @@ export const ToolHeader = ({
|
|||||||
toolName,
|
toolName,
|
||||||
...props
|
...props
|
||||||
}: ToolHeaderProps) => {
|
}: ToolHeaderProps) => {
|
||||||
|
const t = useTranslations("Folder.chat.tool")
|
||||||
const derivedName =
|
const derivedName =
|
||||||
type === "dynamic-tool" ? toolName : type.split("-").slice(1).join("-")
|
type === "dynamic-tool" ? toolName : type.split("-").slice(1).join("-")
|
||||||
|
const statusLabel =
|
||||||
|
state === "approval-requested"
|
||||||
|
? t("status.approvalRequested")
|
||||||
|
: state === "approval-responded"
|
||||||
|
? t("status.approvalResponded")
|
||||||
|
: state === "input-available"
|
||||||
|
? t("status.inputAvailable")
|
||||||
|
: state === "input-streaming"
|
||||||
|
? t("status.inputStreaming")
|
||||||
|
: state === "output-available"
|
||||||
|
? t("status.outputAvailable")
|
||||||
|
: state === "output-denied"
|
||||||
|
? t("status.outputDenied")
|
||||||
|
: t("status.outputError")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
@@ -103,7 +109,7 @@ export const ToolHeader = ({
|
|||||||
{title ?? derivedName}
|
{title ?? derivedName}
|
||||||
</span>
|
</span>
|
||||||
{titleSuffix ? <span className="shrink-0">{titleSuffix}</span> : null}
|
{titleSuffix ? <span className="shrink-0">{titleSuffix}</span> : null}
|
||||||
<span className="shrink-0">{getStatusBadge(state)}</span>
|
<span className="shrink-0">{getStatusBadge(state, statusLabel)}</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
|
<ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
@@ -127,6 +133,7 @@ export type ToolInputProps = ComponentProps<"div"> & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ToolInput = ({ className, input, ...props }: ToolInputProps) => {
|
export const ToolInput = ({ className, input, ...props }: ToolInputProps) => {
|
||||||
|
const t = useTranslations("Folder.chat.tool")
|
||||||
const formattedCode = (() => {
|
const formattedCode = (() => {
|
||||||
if (typeof input === "string") {
|
if (typeof input === "string") {
|
||||||
try {
|
try {
|
||||||
@@ -142,7 +149,7 @@ export const ToolInput = ({ className, input, ...props }: ToolInputProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className={cn("space-y-2 overflow-hidden", className)} {...props}>
|
<div className={cn("space-y-2 overflow-hidden", 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">
|
||||||
Parameters
|
{t("parameters")}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="rounded-md bg-muted/50">
|
<div className="rounded-md bg-muted/50">
|
||||||
<CodeBlock code={formattedCode} language="json" />
|
<CodeBlock code={formattedCode} language="json" />
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { BundledLanguage } from "shiki"
|
|||||||
import type { AdaptedContentPart } from "@/lib/adapters/ai-elements-adapter"
|
import type { AdaptedContentPart } from "@/lib/adapters/ai-elements-adapter"
|
||||||
import type { MessageRole } from "@/lib/types"
|
import type { MessageRole } from "@/lib/types"
|
||||||
import { normalizeToolName } from "@/lib/tool-call-normalization"
|
import { normalizeToolName } from "@/lib/tool-call-normalization"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
import {
|
import {
|
||||||
countUnifiedDiffLineChanges,
|
countUnifiedDiffLineChanges,
|
||||||
estimateChangedLineStats,
|
estimateChangedLineStats,
|
||||||
@@ -1881,6 +1882,7 @@ const ToolCallPart = memo(function ToolCallPart({
|
|||||||
}: {
|
}: {
|
||||||
part: Extract<AdaptedContentPart, { type: "tool-call" }>
|
part: Extract<AdaptedContentPart, { type: "tool-call" }>
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations("Folder.chat.contentParts")
|
||||||
const [manualOpen, setManualOpen] = useState(false)
|
const [manualOpen, setManualOpen] = useState(false)
|
||||||
const normalizedToolName = useMemo(
|
const normalizedToolName = useMemo(
|
||||||
() => normalizeToolName(part.toolName),
|
() => normalizeToolName(part.toolName),
|
||||||
@@ -2046,7 +2048,7 @@ const ToolCallPart = memo(function ToolCallPart({
|
|||||||
/>
|
/>
|
||||||
{liveOutputTruncated && (
|
{liveOutputTruncated && (
|
||||||
<div className="text-[11px] text-muted-foreground">
|
<div className="text-[11px] text-muted-foreground">
|
||||||
Showing tail output while streaming for performance.
|
{t("showingTailOutput")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -2068,9 +2070,14 @@ const ToolResultPart = memo(function ToolResultPart({
|
|||||||
}: {
|
}: {
|
||||||
part: Extract<AdaptedContentPart, { type: "tool-result" }>
|
part: Extract<AdaptedContentPart, { type: "tool-result" }>
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations("Folder.chat.contentParts")
|
||||||
return (
|
return (
|
||||||
<Tool>
|
<Tool>
|
||||||
<ToolHeader type="dynamic-tool" state={part.state} toolName="Result" />
|
<ToolHeader
|
||||||
|
type="dynamic-tool"
|
||||||
|
state={part.state}
|
||||||
|
toolName={t("result")}
|
||||||
|
/>
|
||||||
<ToolContent>
|
<ToolContent>
|
||||||
<ToolOutput output={part.output} errorText={part.errorText} />
|
<ToolOutput output={part.output} errorText={part.errorText} />
|
||||||
</ToolContent>
|
</ToolContent>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
|
import { useLocale, useTranslations } from "next-intl"
|
||||||
import type { LiveMessage } from "@/contexts/acp-connections-context"
|
import type { LiveMessage } from "@/contexts/acp-connections-context"
|
||||||
import { inferLiveToolName } from "@/lib/tool-call-normalization"
|
import { inferLiveToolName } from "@/lib/tool-call-normalization"
|
||||||
import {
|
import {
|
||||||
@@ -9,11 +10,6 @@ import {
|
|||||||
} from "@/lib/line-change-stats"
|
} from "@/lib/line-change-stats"
|
||||||
import { FilePenLine, Timer, Wrench } from "lucide-react"
|
import { FilePenLine, Timer, Wrench } from "lucide-react"
|
||||||
|
|
||||||
function formatElapsed(ms: number): string {
|
|
||||||
if (ms >= 60_000) return `${(ms / 60_000).toFixed(1)}m`
|
|
||||||
return `${(ms / 1_000).toFixed(1)}s`
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LiveTurnStatsProps {
|
interface LiveTurnStatsProps {
|
||||||
message: LiveMessage
|
message: LiveMessage
|
||||||
}
|
}
|
||||||
@@ -27,14 +23,9 @@ interface LiveEditStats extends LineChangeStats {
|
|||||||
files: number
|
files: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const COMPACT_NUMBER = new Intl.NumberFormat("en-US", {
|
function formatCompactInt(n: number, formatter: Intl.NumberFormat): string {
|
||||||
notation: "compact",
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
function formatCompactInt(n: number): string {
|
|
||||||
if (n < 1000) return String(n)
|
if (n < 1000) return String(n)
|
||||||
return COMPACT_NUMBER.format(n).toUpperCase()
|
return formatter.format(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
function asObject(value: unknown): Record<string, unknown> | null {
|
function asObject(value: unknown): Record<string, unknown> | null {
|
||||||
@@ -267,8 +258,18 @@ function extractLiveEditStats(message: LiveMessage): LiveEditStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LiveTurnStats({ message }: LiveTurnStatsProps) {
|
export function LiveTurnStats({ message }: LiveTurnStatsProps) {
|
||||||
|
const locale = useLocale()
|
||||||
|
const t = useTranslations("Folder.chat.liveTurnStats")
|
||||||
const [elapsed, setElapsed] = useState(() => Date.now() - message.startedAt)
|
const [elapsed, setElapsed] = useState(() => Date.now() - message.startedAt)
|
||||||
const editStats = useMemo(() => extractLiveEditStats(message), [message])
|
const editStats = useMemo(() => extractLiveEditStats(message), [message])
|
||||||
|
const compactNumberFormatter = useMemo(
|
||||||
|
() =>
|
||||||
|
new Intl.NumberFormat(locale, {
|
||||||
|
notation: "compact",
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
}),
|
||||||
|
[locale]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
@@ -295,26 +296,32 @@ export function LiveTurnStats({ message }: LiveTurnStatsProps) {
|
|||||||
isThinking = true
|
isThinking = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const elapsedLabel =
|
||||||
|
elapsed >= 60_000
|
||||||
|
? t("elapsedMinutes", { value: (elapsed / 60_000).toFixed(1) })
|
||||||
|
: t("elapsedSeconds", { value: (elapsed / 1_000).toFixed(1) })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-8 shrink-0 items-center justify-center gap-3 px-4 text-xs leading-none text-muted-foreground">
|
<div className="flex h-8 shrink-0 items-center justify-center gap-3 px-4 text-xs leading-none text-muted-foreground">
|
||||||
<span className="inline-block h-1.5 w-1.5 rounded-full bg-primary animate-pulse shrink-0" />
|
<span className="inline-block h-1.5 w-1.5 rounded-full bg-primary animate-pulse shrink-0" />
|
||||||
{isThinking && message.content.length <= 1 ? (
|
{isThinking && message.content.length <= 1 ? (
|
||||||
<span>Thinking...</span>
|
<span>{t("thinking")}</span>
|
||||||
) : (
|
) : (
|
||||||
<span>Streaming</span>
|
<span>{t("streaming")}</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-border leading-none">|</span>
|
<span className="text-border leading-none">|</span>
|
||||||
<span className="inline-flex items-center gap-1 leading-none">
|
<span className="inline-flex items-center gap-1 leading-none">
|
||||||
<Timer className="h-3 w-3 shrink-0" />
|
<Timer className="h-3 w-3 shrink-0" />
|
||||||
{formatElapsed(elapsed)}
|
{elapsedLabel}
|
||||||
</span>
|
</span>
|
||||||
{editStats.files > 0 && (
|
{editStats.files > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-border leading-none">|</span>
|
<span className="text-border leading-none">|</span>
|
||||||
<span className="inline-flex items-center gap-1 leading-none">
|
<span className="inline-flex items-center gap-1 leading-none">
|
||||||
<FilePenLine className="h-3 w-3 shrink-0" />
|
<FilePenLine className="h-3 w-3 shrink-0" />
|
||||||
{editStats.files}F +{formatCompactInt(editStats.additions)}/-
|
{editStats.files}F +
|
||||||
{formatCompactInt(editStats.deletions)}
|
{formatCompactInt(editStats.additions, compactNumberFormatter)}/-
|
||||||
|
{formatCompactInt(editStats.deletions, compactNumberFormatter)}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -323,7 +330,7 @@ export function LiveTurnStats({ message }: LiveTurnStatsProps) {
|
|||||||
<span className="text-border leading-none">|</span>
|
<span className="text-border leading-none">|</span>
|
||||||
<span className="inline-flex items-center gap-1 leading-none">
|
<span className="inline-flex items-center gap-1 leading-none">
|
||||||
<Wrench className="h-3 w-3 shrink-0" />
|
<Wrench className="h-3 w-3 shrink-0" />
|
||||||
{toolCallCount} tool {toolCallCount === 1 ? "use" : "uses"}
|
{t("toolUseCount", { count: toolCallCount })}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
} from "@/components/ai-elements/message-thread"
|
} from "@/components/ai-elements/message-thread"
|
||||||
import { Message, MessageContent } from "@/components/ai-elements/message"
|
import { Message, MessageContent } from "@/components/ai-elements/message"
|
||||||
import { Loader2 } from "lucide-react"
|
import { Loader2 } from "lucide-react"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
import {
|
import {
|
||||||
buildPlanKey,
|
buildPlanKey,
|
||||||
extractLatestPlanEntriesFromMessages,
|
extractLatestPlanEntriesFromMessages,
|
||||||
@@ -46,7 +47,10 @@ interface ResolvedMessageGroup extends MessageGroup {
|
|||||||
resources: UserResourceDisplay[]
|
resources: UserResourceDisplay[]
|
||||||
}
|
}
|
||||||
|
|
||||||
function fallbackExtractUserResources(group: MessageGroup): {
|
function fallbackExtractUserResources(
|
||||||
|
group: MessageGroup,
|
||||||
|
attachedResourcesText: string
|
||||||
|
): {
|
||||||
parts: AdaptedContentPart[]
|
parts: AdaptedContentPart[]
|
||||||
resources: UserResourceDisplay[]
|
resources: UserResourceDisplay[]
|
||||||
} {
|
} {
|
||||||
@@ -87,14 +91,17 @@ function fallbackExtractUserResources(group: MessageGroup): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parsedParts.length === 0 && dedupedResources.length > 0) {
|
if (parsedParts.length === 0 && dedupedResources.length > 0) {
|
||||||
parsedParts.push({ type: "text", text: "Attached resources" })
|
parsedParts.push({ type: "text", text: attachedResourcesText })
|
||||||
}
|
}
|
||||||
|
|
||||||
return { parts: parsedParts, resources: dedupedResources }
|
return { parts: parsedParts, resources: dedupedResources }
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveMessageGroup(group: MessageGroup): ResolvedMessageGroup {
|
function resolveMessageGroup(
|
||||||
const resolved = fallbackExtractUserResources(group)
|
group: MessageGroup,
|
||||||
|
attachedResourcesText: string
|
||||||
|
): ResolvedMessageGroup {
|
||||||
|
const resolved = fallbackExtractUserResources(group, attachedResourcesText)
|
||||||
return {
|
return {
|
||||||
...group,
|
...group,
|
||||||
parts: resolved.parts,
|
parts: resolved.parts,
|
||||||
@@ -161,6 +168,7 @@ export function MessageListView({
|
|||||||
onPendingClear,
|
onPendingClear,
|
||||||
isActive = true,
|
isActive = true,
|
||||||
}: MessageListViewProps) {
|
}: MessageListViewProps) {
|
||||||
|
const t = useTranslations("Folder.chat.messageList")
|
||||||
const { detail, loading, error, refetch } = useDbMessageDetail(conversationId)
|
const { detail, loading, error, refetch } = useDbMessageDetail(conversationId)
|
||||||
const turnCount = detail?.turns.length ?? 0
|
const turnCount = detail?.turns.length ?? 0
|
||||||
|
|
||||||
@@ -225,12 +233,16 @@ export function MessageListView({
|
|||||||
[pendingMessages]
|
[pendingMessages]
|
||||||
)
|
)
|
||||||
const resolvedGroups = useMemo(
|
const resolvedGroups = useMemo(
|
||||||
() => groups.map(resolveMessageGroup),
|
() =>
|
||||||
[groups]
|
groups.map((group) => resolveMessageGroup(group, t("attachedResources"))),
|
||||||
|
[groups, t]
|
||||||
)
|
)
|
||||||
const resolvedPendingGroups = useMemo(
|
const resolvedPendingGroups = useMemo(
|
||||||
() => pendingGroups.map(resolveMessageGroup),
|
() =>
|
||||||
[pendingGroups]
|
pendingGroups.map((group) =>
|
||||||
|
resolveMessageGroup(group, t("attachedResources"))
|
||||||
|
),
|
||||||
|
[pendingGroups, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const showLiveMessage = Boolean(
|
const showLiveMessage = Boolean(
|
||||||
@@ -245,7 +257,7 @@ export function MessageListView({
|
|||||||
<div className="flex h-full items-center justify-center">
|
<div className="flex h-full items-center justify-center">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
<span>Loading...</span>
|
<span>{t("loading")}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -255,7 +267,9 @@ export function MessageListView({
|
|||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-destructive text-sm">Error: {error}</p>
|
<p className="text-destructive text-sm">
|
||||||
|
{t("error", { message: error })}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -276,7 +290,7 @@ export function MessageListView({
|
|||||||
!showLiveMessage ? (
|
!showLiveMessage ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
No messages in this conversation.
|
{t("emptyConversation")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
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 {
|
import {
|
||||||
gitDiff,
|
gitDiff,
|
||||||
@@ -172,6 +173,7 @@ interface WorkspaceProviderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
||||||
|
const t = useTranslations("Folder.workspaceContext")
|
||||||
const { folder, folderId } = useFolderContext()
|
const { folder, folderId } = useFolderContext()
|
||||||
const folderPath = folder?.path
|
const folderPath = folder?.path
|
||||||
const storageKey = useMemo(
|
const storageKey = useMemo(
|
||||||
@@ -270,7 +272,11 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
|
|
||||||
const rejectTab = useCallback(
|
const rejectTab = useCallback(
|
||||||
(tabId: string, errorMessage: string) => {
|
(tabId: string, errorMessage: string) => {
|
||||||
resolveTab(tabId, `Unable to load content.\n\n${errorMessage}`, false)
|
resolveTab(
|
||||||
|
tabId,
|
||||||
|
t("unableLoadContent", { message: errorMessage }),
|
||||||
|
false
|
||||||
|
)
|
||||||
setFileTabs((prev) =>
|
setFileTabs((prev) =>
|
||||||
prev.map((tab) =>
|
prev.map((tab) =>
|
||||||
tab.id === tabId
|
tab.id === tabId
|
||||||
@@ -283,7 +289,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[resolveTab]
|
[resolveTab, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const resolveRichDiffTab = useCallback(
|
const resolveRichDiffTab = useCallback(
|
||||||
@@ -333,7 +339,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
})(),
|
})(),
|
||||||
]),
|
]),
|
||||||
15_000,
|
15_000,
|
||||||
"Preview request timed out"
|
t("previewRequestTimedOut")
|
||||||
)
|
)
|
||||||
setFileTabs((prev) =>
|
setFileTabs((prev) =>
|
||||||
prev.map((tab) =>
|
prev.map((tab) =>
|
||||||
@@ -360,7 +366,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
rejectTab(tabId, error instanceof Error ? error.message : String(error))
|
rejectTab(tabId, error instanceof Error ? error.message : String(error))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[folderPath, rejectTab, upsertLoadingTab]
|
[folderPath, rejectTab, t, upsertLoadingTab]
|
||||||
)
|
)
|
||||||
|
|
||||||
const openWorkingTreeDiff = useCallback(
|
const openWorkingTreeDiff = useCallback(
|
||||||
@@ -372,8 +378,8 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
|
|
||||||
if (!rawPath) {
|
if (!rawPath) {
|
||||||
const tabId = "diff:working:all"
|
const tabId = "diff:working:all"
|
||||||
const title = "Diff · Workspace"
|
const title = t("diffTitleWorkspace")
|
||||||
const description = "Working tree (HEAD)"
|
const description = t("diffDescriptionWorkingTree")
|
||||||
upsertLoadingTab(
|
upsertLoadingTab(
|
||||||
loadingTab(tabId, "diff", title, description, null, "diff")
|
loadingTab(tabId, "diff", title, description, null, "diff")
|
||||||
)
|
)
|
||||||
@@ -381,9 +387,9 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
const result = await withTimeout(
|
const result = await withTimeout(
|
||||||
gitDiff(folderPath),
|
gitDiff(folderPath),
|
||||||
20_000,
|
20_000,
|
||||||
"Diff request timed out"
|
t("diffRequestTimedOut")
|
||||||
)
|
)
|
||||||
resolveTab(tabId, result || "No changes.", false)
|
resolveTab(tabId, result || t("noChanges"), false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rejectTab(
|
rejectTab(
|
||||||
tabId,
|
tabId,
|
||||||
@@ -399,7 +405,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
if (mode === "overview") {
|
if (mode === "overview") {
|
||||||
const encodedPath = encodeURIComponent(path)
|
const encodedPath = encodeURIComponent(path)
|
||||||
const tabId = `diff:working-overview:${encodedPath}`
|
const tabId = `diff:working-overview:${encodedPath}`
|
||||||
const title = `Diff · ${fileName(path)}`
|
const title = t("diffTitleFile", { name: fileName(path) })
|
||||||
const description = path
|
const description = path
|
||||||
upsertLoadingTab(
|
upsertLoadingTab(
|
||||||
loadingTab(tabId, "diff", title, description, path, "diff")
|
loadingTab(tabId, "diff", title, description, path, "diff")
|
||||||
@@ -409,9 +415,9 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
const result = await withTimeout(
|
const result = await withTimeout(
|
||||||
gitDiff(folderPath, path),
|
gitDiff(folderPath, path),
|
||||||
20_000,
|
20_000,
|
||||||
"Diff request timed out"
|
t("diffRequestTimedOut")
|
||||||
)
|
)
|
||||||
resolveTab(tabId, result || "No changes.", false)
|
resolveTab(tabId, result || t("noChanges"), false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rejectTab(
|
rejectTab(
|
||||||
tabId,
|
tabId,
|
||||||
@@ -423,7 +429,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
|
|
||||||
if (mode === "unified") {
|
if (mode === "unified") {
|
||||||
const tabId = `diff:working:${path}:unified`
|
const tabId = `diff:working:${path}:unified`
|
||||||
const title = `Diff · ${fileName(path)}`
|
const title = t("diffTitleFile", { name: fileName(path) })
|
||||||
const description = path
|
const description = path
|
||||||
upsertLoadingTab(
|
upsertLoadingTab(
|
||||||
loadingTab(tabId, "diff", title, description, path, "diff")
|
loadingTab(tabId, "diff", title, description, path, "diff")
|
||||||
@@ -433,9 +439,9 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
const result = await withTimeout(
|
const result = await withTimeout(
|
||||||
gitDiff(folderPath, path),
|
gitDiff(folderPath, path),
|
||||||
20_000,
|
20_000,
|
||||||
"Diff request timed out"
|
t("diffRequestTimedOut")
|
||||||
)
|
)
|
||||||
resolveTab(tabId, result || "No changes.", false)
|
resolveTab(tabId, result || t("noChanges"), false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rejectTab(
|
rejectTab(
|
||||||
tabId,
|
tabId,
|
||||||
@@ -446,7 +452,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tabId = `diff:working:${path}`
|
const tabId = `diff:working:${path}`
|
||||||
const title = `Diff · ${fileName(path)}`
|
const title = t("diffTitleFile", { name: fileName(path) })
|
||||||
const description = path
|
const description = path
|
||||||
const lang = languageFromPath(path)
|
const lang = languageFromPath(path)
|
||||||
|
|
||||||
@@ -465,14 +471,14 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
})),
|
})),
|
||||||
]),
|
]),
|
||||||
20_000,
|
20_000,
|
||||||
"Diff request timed out"
|
t("diffRequestTimedOut")
|
||||||
)
|
)
|
||||||
resolveRichDiffTab(tabId, originalContent, modifiedResult.content)
|
resolveRichDiffTab(tabId, originalContent, modifiedResult.content)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rejectTab(tabId, error instanceof Error ? error.message : String(error))
|
rejectTab(tabId, error instanceof Error ? error.message : String(error))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[folderPath, rejectTab, resolveTab, resolveRichDiffTab, upsertLoadingTab]
|
[folderPath, rejectTab, resolveTab, resolveRichDiffTab, t, upsertLoadingTab]
|
||||||
)
|
)
|
||||||
|
|
||||||
const openBranchDiff = useCallback(
|
const openBranchDiff = useCallback(
|
||||||
@@ -494,11 +500,11 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
? `diff:branch-overview:${encodedBranch}:${encodedPath}`
|
? `diff:branch-overview:${encodedBranch}:${encodedPath}`
|
||||||
: `diff:branch:${targetBranch}:${path ?? "all"}`
|
: `diff:branch:${targetBranch}:${path ?? "all"}`
|
||||||
const title = path
|
const title = path
|
||||||
? `Compare · ${fileName(path)}`
|
? t("compareTitleFile", { name: fileName(path) })
|
||||||
: `Compare · ${targetBranch}`
|
: t("compareTitleBranch", { branch: targetBranch })
|
||||||
const description = path
|
const description = path
|
||||||
? `${path} · compare with ${targetBranch}`
|
? t("compareDescriptionPath", { path, branch: targetBranch })
|
||||||
: `compare with ${targetBranch}`
|
: t("compareDescriptionBranch", { branch: targetBranch })
|
||||||
|
|
||||||
if (mode !== "overview" && path) {
|
if (mode !== "overview" && path) {
|
||||||
const lang = languageFromPath(path)
|
const lang = languageFromPath(path)
|
||||||
@@ -517,7 +523,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
})),
|
})),
|
||||||
]),
|
]),
|
||||||
20_000,
|
20_000,
|
||||||
"Branch compare request timed out"
|
t("branchCompareRequestTimedOut")
|
||||||
)
|
)
|
||||||
resolveRichDiffTab(tabId, originalContent, modifiedResult.content)
|
resolveRichDiffTab(tabId, originalContent, modifiedResult.content)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -537,14 +543,14 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
const result = await withTimeout(
|
const result = await withTimeout(
|
||||||
gitDiffWithBranch(folderPath, targetBranch, path ?? undefined),
|
gitDiffWithBranch(folderPath, targetBranch, path ?? undefined),
|
||||||
20_000,
|
20_000,
|
||||||
"Branch compare request timed out"
|
t("branchCompareRequestTimedOut")
|
||||||
)
|
)
|
||||||
resolveTab(tabId, result || "No changes.", false)
|
resolveTab(tabId, result || t("noChanges"), false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rejectTab(tabId, error instanceof Error ? error.message : String(error))
|
rejectTab(tabId, error instanceof Error ? error.message : String(error))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[folderPath, rejectTab, resolveRichDiffTab, resolveTab, upsertLoadingTab]
|
[folderPath, rejectTab, resolveRichDiffTab, resolveTab, t, upsertLoadingTab]
|
||||||
)
|
)
|
||||||
|
|
||||||
const openCommitDiff = useCallback(
|
const openCommitDiff = useCallback(
|
||||||
@@ -553,11 +559,14 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
const path = rawPath ? normalizePath(rawPath) : null
|
const path = rawPath ? normalizePath(rawPath) : null
|
||||||
const tabId = `diff:commit:${commit}:${path ?? "all"}`
|
const tabId = `diff:commit:${commit}:${path ?? "all"}`
|
||||||
const title = path
|
const title = path
|
||||||
? `Diff · ${fileName(path)} @ ${commit.slice(0, 7)}`
|
? t("diffTitleCommitFile", {
|
||||||
: `Diff · ${commit.slice(0, 7)}`
|
name: fileName(path),
|
||||||
|
hash: commit.slice(0, 7),
|
||||||
|
})
|
||||||
|
: t("diffTitleCommit", { hash: commit.slice(0, 7) })
|
||||||
const description = path
|
const description = path
|
||||||
? `${path} · commit ${commit}`
|
? t("diffDescriptionCommitPath", { path, commit })
|
||||||
: message || `commit ${commit}`
|
: message || t("diffDescriptionCommit", { commit })
|
||||||
|
|
||||||
if (path) {
|
if (path) {
|
||||||
const lang = languageFromPath(path)
|
const lang = languageFromPath(path)
|
||||||
@@ -572,7 +581,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
gitShowFile(folderPath, path, commit).catch(() => ""),
|
gitShowFile(folderPath, path, commit).catch(() => ""),
|
||||||
]),
|
]),
|
||||||
20_000,
|
20_000,
|
||||||
"Commit diff request timed out"
|
t("commitDiffRequestTimedOut")
|
||||||
)
|
)
|
||||||
resolveRichDiffTab(tabId, originalContent, modifiedContent)
|
resolveRichDiffTab(tabId, originalContent, modifiedContent)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -590,9 +599,9 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
const result = await withTimeout(
|
const result = await withTimeout(
|
||||||
gitShowDiff(folderPath, commit, undefined),
|
gitShowDiff(folderPath, commit, undefined),
|
||||||
20_000,
|
20_000,
|
||||||
"Commit diff request timed out"
|
t("commitDiffRequestTimedOut")
|
||||||
)
|
)
|
||||||
resolveTab(tabId, result || "No diff output.", false)
|
resolveTab(tabId, result || t("noDiffOutput"), false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rejectTab(
|
rejectTab(
|
||||||
tabId,
|
tabId,
|
||||||
@@ -601,14 +610,14 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[folderPath, rejectTab, resolveTab, resolveRichDiffTab, upsertLoadingTab]
|
[folderPath, rejectTab, resolveTab, resolveRichDiffTab, t, upsertLoadingTab]
|
||||||
)
|
)
|
||||||
|
|
||||||
const openSessionFileDiff = useCallback(
|
const openSessionFileDiff = useCallback(
|
||||||
(filePath: string, diffContent: string, groupLabel: string) => {
|
(filePath: string, diffContent: string, groupLabel: string) => {
|
||||||
const path = normalizePath(filePath)
|
const path = normalizePath(filePath)
|
||||||
const tabId = `diff:session:${groupLabel}:${path}`
|
const tabId = `diff:session:${groupLabel}:${path}`
|
||||||
const title = `Diff · ${fileName(path)}`
|
const title = t("diffTitleFile", { name: fileName(path) })
|
||||||
const description = `${path} · ${groupLabel}`
|
const description = `${path} · ${groupLabel}`
|
||||||
|
|
||||||
const tab: FileWorkspaceTab = {
|
const tab: FileWorkspaceTab = {
|
||||||
@@ -624,15 +633,15 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
|
|
||||||
upsertLoadingTab(tab)
|
upsertLoadingTab(tab)
|
||||||
},
|
},
|
||||||
[upsertLoadingTab]
|
[t, upsertLoadingTab]
|
||||||
)
|
)
|
||||||
|
|
||||||
const openExternalConflictDiff = useCallback(
|
const openExternalConflictDiff = useCallback(
|
||||||
(filePath: string, diskContent: string, unsavedContent: string) => {
|
(filePath: string, diskContent: string, unsavedContent: string) => {
|
||||||
const path = normalizePath(filePath)
|
const path = normalizePath(filePath)
|
||||||
const tabId = `diff:external-conflict:${path}`
|
const tabId = `diff:external-conflict:${path}`
|
||||||
const title = `Conflict · ${fileName(path)}`
|
const title = t("diffTitleConflictFile", { name: fileName(path) })
|
||||||
const description = `${path} · disk vs unsaved`
|
const description = t("diffDescriptionConflict", { path })
|
||||||
const language = languageFromPath(path)
|
const language = languageFromPath(path)
|
||||||
|
|
||||||
const tab: FileWorkspaceTab = {
|
const tab: FileWorkspaceTab = {
|
||||||
@@ -650,7 +659,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
|
|
||||||
upsertLoadingTab(tab)
|
upsertLoadingTab(tab)
|
||||||
},
|
},
|
||||||
[upsertLoadingTab]
|
[t, upsertLoadingTab]
|
||||||
)
|
)
|
||||||
|
|
||||||
const updateActiveFileContent = useCallback(
|
const updateActiveFileContent = useCallback(
|
||||||
@@ -712,7 +721,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
expectedEtag
|
expectedEtag
|
||||||
),
|
),
|
||||||
20_000,
|
20_000,
|
||||||
"Save request timed out"
|
t("saveRequestTimedOut")
|
||||||
)
|
)
|
||||||
|
|
||||||
setFileTabs((prev) =>
|
setFileTabs((prev) =>
|
||||||
@@ -753,7 +762,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[folderPath]
|
[folderPath, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const saveActiveFile = useCallback(
|
const saveActiveFile = useCallback(
|
||||||
@@ -799,7 +808,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
})(),
|
})(),
|
||||||
]),
|
]),
|
||||||
15_000,
|
15_000,
|
||||||
"Reload request timed out"
|
t("reloadRequestTimedOut")
|
||||||
)
|
)
|
||||||
setFileTabs((prev) =>
|
setFileTabs((prev) =>
|
||||||
prev.map((candidate) =>
|
prev.map((candidate) =>
|
||||||
@@ -838,7 +847,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[folderPath]
|
[folderPath, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const reloadActiveFile = useCallback(async () => {
|
const reloadActiveFile = useCallback(async () => {
|
||||||
@@ -866,7 +875,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
const tab = prev[idx]
|
const tab = prev[idx]
|
||||||
if (isDirtyFileTab(tab)) {
|
if (isDirtyFileTab(tab)) {
|
||||||
const confirmed = window.confirm(
|
const confirmed = window.confirm(
|
||||||
`“${tab.title}” 有未保存更改,确定关闭吗?`
|
t("confirmCloseDirtyTab", { title: tab.title })
|
||||||
)
|
)
|
||||||
if (!confirmed) return prev
|
if (!confirmed) return prev
|
||||||
}
|
}
|
||||||
@@ -886,7 +895,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[activateConversationPane]
|
[activateConversationPane, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const closeOtherFileTabs = useCallback(
|
const closeOtherFileTabs = useCallback(
|
||||||
@@ -897,9 +906,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
|
|
||||||
const closingTabs = prev.filter((tab) => tab.id !== tabId)
|
const closingTabs = prev.filter((tab) => tab.id !== tabId)
|
||||||
if (closingTabs.some(isDirtyFileTab)) {
|
if (closingTabs.some(isDirtyFileTab)) {
|
||||||
const confirmed = window.confirm(
|
const confirmed = window.confirm(t("confirmCloseOtherDirtyTabs"))
|
||||||
"存在未保存文件,确定关闭其它标签页吗?"
|
|
||||||
)
|
|
||||||
if (!confirmed) return prev
|
if (!confirmed) return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,15 +915,13 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
return remaining
|
return remaining
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[activateFilePane]
|
[activateFilePane, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const closeAllFileTabs = useCallback(() => {
|
const closeAllFileTabs = useCallback(() => {
|
||||||
setFileTabs((prev) => {
|
setFileTabs((prev) => {
|
||||||
if (prev.some(isDirtyFileTab)) {
|
if (prev.some(isDirtyFileTab)) {
|
||||||
const confirmed = window.confirm(
|
const confirmed = window.confirm(t("confirmCloseAllDirtyTabs"))
|
||||||
"存在未保存文件,确定关闭全部标签页吗?"
|
|
||||||
)
|
|
||||||
if (!confirmed) return prev
|
if (!confirmed) return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -924,7 +929,7 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
|
|||||||
activateConversationPane()
|
activateConversationPane()
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
}, [activateConversationPane])
|
}, [activateConversationPane, t])
|
||||||
|
|
||||||
const reorderFileTabs = useCallback((tabs: FileWorkspaceTab[]) => {
|
const reorderFileTabs = useCallback((tabs: FileWorkspaceTab[]) => {
|
||||||
setFileTabs(tabs)
|
setFileTabs(tabs)
|
||||||
|
|||||||
@@ -513,6 +513,11 @@
|
|||||||
"SettingsPages": {
|
"SettingsPages": {
|
||||||
"agentsLoading": "Loading agent settings..."
|
"agentsLoading": "Loading agent settings..."
|
||||||
},
|
},
|
||||||
|
"CommitPage": {
|
||||||
|
"title": "Commit",
|
||||||
|
"invalidFolderId": "Invalid folder ID",
|
||||||
|
"loadingRepo": "Loading repository..."
|
||||||
|
},
|
||||||
"Folder": {
|
"Folder": {
|
||||||
"common": {
|
"common": {
|
||||||
"all": "All",
|
"all": "All",
|
||||||
@@ -1023,6 +1028,33 @@
|
|||||||
"saving": "Saving..."
|
"saving": "Saving..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"workspaceContext": {
|
||||||
|
"confirmCloseDirtyTab": "Close \"{title}\" without saving?",
|
||||||
|
"confirmCloseOtherDirtyTabs": "Close other tabs with unsaved changes?",
|
||||||
|
"confirmCloseAllDirtyTabs": "Close all tabs with unsaved changes?",
|
||||||
|
"unableLoadContent": "Unable to load content.\n\n{message}",
|
||||||
|
"previewRequestTimedOut": "Preview request timed out",
|
||||||
|
"diffRequestTimedOut": "Diff request timed out",
|
||||||
|
"branchCompareRequestTimedOut": "Branch compare request timed out",
|
||||||
|
"commitDiffRequestTimedOut": "Commit diff request timed out",
|
||||||
|
"saveRequestTimedOut": "Save request timed out",
|
||||||
|
"reloadRequestTimedOut": "Reload request timed out",
|
||||||
|
"noChanges": "No changes.",
|
||||||
|
"noDiffOutput": "No diff output.",
|
||||||
|
"diffTitleWorkspace": "Diff · Workspace",
|
||||||
|
"diffDescriptionWorkingTree": "Working tree (HEAD)",
|
||||||
|
"diffTitleFile": "Diff · {name}",
|
||||||
|
"compareTitleFile": "Compare · {name}",
|
||||||
|
"compareTitleBranch": "Compare · {branch}",
|
||||||
|
"compareDescriptionPath": "{path} · compare with {branch}",
|
||||||
|
"compareDescriptionBranch": "compare with {branch}",
|
||||||
|
"diffTitleCommitFile": "Diff · {name} @ {hash}",
|
||||||
|
"diffTitleCommit": "Diff · {hash}",
|
||||||
|
"diffDescriptionCommitPath": "{path} · commit {commit}",
|
||||||
|
"diffDescriptionCommit": "commit {commit}",
|
||||||
|
"diffTitleConflictFile": "Conflict · {name}",
|
||||||
|
"diffDescriptionConflict": "{path} · disk vs unsaved"
|
||||||
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"chatInput": {
|
"chatInput": {
|
||||||
"connecting": "Connecting...",
|
"connecting": "Connecting...",
|
||||||
@@ -1078,6 +1110,49 @@
|
|||||||
"moreFiles": "+{count} more files",
|
"moreFiles": "+{count} more files",
|
||||||
"plan": "Plan",
|
"plan": "Plan",
|
||||||
"targetMode": "Target mode: {mode}"
|
"targetMode": "Target mode: {mode}"
|
||||||
|
},
|
||||||
|
"messageBranch": {
|
||||||
|
"previousBranchAria": "Previous branch",
|
||||||
|
"nextBranchAria": "Next branch",
|
||||||
|
"pageOf": "{current} of {total}"
|
||||||
|
},
|
||||||
|
"terminal": {
|
||||||
|
"title": "Terminal",
|
||||||
|
"running": "Running"
|
||||||
|
},
|
||||||
|
"reasoning": {
|
||||||
|
"thinking": "Thinking...",
|
||||||
|
"thoughtForFewSeconds": "Thought for a few seconds",
|
||||||
|
"thoughtForSeconds": "Thought for {duration} seconds"
|
||||||
|
},
|
||||||
|
"messageList": {
|
||||||
|
"attachedResources": "Attached resources",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"error": "Error: {message}",
|
||||||
|
"emptyConversation": "No messages in this conversation."
|
||||||
|
},
|
||||||
|
"liveTurnStats": {
|
||||||
|
"thinking": "Thinking...",
|
||||||
|
"streaming": "Streaming",
|
||||||
|
"elapsedMinutes": "{value}m",
|
||||||
|
"elapsedSeconds": "{value}s",
|
||||||
|
"toolUseCount": "{count} tool {count, plural, one {use} other {uses}}"
|
||||||
|
},
|
||||||
|
"tool": {
|
||||||
|
"parameters": "Parameters",
|
||||||
|
"status": {
|
||||||
|
"approvalRequested": "Awaiting Approval",
|
||||||
|
"approvalResponded": "Responded",
|
||||||
|
"inputAvailable": "Running",
|
||||||
|
"inputStreaming": "Pending",
|
||||||
|
"outputAvailable": "Completed",
|
||||||
|
"outputDenied": "Denied",
|
||||||
|
"outputError": "Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contentParts": {
|
||||||
|
"showingTailOutput": "Showing tail output while streaming for performance.",
|
||||||
|
"result": "Result"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"diffPreview": {
|
"diffPreview": {
|
||||||
|
|||||||
@@ -513,6 +513,11 @@
|
|||||||
"SettingsPages": {
|
"SettingsPages": {
|
||||||
"agentsLoading": "加载 Agent 设置中..."
|
"agentsLoading": "加载 Agent 设置中..."
|
||||||
},
|
},
|
||||||
|
"CommitPage": {
|
||||||
|
"title": "提交代码",
|
||||||
|
"invalidFolderId": "无效的 folderId",
|
||||||
|
"loadingRepo": "正在加载仓库..."
|
||||||
|
},
|
||||||
"Folder": {
|
"Folder": {
|
||||||
"common": {
|
"common": {
|
||||||
"all": "全部",
|
"all": "全部",
|
||||||
@@ -1023,6 +1028,33 @@
|
|||||||
"saving": "保存中..."
|
"saving": "保存中..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"workspaceContext": {
|
||||||
|
"confirmCloseDirtyTab": "文件“{title}”有未保存更改,确定关闭吗?",
|
||||||
|
"confirmCloseOtherDirtyTabs": "其它标签页有未保存更改,确定关闭吗?",
|
||||||
|
"confirmCloseAllDirtyTabs": "存在未保存更改,确定关闭全部标签页吗?",
|
||||||
|
"unableLoadContent": "无法加载内容。\n\n{message}",
|
||||||
|
"previewRequestTimedOut": "预览请求超时",
|
||||||
|
"diffRequestTimedOut": "Diff 请求超时",
|
||||||
|
"branchCompareRequestTimedOut": "分支比较请求超时",
|
||||||
|
"commitDiffRequestTimedOut": "提交差异请求超时",
|
||||||
|
"saveRequestTimedOut": "保存请求超时",
|
||||||
|
"reloadRequestTimedOut": "重载请求超时",
|
||||||
|
"noChanges": "暂无变更。",
|
||||||
|
"noDiffOutput": "无差异输出。",
|
||||||
|
"diffTitleWorkspace": "Diff · 工作区",
|
||||||
|
"diffDescriptionWorkingTree": "工作区变更(HEAD)",
|
||||||
|
"diffTitleFile": "Diff · {name}",
|
||||||
|
"compareTitleFile": "比较 · {name}",
|
||||||
|
"compareTitleBranch": "比较 · {branch}",
|
||||||
|
"compareDescriptionPath": "{path} · 与 {branch} 比较",
|
||||||
|
"compareDescriptionBranch": "与 {branch} 比较",
|
||||||
|
"diffTitleCommitFile": "Diff · {name} @ {hash}",
|
||||||
|
"diffTitleCommit": "Diff · {hash}",
|
||||||
|
"diffDescriptionCommitPath": "{path} · 提交 {commit}",
|
||||||
|
"diffDescriptionCommit": "提交 {commit}",
|
||||||
|
"diffTitleConflictFile": "冲突 · {name}",
|
||||||
|
"diffDescriptionConflict": "{path} · 磁盘与未保存内容"
|
||||||
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"chatInput": {
|
"chatInput": {
|
||||||
"connecting": "连接中...",
|
"connecting": "连接中...",
|
||||||
@@ -1078,6 +1110,49 @@
|
|||||||
"moreFiles": "+{count} 个更多文件",
|
"moreFiles": "+{count} 个更多文件",
|
||||||
"plan": "计划",
|
"plan": "计划",
|
||||||
"targetMode": "目标模式:{mode}"
|
"targetMode": "目标模式:{mode}"
|
||||||
|
},
|
||||||
|
"messageBranch": {
|
||||||
|
"previousBranchAria": "上一分支",
|
||||||
|
"nextBranchAria": "下一分支",
|
||||||
|
"pageOf": "{current} / {total}"
|
||||||
|
},
|
||||||
|
"terminal": {
|
||||||
|
"title": "终端",
|
||||||
|
"running": "运行中"
|
||||||
|
},
|
||||||
|
"reasoning": {
|
||||||
|
"thinking": "思考中...",
|
||||||
|
"thoughtForFewSeconds": "思考了几秒",
|
||||||
|
"thoughtForSeconds": "思考了 {duration} 秒"
|
||||||
|
},
|
||||||
|
"messageList": {
|
||||||
|
"attachedResources": "附加资源",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"error": "错误:{message}",
|
||||||
|
"emptyConversation": "当前会话暂无消息。"
|
||||||
|
},
|
||||||
|
"liveTurnStats": {
|
||||||
|
"thinking": "思考中...",
|
||||||
|
"streaming": "生成中",
|
||||||
|
"elapsedMinutes": "{value} 分钟",
|
||||||
|
"elapsedSeconds": "{value} 秒",
|
||||||
|
"toolUseCount": "{count} 次工具调用"
|
||||||
|
},
|
||||||
|
"tool": {
|
||||||
|
"parameters": "参数",
|
||||||
|
"status": {
|
||||||
|
"approvalRequested": "等待授权",
|
||||||
|
"approvalResponded": "已响应",
|
||||||
|
"inputAvailable": "运行中",
|
||||||
|
"inputStreaming": "等待中",
|
||||||
|
"outputAvailable": "已完成",
|
||||||
|
"outputDenied": "已拒绝",
|
||||||
|
"outputError": "错误"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contentParts": {
|
||||||
|
"showingTailOutput": "为保证性能,流式输出时仅显示尾部内容。",
|
||||||
|
"result": "结果"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"diffPreview": {
|
"diffPreview": {
|
||||||
|
|||||||
@@ -513,6 +513,11 @@
|
|||||||
"SettingsPages": {
|
"SettingsPages": {
|
||||||
"agentsLoading": "載入 Agent 設定中..."
|
"agentsLoading": "載入 Agent 設定中..."
|
||||||
},
|
},
|
||||||
|
"CommitPage": {
|
||||||
|
"title": "提交程式碼",
|
||||||
|
"invalidFolderId": "無效的 folderId",
|
||||||
|
"loadingRepo": "正在載入倉庫..."
|
||||||
|
},
|
||||||
"Folder": {
|
"Folder": {
|
||||||
"common": {
|
"common": {
|
||||||
"all": "全部",
|
"all": "全部",
|
||||||
@@ -1023,6 +1028,33 @@
|
|||||||
"saving": "儲存中..."
|
"saving": "儲存中..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"workspaceContext": {
|
||||||
|
"confirmCloseDirtyTab": "檔案「{title}」有未儲存變更,確定關閉嗎?",
|
||||||
|
"confirmCloseOtherDirtyTabs": "其他分頁有未儲存變更,確定關閉嗎?",
|
||||||
|
"confirmCloseAllDirtyTabs": "存在未儲存變更,確定關閉全部分頁嗎?",
|
||||||
|
"unableLoadContent": "無法載入內容。\n\n{message}",
|
||||||
|
"previewRequestTimedOut": "預覽請求逾時",
|
||||||
|
"diffRequestTimedOut": "Diff 請求逾時",
|
||||||
|
"branchCompareRequestTimedOut": "分支比較請求逾時",
|
||||||
|
"commitDiffRequestTimedOut": "提交差異請求逾時",
|
||||||
|
"saveRequestTimedOut": "儲存請求逾時",
|
||||||
|
"reloadRequestTimedOut": "重載請求逾時",
|
||||||
|
"noChanges": "暫無變更。",
|
||||||
|
"noDiffOutput": "無差異輸出。",
|
||||||
|
"diffTitleWorkspace": "Diff · 工作區",
|
||||||
|
"diffDescriptionWorkingTree": "工作區變更(HEAD)",
|
||||||
|
"diffTitleFile": "Diff · {name}",
|
||||||
|
"compareTitleFile": "比較 · {name}",
|
||||||
|
"compareTitleBranch": "比較 · {branch}",
|
||||||
|
"compareDescriptionPath": "{path} · 與 {branch} 比較",
|
||||||
|
"compareDescriptionBranch": "與 {branch} 比較",
|
||||||
|
"diffTitleCommitFile": "Diff · {name} @ {hash}",
|
||||||
|
"diffTitleCommit": "Diff · {hash}",
|
||||||
|
"diffDescriptionCommitPath": "{path} · 提交 {commit}",
|
||||||
|
"diffDescriptionCommit": "提交 {commit}",
|
||||||
|
"diffTitleConflictFile": "衝突 · {name}",
|
||||||
|
"diffDescriptionConflict": "{path} · 磁碟與未儲存內容"
|
||||||
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"chatInput": {
|
"chatInput": {
|
||||||
"connecting": "連線中...",
|
"connecting": "連線中...",
|
||||||
@@ -1078,6 +1110,49 @@
|
|||||||
"moreFiles": "+{count} 個更多檔案",
|
"moreFiles": "+{count} 個更多檔案",
|
||||||
"plan": "計畫",
|
"plan": "計畫",
|
||||||
"targetMode": "目標模式:{mode}"
|
"targetMode": "目標模式:{mode}"
|
||||||
|
},
|
||||||
|
"messageBranch": {
|
||||||
|
"previousBranchAria": "上一個分支",
|
||||||
|
"nextBranchAria": "下一個分支",
|
||||||
|
"pageOf": "{current} / {total}"
|
||||||
|
},
|
||||||
|
"terminal": {
|
||||||
|
"title": "終端",
|
||||||
|
"running": "執行中"
|
||||||
|
},
|
||||||
|
"reasoning": {
|
||||||
|
"thinking": "思考中...",
|
||||||
|
"thoughtForFewSeconds": "思考了幾秒",
|
||||||
|
"thoughtForSeconds": "思考了 {duration} 秒"
|
||||||
|
},
|
||||||
|
"messageList": {
|
||||||
|
"attachedResources": "附加資源",
|
||||||
|
"loading": "載入中...",
|
||||||
|
"error": "錯誤:{message}",
|
||||||
|
"emptyConversation": "目前會話暫無訊息。"
|
||||||
|
},
|
||||||
|
"liveTurnStats": {
|
||||||
|
"thinking": "思考中...",
|
||||||
|
"streaming": "生成中",
|
||||||
|
"elapsedMinutes": "{value} 分鐘",
|
||||||
|
"elapsedSeconds": "{value} 秒",
|
||||||
|
"toolUseCount": "{count} 次工具呼叫"
|
||||||
|
},
|
||||||
|
"tool": {
|
||||||
|
"parameters": "參數",
|
||||||
|
"status": {
|
||||||
|
"approvalRequested": "等待授權",
|
||||||
|
"approvalResponded": "已回應",
|
||||||
|
"inputAvailable": "執行中",
|
||||||
|
"inputStreaming": "等待中",
|
||||||
|
"outputAvailable": "已完成",
|
||||||
|
"outputDenied": "已拒絕",
|
||||||
|
"outputError": "錯誤"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contentParts": {
|
||||||
|
"showingTailOutput": "為確保效能,串流輸出時僅顯示尾端內容。",
|
||||||
|
"result": "結果"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"diffPreview": {
|
"diffPreview": {
|
||||||
|
|||||||
Reference in New Issue
Block a user