继续多语言补充
This commit is contained in:
@@ -3,6 +3,7 @@ import type { BundledLanguage } from "shiki"
|
||||
import type { AdaptedContentPart } from "@/lib/adapters/ai-elements-adapter"
|
||||
import type { MessageRole } from "@/lib/types"
|
||||
import { normalizeToolName } from "@/lib/tool-call-normalization"
|
||||
import { useTranslations } from "next-intl"
|
||||
import {
|
||||
countUnifiedDiffLineChanges,
|
||||
estimateChangedLineStats,
|
||||
@@ -1881,6 +1882,7 @@ const ToolCallPart = memo(function ToolCallPart({
|
||||
}: {
|
||||
part: Extract<AdaptedContentPart, { type: "tool-call" }>
|
||||
}) {
|
||||
const t = useTranslations("Folder.chat.contentParts")
|
||||
const [manualOpen, setManualOpen] = useState(false)
|
||||
const normalizedToolName = useMemo(
|
||||
() => normalizeToolName(part.toolName),
|
||||
@@ -2046,7 +2048,7 @@ const ToolCallPart = memo(function ToolCallPart({
|
||||
/>
|
||||
{liveOutputTruncated && (
|
||||
<div className="text-[11px] text-muted-foreground">
|
||||
Showing tail output while streaming for performance.
|
||||
{t("showingTailOutput")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -2068,9 +2070,14 @@ const ToolResultPart = memo(function ToolResultPart({
|
||||
}: {
|
||||
part: Extract<AdaptedContentPart, { type: "tool-result" }>
|
||||
}) {
|
||||
const t = useTranslations("Folder.chat.contentParts")
|
||||
return (
|
||||
<Tool>
|
||||
<ToolHeader type="dynamic-tool" state={part.state} toolName="Result" />
|
||||
<ToolHeader
|
||||
type="dynamic-tool"
|
||||
state={part.state}
|
||||
toolName={t("result")}
|
||||
/>
|
||||
<ToolContent>
|
||||
<ToolOutput output={part.output} errorText={part.errorText} />
|
||||
</ToolContent>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useLocale, useTranslations } from "next-intl"
|
||||
import type { LiveMessage } from "@/contexts/acp-connections-context"
|
||||
import { inferLiveToolName } from "@/lib/tool-call-normalization"
|
||||
import {
|
||||
@@ -9,11 +10,6 @@ import {
|
||||
} from "@/lib/line-change-stats"
|
||||
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 {
|
||||
message: LiveMessage
|
||||
}
|
||||
@@ -27,14 +23,9 @@ interface LiveEditStats extends LineChangeStats {
|
||||
files: number
|
||||
}
|
||||
|
||||
const COMPACT_NUMBER = new Intl.NumberFormat("en-US", {
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1,
|
||||
})
|
||||
|
||||
function formatCompactInt(n: number): string {
|
||||
function formatCompactInt(n: number, formatter: Intl.NumberFormat): string {
|
||||
if (n < 1000) return String(n)
|
||||
return COMPACT_NUMBER.format(n).toUpperCase()
|
||||
return formatter.format(n)
|
||||
}
|
||||
|
||||
function asObject(value: unknown): Record<string, unknown> | null {
|
||||
@@ -267,8 +258,18 @@ function extractLiveEditStats(message: LiveMessage): LiveEditStats {
|
||||
}
|
||||
|
||||
export function LiveTurnStats({ message }: LiveTurnStatsProps) {
|
||||
const locale = useLocale()
|
||||
const t = useTranslations("Folder.chat.liveTurnStats")
|
||||
const [elapsed, setElapsed] = useState(() => Date.now() - message.startedAt)
|
||||
const editStats = useMemo(() => extractLiveEditStats(message), [message])
|
||||
const compactNumberFormatter = useMemo(
|
||||
() =>
|
||||
new Intl.NumberFormat(locale, {
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1,
|
||||
}),
|
||||
[locale]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
@@ -295,26 +296,32 @@ export function LiveTurnStats({ message }: LiveTurnStatsProps) {
|
||||
isThinking = true
|
||||
}
|
||||
|
||||
const elapsedLabel =
|
||||
elapsed >= 60_000
|
||||
? t("elapsedMinutes", { value: (elapsed / 60_000).toFixed(1) })
|
||||
: t("elapsedSeconds", { value: (elapsed / 1_000).toFixed(1) })
|
||||
|
||||
return (
|
||||
<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" />
|
||||
{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="inline-flex items-center gap-1 leading-none">
|
||||
<Timer className="h-3 w-3 shrink-0" />
|
||||
{formatElapsed(elapsed)}
|
||||
{elapsedLabel}
|
||||
</span>
|
||||
{editStats.files > 0 && (
|
||||
<>
|
||||
<span className="text-border leading-none">|</span>
|
||||
<span className="inline-flex items-center gap-1 leading-none">
|
||||
<FilePenLine className="h-3 w-3 shrink-0" />
|
||||
{editStats.files}F +{formatCompactInt(editStats.additions)}/-
|
||||
{formatCompactInt(editStats.deletions)}
|
||||
{editStats.files}F +
|
||||
{formatCompactInt(editStats.additions, compactNumberFormatter)}/-
|
||||
{formatCompactInt(editStats.deletions, compactNumberFormatter)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
@@ -323,7 +330,7 @@ export function LiveTurnStats({ message }: LiveTurnStatsProps) {
|
||||
<span className="text-border leading-none">|</span>
|
||||
<span className="inline-flex items-center gap-1 leading-none">
|
||||
<Wrench className="h-3 w-3 shrink-0" />
|
||||
{toolCallCount} tool {toolCallCount === 1 ? "use" : "uses"}
|
||||
{t("toolUseCount", { count: toolCallCount })}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "@/components/ai-elements/message-thread"
|
||||
import { Message, MessageContent } from "@/components/ai-elements/message"
|
||||
import { Loader2 } from "lucide-react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import {
|
||||
buildPlanKey,
|
||||
extractLatestPlanEntriesFromMessages,
|
||||
@@ -46,7 +47,10 @@ interface ResolvedMessageGroup extends MessageGroup {
|
||||
resources: UserResourceDisplay[]
|
||||
}
|
||||
|
||||
function fallbackExtractUserResources(group: MessageGroup): {
|
||||
function fallbackExtractUserResources(
|
||||
group: MessageGroup,
|
||||
attachedResourcesText: string
|
||||
): {
|
||||
parts: AdaptedContentPart[]
|
||||
resources: UserResourceDisplay[]
|
||||
} {
|
||||
@@ -87,14 +91,17 @@ function fallbackExtractUserResources(group: MessageGroup): {
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
function resolveMessageGroup(group: MessageGroup): ResolvedMessageGroup {
|
||||
const resolved = fallbackExtractUserResources(group)
|
||||
function resolveMessageGroup(
|
||||
group: MessageGroup,
|
||||
attachedResourcesText: string
|
||||
): ResolvedMessageGroup {
|
||||
const resolved = fallbackExtractUserResources(group, attachedResourcesText)
|
||||
return {
|
||||
...group,
|
||||
parts: resolved.parts,
|
||||
@@ -161,6 +168,7 @@ export function MessageListView({
|
||||
onPendingClear,
|
||||
isActive = true,
|
||||
}: MessageListViewProps) {
|
||||
const t = useTranslations("Folder.chat.messageList")
|
||||
const { detail, loading, error, refetch } = useDbMessageDetail(conversationId)
|
||||
const turnCount = detail?.turns.length ?? 0
|
||||
|
||||
@@ -225,12 +233,16 @@ export function MessageListView({
|
||||
[pendingMessages]
|
||||
)
|
||||
const resolvedGroups = useMemo(
|
||||
() => groups.map(resolveMessageGroup),
|
||||
[groups]
|
||||
() =>
|
||||
groups.map((group) => resolveMessageGroup(group, t("attachedResources"))),
|
||||
[groups, t]
|
||||
)
|
||||
const resolvedPendingGroups = useMemo(
|
||||
() => pendingGroups.map(resolveMessageGroup),
|
||||
[pendingGroups]
|
||||
() =>
|
||||
pendingGroups.map((group) =>
|
||||
resolveMessageGroup(group, t("attachedResources"))
|
||||
),
|
||||
[pendingGroups, t]
|
||||
)
|
||||
|
||||
const showLiveMessage = Boolean(
|
||||
@@ -245,7 +257,7 @@ export function MessageListView({
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<span>Loading...</span>
|
||||
<span>{t("loading")}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -255,7 +267,9 @@ export function MessageListView({
|
||||
return (
|
||||
<div className="p-6">
|
||||
<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>
|
||||
)
|
||||
@@ -276,7 +290,7 @@ export function MessageListView({
|
||||
!showLiveMessage ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
No messages in this conversation.
|
||||
{t("emptyConversation")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user