mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-24 22:54:46 +08:00
feat: implement i18n
This commit is contained in:
@@ -23,6 +23,7 @@ import { ThreadContext } from "@/components/workspace/messages/context";
|
|||||||
import { ThreadTitle } from "@/components/workspace/thread-title";
|
import { ThreadTitle } from "@/components/workspace/thread-title";
|
||||||
import { Tooltip } from "@/components/workspace/tooltip";
|
import { Tooltip } from "@/components/workspace/tooltip";
|
||||||
import { Welcome } from "@/components/workspace/welcome";
|
import { Welcome } from "@/components/workspace/welcome";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { useLocalSettings } from "@/core/settings";
|
import { useLocalSettings } from "@/core/settings";
|
||||||
import { type AgentThread } from "@/core/threads";
|
import { type AgentThread } from "@/core/threads";
|
||||||
import { useSubmitThread, useThreadStream } from "@/core/threads/hooks";
|
import { useSubmitThread, useThreadStream } from "@/core/threads/hooks";
|
||||||
@@ -31,6 +32,7 @@ import { uuid } from "@/core/utils/uuid";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [settings, setSettings] = useLocalSettings();
|
const [settings, setSettings] = useLocalSettings();
|
||||||
const { setOpen: setSidebarOpen } = useSidebar();
|
const { setOpen: setSidebarOpen } = useSidebar();
|
||||||
@@ -41,7 +43,6 @@ export default function ChatPage() {
|
|||||||
setArtifacts,
|
setArtifacts,
|
||||||
selectedArtifact,
|
selectedArtifact,
|
||||||
} = useArtifacts();
|
} = useArtifacts();
|
||||||
|
|
||||||
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
||||||
const isNewThread = useMemo(
|
const isNewThread = useMemo(
|
||||||
() => threadIdFromPath === "new",
|
() => threadIdFromPath === "new",
|
||||||
@@ -117,7 +118,7 @@ export default function ChatPage() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FilesIcon />
|
<FilesIcon />
|
||||||
Artifacts
|
{t.common.artifacts}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import {
|
|||||||
WorkspaceContainer,
|
WorkspaceContainer,
|
||||||
WorkspaceHeader,
|
WorkspaceHeader,
|
||||||
} from "@/components/workspace/workspace-container";
|
} from "@/components/workspace/workspace-container";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { useThreads } from "@/core/threads/hooks";
|
import { useThreads } from "@/core/threads/hooks";
|
||||||
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
||||||
import { formatTimeAgo } from "@/core/utils/datetime";
|
import { formatTimeAgo } from "@/core/utils/datetime";
|
||||||
|
|
||||||
export default function ChatsPage() {
|
export default function ChatsPage() {
|
||||||
|
const { t } = useI18n();
|
||||||
const { data: threads } = useThreads();
|
const { data: threads } = useThreads();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const filteredThreads = useMemo(() => {
|
const filteredThreads = useMemo(() => {
|
||||||
@@ -31,7 +33,7 @@ export default function ChatsPage() {
|
|||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
className="h-12 w-full max-w-(--container-width-md) text-xl"
|
className="h-12 w-full max-w-(--container-width-md) text-xl"
|
||||||
placeholder="Search chats"
|
placeholder={t.chats.searchChats}
|
||||||
autoFocus
|
autoFocus
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||||
import { useArtifactContent } from "@/core/artifacts/hooks";
|
import { useArtifactContent } from "@/core/artifacts/hooks";
|
||||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { checkCodeFile, getFileName } from "@/core/utils/files";
|
import { checkCodeFile, getFileName } from "@/core/utils/files";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ export function ArtifactFileDetail({
|
|||||||
filepath: string;
|
filepath: string;
|
||||||
threadId: string;
|
threadId: string;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useI18n();
|
||||||
const { artifacts, setOpen, select } = useArtifacts();
|
const { artifacts, setOpen, select } = useArtifacts();
|
||||||
const isWriteFile = useMemo(() => {
|
const isWriteFile = useMemo(() => {
|
||||||
return filepathFromProps.startsWith("write-file:");
|
return filepathFromProps.startsWith("write-file:");
|
||||||
@@ -124,26 +126,26 @@ export function ArtifactFileDetail({
|
|||||||
<a href={urlOfArtifact({ filepath, threadId })} target="_blank">
|
<a href={urlOfArtifact({ filepath, threadId })} target="_blank">
|
||||||
<ArtifactAction
|
<ArtifactAction
|
||||||
icon={SquareArrowOutUpRightIcon}
|
icon={SquareArrowOutUpRightIcon}
|
||||||
label="Open in new window"
|
label={t.common.openInNewWindow}
|
||||||
tooltip="Open in new window"
|
tooltip={t.common.openInNewWindow}
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{isCodeFile && (
|
{isCodeFile && (
|
||||||
<ArtifactAction
|
<ArtifactAction
|
||||||
icon={CopyIcon}
|
icon={CopyIcon}
|
||||||
label="Copy"
|
label={t.clipboard.copyToClipboard}
|
||||||
disabled={!content}
|
disabled={!content}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(content ?? "");
|
await navigator.clipboard.writeText(content ?? "");
|
||||||
toast.success("Copied to clipboard");
|
toast.success(t.clipboard.copiedToClipboard);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Failed to copy to clipboard");
|
toast.error("Failed to copy to clipboard");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
tooltip="Copy content to clipboard"
|
tooltip={t.clipboard.copyToClipboard}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isWriteFile && (
|
{!isWriteFile && (
|
||||||
@@ -153,17 +155,16 @@ export function ArtifactFileDetail({
|
|||||||
>
|
>
|
||||||
<ArtifactAction
|
<ArtifactAction
|
||||||
icon={DownloadIcon}
|
icon={DownloadIcon}
|
||||||
label="Download"
|
label={t.common.download}
|
||||||
onClick={() => console.log("Download")}
|
tooltip={t.common.download}
|
||||||
tooltip="Download file"
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
<ArtifactAction
|
<ArtifactAction
|
||||||
icon={XIcon}
|
icon={XIcon}
|
||||||
label="Close"
|
label={t.common.close}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
tooltip="Close"
|
tooltip={t.common.close}
|
||||||
/>
|
/>
|
||||||
</ArtifactActions>
|
</ArtifactActions>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { getFileExtensionDisplayName, getFileName } from "@/core/utils/files";
|
import { getFileExtensionDisplayName, getFileName } from "@/core/utils/files";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ export function ArtifactFileList({
|
|||||||
files: string[];
|
files: string[];
|
||||||
threadId: string;
|
threadId: string;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useI18n();
|
||||||
const { select: selectArtifact, setOpen } = useArtifacts();
|
const { select: selectArtifact, setOpen } = useArtifacts();
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(filepath: string) => {
|
(filepath: string) => {
|
||||||
@@ -57,7 +59,7 @@ export function ArtifactFileList({
|
|||||||
>
|
>
|
||||||
<Button variant="ghost">
|
<Button variant="ghost">
|
||||||
<DownloadIcon className="size-4" />
|
<DownloadIcon className="size-4" />
|
||||||
Download
|
{t.common.download}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
</CardAction>
|
</CardAction>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { CheckIcon, CopyIcon } from "lucide-react";
|
|||||||
import { useCallback, useState, type ComponentProps } from "react";
|
import { useCallback, useState, type ComponentProps } from "react";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
|
|
||||||
import { Tooltip } from "./tooltip";
|
import { Tooltip } from "./tooltip";
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ export function CopyButton({
|
|||||||
}: ComponentProps<typeof Button> & {
|
}: ComponentProps<typeof Button> & {
|
||||||
clipboardData: string;
|
clipboardData: string;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useI18n();
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback(() => {
|
||||||
void navigator.clipboard.writeText(clipboardData);
|
void navigator.clipboard.writeText(clipboardData);
|
||||||
@@ -18,7 +20,7 @@ export function CopyButton({
|
|||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
}, [clipboardData]);
|
}, [clipboardData]);
|
||||||
return (
|
return (
|
||||||
<Tooltip content="Copy to clipboard">
|
<Tooltip content={t.clipboard.copyToClipboard}>
|
||||||
<Button
|
<Button
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import type { ChatStatus } from "ai";
|
import type { ChatStatus } from "ai";
|
||||||
import { CheckIcon, LightbulbIcon, LightbulbOffIcon } from "lucide-react";
|
import { CheckIcon, LightbulbIcon, LightbulbOffIcon } from "lucide-react";
|
||||||
import { useCallback, useMemo, useState, type ComponentProps } from "react";
|
import { useCallback, useMemo, useState, type ComponentProps } from "react";
|
||||||
@@ -11,6 +13,7 @@ import {
|
|||||||
PromptInputTextarea,
|
PromptInputTextarea,
|
||||||
type PromptInputMessage,
|
type PromptInputMessage,
|
||||||
} from "@/components/ai-elements/prompt-input";
|
} from "@/components/ai-elements/prompt-input";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { useModels } from "@/core/models/hooks";
|
import { useModels } from "@/core/models/hooks";
|
||||||
import type { AgentThreadContext } from "@/core/threads";
|
import type { AgentThreadContext } from "@/core/threads";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -40,11 +43,11 @@ export function InputBox({
|
|||||||
assistantId?: string | null;
|
assistantId?: string | null;
|
||||||
status?: ChatStatus;
|
status?: ChatStatus;
|
||||||
context: Omit<AgentThreadContext, "thread_id">;
|
context: Omit<AgentThreadContext, "thread_id">;
|
||||||
showWelcome?: boolean;
|
|
||||||
onContextChange?: (context: Omit<AgentThreadContext, "thread_id">) => void;
|
onContextChange?: (context: Omit<AgentThreadContext, "thread_id">) => void;
|
||||||
onSubmit?: (message: PromptInputMessage) => void;
|
onSubmit?: (message: PromptInputMessage) => void;
|
||||||
onStop?: () => void;
|
onStop?: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useI18n();
|
||||||
const [modelDialogOpen, setModelDialogOpen] = useState(false);
|
const [modelDialogOpen, setModelDialogOpen] = useState(false);
|
||||||
const { models } = useModels();
|
const { models } = useModels();
|
||||||
const selectedModel = useMemo(
|
const selectedModel = useMemo(
|
||||||
@@ -97,7 +100,7 @@ export function InputBox({
|
|||||||
<PromptInputBody>
|
<PromptInputBody>
|
||||||
<PromptInputTextarea
|
<PromptInputTextarea
|
||||||
className={cn("size-full")}
|
className={cn("size-full")}
|
||||||
placeholder="How can I assist you today?"
|
placeholder={t.inputBox.placeholder}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
/>
|
/>
|
||||||
</PromptInputBody>
|
</PromptInputBody>
|
||||||
@@ -107,13 +110,17 @@ export function InputBox({
|
|||||||
content={
|
content={
|
||||||
context.thinking_enabled ? (
|
context.thinking_enabled ? (
|
||||||
<div className="tex-sm flex flex-col gap-1">
|
<div className="tex-sm flex flex-col gap-1">
|
||||||
<div>Thinking is enabled</div>
|
<div>{t.inputBox.thinkingEnabled}</div>
|
||||||
<div className="opacity-50">Click to disable thinking</div>
|
<div className="opacity-50">
|
||||||
|
{t.inputBox.clickToDisableThinking}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="tex-sm flex flex-col gap-1">
|
<div className="tex-sm flex flex-col gap-1">
|
||||||
<div>Thinking is disabled</div>
|
<div>{t.inputBox.thinkingDisabled}</div>
|
||||||
<div className="opacity-50">Click to enable thinking</div>
|
<div className="opacity-50">
|
||||||
|
{t.inputBox.clickToEnableThinking}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -142,7 +149,7 @@ export function InputBox({
|
|||||||
</PromptInputButton>
|
</PromptInputButton>
|
||||||
</ModelSelectorTrigger>
|
</ModelSelectorTrigger>
|
||||||
<ModelSelectorContent>
|
<ModelSelectorContent>
|
||||||
<ModelSelectorInput placeholder="Search models..." />
|
<ModelSelectorInput placeholder={t.inputBox.searchModels} />
|
||||||
<ModelSelectorList>
|
<ModelSelectorList>
|
||||||
{models.map((m) => (
|
{models.map((m) => (
|
||||||
<ModelSelectorItem
|
<ModelSelectorItem
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ import {
|
|||||||
ChainOfThoughtSearchResults,
|
ChainOfThoughtSearchResults,
|
||||||
ChainOfThoughtStep,
|
ChainOfThoughtStep,
|
||||||
} from "@/components/ai-elements/chain-of-thought";
|
} from "@/components/ai-elements/chain-of-thought";
|
||||||
|
import { CodeBlock } from "@/components/ai-elements/code-block";
|
||||||
import { MessageResponse } from "@/components/ai-elements/message";
|
import { MessageResponse } from "@/components/ai-elements/message";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import {
|
import {
|
||||||
extractReasoningContentFromMessage,
|
extractReasoningContentFromMessage,
|
||||||
findToolCallResult,
|
findToolCallResult,
|
||||||
@@ -33,7 +35,6 @@ import { cn } from "@/lib/utils";
|
|||||||
|
|
||||||
import { useArtifacts } from "../artifacts";
|
import { useArtifacts } from "../artifacts";
|
||||||
import { FlipDisplay } from "../flip-display";
|
import { FlipDisplay } from "../flip-display";
|
||||||
import { CodeBlock } from "@/components/ai-elements/code-block";
|
|
||||||
|
|
||||||
export function MessageGroup({
|
export function MessageGroup({
|
||||||
className,
|
className,
|
||||||
@@ -44,6 +45,7 @@ export function MessageGroup({
|
|||||||
messages: Message[];
|
messages: Message[];
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useI18n();
|
||||||
const [showAbove, setShowAbove] = useState(false);
|
const [showAbove, setShowAbove] = useState(false);
|
||||||
const [showLastThinking, setShowLastThinking] = useState(false);
|
const [showLastThinking, setShowLastThinking] = useState(false);
|
||||||
const steps = useMemo(() => convertToSteps(messages), [messages]);
|
const steps = useMemo(() => convertToSteps(messages), [messages]);
|
||||||
@@ -84,8 +86,8 @@ export function MessageGroup({
|
|||||||
label={
|
label={
|
||||||
<span className="opacity-60">
|
<span className="opacity-60">
|
||||||
{showAbove
|
{showAbove
|
||||||
? "Less steps"
|
? t.toolCalls.lessSteps
|
||||||
: `${aboveLastToolCallSteps.length} more step${aboveLastToolCallSteps.length === 1 ? "" : "s"}`}
|
: t.toolCalls.moreSteps(aboveLastToolCallSteps.length)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
@@ -134,7 +136,7 @@ export function MessageGroup({
|
|||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<ChainOfThoughtStep
|
<ChainOfThoughtStep
|
||||||
className="font-normal"
|
className="font-normal"
|
||||||
label="Thinking"
|
label={t.common.thinking}
|
||||||
icon={LightbulbIcon}
|
icon={LightbulbIcon}
|
||||||
></ChainOfThoughtStep>
|
></ChainOfThoughtStep>
|
||||||
<div>
|
<div>
|
||||||
@@ -178,16 +180,12 @@ function ToolCall({
|
|||||||
args: Record<string, unknown>;
|
args: Record<string, unknown>;
|
||||||
result?: string | Record<string, unknown>;
|
result?: string | Record<string, unknown>;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useI18n();
|
||||||
const { select, setOpen } = useArtifacts();
|
const { select, setOpen } = useArtifacts();
|
||||||
if (name === "web_search") {
|
if (name === "web_search") {
|
||||||
let label: React.ReactNode = "Search for related information";
|
let label: React.ReactNode = t.toolCalls.searchForRelatedInfo;
|
||||||
if (typeof args.query === "string") {
|
if (typeof args.query === "string") {
|
||||||
label = (
|
label = t.toolCalls.searchOnWebFor(args.query);
|
||||||
<div>
|
|
||||||
Search on the web for{" "}
|
|
||||||
<span className="font-bold">"{args.query}"</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ChainOfThoughtStep key={id} label={label} icon={SearchIcon}>
|
<ChainOfThoughtStep key={id} label={label} icon={SearchIcon}>
|
||||||
@@ -217,7 +215,7 @@ function ToolCall({
|
|||||||
<ChainOfThoughtStep
|
<ChainOfThoughtStep
|
||||||
key={id}
|
key={id}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
label="View web page"
|
label={t.toolCalls.viewWebPage}
|
||||||
icon={GlobeIcon}
|
icon={GlobeIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(url, "_blank");
|
window.open(url, "_blank");
|
||||||
@@ -236,7 +234,7 @@ function ToolCall({
|
|||||||
let description: string | undefined = (args as { description: string })
|
let description: string | undefined = (args as { description: string })
|
||||||
?.description;
|
?.description;
|
||||||
if (!description) {
|
if (!description) {
|
||||||
description = "List folder";
|
description = t.toolCalls.listFolder;
|
||||||
}
|
}
|
||||||
const path: string | undefined = (args as { path: string })?.path;
|
const path: string | undefined = (args as { path: string })?.path;
|
||||||
return (
|
return (
|
||||||
@@ -250,7 +248,7 @@ function ToolCall({
|
|||||||
let description: string | undefined = (args as { description: string })
|
let description: string | undefined = (args as { description: string })
|
||||||
?.description;
|
?.description;
|
||||||
if (!description) {
|
if (!description) {
|
||||||
description = "Read file";
|
description = t.toolCalls.readFile;
|
||||||
}
|
}
|
||||||
const path: string | undefined = (args as { path: string })?.path;
|
const path: string | undefined = (args as { path: string })?.path;
|
||||||
return (
|
return (
|
||||||
@@ -264,7 +262,7 @@ function ToolCall({
|
|||||||
let description: string | undefined = (args as { description: string })
|
let description: string | undefined = (args as { description: string })
|
||||||
?.description;
|
?.description;
|
||||||
if (!description) {
|
if (!description) {
|
||||||
description = "Write file";
|
description = t.toolCalls.writeFile;
|
||||||
}
|
}
|
||||||
const path: string | undefined = (args as { path: string })?.path;
|
const path: string | undefined = (args as { path: string })?.path;
|
||||||
return (
|
return (
|
||||||
@@ -291,7 +289,7 @@ function ToolCall({
|
|||||||
const description: string | undefined = (args as { description: string })
|
const description: string | undefined = (args as { description: string })
|
||||||
?.description;
|
?.description;
|
||||||
if (!description) {
|
if (!description) {
|
||||||
return "Execute command";
|
return t.toolCalls.executeCommand;
|
||||||
}
|
}
|
||||||
const command: string | undefined = (args as { command: string })?.command;
|
const command: string | undefined = (args as { command: string })?.command;
|
||||||
return (
|
return (
|
||||||
@@ -312,7 +310,11 @@ function ToolCall({
|
|||||||
);
|
);
|
||||||
} else if (name === "present_files") {
|
} else if (name === "present_files") {
|
||||||
return (
|
return (
|
||||||
<ChainOfThoughtStep key={id} label="Present files" icon={FileTextIcon}>
|
<ChainOfThoughtStep
|
||||||
|
key={id}
|
||||||
|
label={t.toolCalls.presentFiles}
|
||||||
|
icon={FileTextIcon}
|
||||||
|
>
|
||||||
<ChainOfThoughtSearchResult>
|
<ChainOfThoughtSearchResult>
|
||||||
{Array.isArray((args as { filepaths: string[] }).filepaths) &&
|
{Array.isArray((args as { filepaths: string[] }).filepaths) &&
|
||||||
(args as { filepaths: string[] }).filepaths.map(
|
(args as { filepaths: string[] }).filepaths.map(
|
||||||
@@ -330,7 +332,7 @@ function ToolCall({
|
|||||||
return (
|
return (
|
||||||
<ChainOfThoughtStep
|
<ChainOfThoughtStep
|
||||||
key={id}
|
key={id}
|
||||||
label="Need your help"
|
label={t.toolCalls.needYourHelp}
|
||||||
icon={MessageCircleQuestionMarkIcon}
|
icon={MessageCircleQuestionMarkIcon}
|
||||||
></ChainOfThoughtStep>
|
></ChainOfThoughtStep>
|
||||||
);
|
);
|
||||||
@@ -340,13 +342,7 @@ function ToolCall({
|
|||||||
return (
|
return (
|
||||||
<ChainOfThoughtStep
|
<ChainOfThoughtStep
|
||||||
key={id}
|
key={id}
|
||||||
label={
|
label={description ?? t.toolCalls.useTool(name)}
|
||||||
description ?? (
|
|
||||||
<div>
|
|
||||||
Use "<span className="font-bold">{name}</span>" tool
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
icon={WrenchIcon}
|
icon={WrenchIcon}
|
||||||
></ChainOfThoughtStep>
|
></ChainOfThoughtStep>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ import {
|
|||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { useDeleteThread, useThreads } from "@/core/threads/hooks";
|
import { useDeleteThread, useThreads } from "@/core/threads/hooks";
|
||||||
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
||||||
|
|
||||||
export function RecentChatList() {
|
export function RecentChatList() {
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
||||||
@@ -52,7 +54,7 @@ export function RecentChatList() {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Recent chats</SidebarGroupLabel>
|
<SidebarGroupLabel>{t.sidebar.recentChats}</SidebarGroupLabel>
|
||||||
<SidebarGroupContent className="group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0">
|
<SidebarGroupContent className="group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0">
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<div className="flex w-full flex-col gap-1">
|
<div className="flex w-full flex-col gap-1">
|
||||||
@@ -78,7 +80,7 @@ export function RecentChatList() {
|
|||||||
className="bg-background/50 hover:bg-background"
|
className="bg-background/50 hover:bg-background"
|
||||||
>
|
>
|
||||||
<MoreHorizontal />
|
<MoreHorizontal />
|
||||||
<span className="sr-only">More</span>
|
<span className="sr-only">{t.common.more}</span>
|
||||||
</SidebarMenuAction>
|
</SidebarMenuAction>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
@@ -90,7 +92,7 @@ export function RecentChatList() {
|
|||||||
onSelect={() => handleDelete(thread.thread_id)}
|
onSelect={() => handleDelete(thread.thread_id)}
|
||||||
>
|
>
|
||||||
<Trash2 className="text-muted-foreground" />
|
<Trash2 className="text-muted-foreground" />
|
||||||
<span>Delete</span>
|
<span>{t.common.delete}</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
0
frontend/src/components/workspace/settings/index.ts
Normal file
0
frontend/src/components/workspace/settings/index.ts
Normal file
@@ -1,6 +1,10 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export function Welcome({ className }: { className?: string }) {
|
export function Welcome({ className }: { className?: string }) {
|
||||||
|
const { t } = useI18n();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -8,17 +12,13 @@ export function Welcome({ className }: { className?: string }) {
|
|||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="text-2xl font-bold">👋 Hello, again!</div>
|
<div className="text-2xl font-bold">{t.welcome.greeting}</div>
|
||||||
<div className="text-muted-foreground text-sm">
|
<div className="text-muted-foreground text-sm">
|
||||||
<p>
|
{t.welcome.description.includes("\n") ? (
|
||||||
Welcome to 🦌 DeerFlow, an open source super agent. With built-in and
|
<pre className="whitespace-pre">{t.welcome.description}</pre>
|
||||||
custom
|
) : (
|
||||||
</p>
|
<p>{t.welcome.description}</p>
|
||||||
<p>
|
)}
|
||||||
skills, DeerFlow helps you search on the web, analyze data, and
|
|
||||||
generate
|
|
||||||
</p>{" "}
|
|
||||||
<p>artifacts like slides, web pages and do almost anything.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from "@/components/ui/breadcrumb";
|
} from "@/components/ui/breadcrumb";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import { GithubIcon } from "./github-icon";
|
import { GithubIcon } from "./github-icon";
|
||||||
@@ -34,6 +35,7 @@ export function WorkspaceHeader({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"header">) {
|
}: React.ComponentProps<"header">) {
|
||||||
|
const { t } = useI18n();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const segments = useMemo(() => {
|
const segments = useMemo(() => {
|
||||||
const parts = pathname?.split("/") || [];
|
const parts = pathname?.split("/") || [];
|
||||||
@@ -56,7 +58,7 @@ export function WorkspaceHeader({
|
|||||||
<BreadcrumbItem className="hidden md:block">
|
<BreadcrumbItem className="hidden md:block">
|
||||||
<BreadcrumbLink asChild>
|
<BreadcrumbLink asChild>
|
||||||
<Link href={`/${segments[0]}`}>
|
<Link href={`/${segments[0]}`}>
|
||||||
{nameOfSegment(segments[0])}
|
{nameOfSegment(segments[0], t)}
|
||||||
</Link>
|
</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
@@ -68,12 +70,12 @@ export function WorkspaceHeader({
|
|||||||
{segments.length >= 2 ? (
|
{segments.length >= 2 ? (
|
||||||
<BreadcrumbLink asChild>
|
<BreadcrumbLink asChild>
|
||||||
<Link href={`/${segments[0]}/${segments[1]}`}>
|
<Link href={`/${segments[0]}/${segments[1]}`}>
|
||||||
{nameOfSegment(segments[1])}
|
{nameOfSegment(segments[1], t)}
|
||||||
</Link>
|
</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
) : (
|
) : (
|
||||||
<BreadcrumbPage>
|
<BreadcrumbPage>
|
||||||
{nameOfSegment(segments[1])}
|
{nameOfSegment(segments[1], t)}
|
||||||
</BreadcrumbPage>
|
</BreadcrumbPage>
|
||||||
)}
|
)}
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
@@ -89,7 +91,7 @@ export function WorkspaceHeader({
|
|||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div className="pr-4">
|
<div className="pr-4">
|
||||||
<Tooltip content="DeerFlow on Github">
|
<Tooltip content={t.workspace.githubTooltip}>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/bytedance/deer-flow"
|
href="https://github.com/bytedance/deer-flow"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -122,7 +124,12 @@ export function WorkspaceBody({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function nameOfSegment(segment: string | undefined) {
|
function nameOfSegment(
|
||||||
if (!segment) return "Home";
|
segment: string | undefined,
|
||||||
|
t: ReturnType<typeof useI18n>["t"],
|
||||||
|
) {
|
||||||
|
if (!segment) return t.common.home;
|
||||||
|
if (segment === "workspace") return t.breadcrumb.workspace;
|
||||||
|
if (segment === "chats") return t.breadcrumb.chats;
|
||||||
return segment[0]?.toUpperCase() + segment.slice(1);
|
return segment[0]?.toUpperCase() + segment.slice(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import {
|
|||||||
SidebarTrigger,
|
SidebarTrigger,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export function WorkspaceHeader({ className }: { className?: string }) {
|
export function WorkspaceHeader({ className }: { className?: string }) {
|
||||||
|
const { t } = useI18n();
|
||||||
const { state } = useSidebar();
|
const { state } = useSidebar();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
return (
|
return (
|
||||||
@@ -48,7 +50,7 @@ export function WorkspaceHeader({ className }: { className?: string }) {
|
|||||||
>
|
>
|
||||||
<Link className="text-muted-foreground" href="/workspace/chats/new">
|
<Link className="text-muted-foreground" href="/workspace/chats/new">
|
||||||
<MessageSquarePlus size={16} />
|
<MessageSquarePlus size={16} />
|
||||||
<span>New chat</span>
|
<span>{t.sidebar.newChat}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import {
|
|||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
|
|
||||||
export function WorkspaceNavMenu() {
|
export function WorkspaceNavMenu() {
|
||||||
|
const { t } = useI18n();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
return (
|
return (
|
||||||
<SidebarGroup className="pt-1">
|
<SidebarGroup className="pt-1">
|
||||||
@@ -20,7 +22,7 @@ export function WorkspaceNavMenu() {
|
|||||||
<SidebarMenuButton isActive={pathname === "/workspace/chats"} asChild>
|
<SidebarMenuButton isActive={pathname === "/workspace/chats"} asChild>
|
||||||
<Link className="text-muted-foreground" href="/workspace/chats">
|
<Link className="text-muted-foreground" href="/workspace/chats">
|
||||||
<MessagesSquare />
|
<MessagesSquare />
|
||||||
<span>Chats</span>
|
<span>{t.sidebar.chats}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
|
import { SettingsIcon } from "lucide-react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
SidebarFooter,
|
SidebarFooter,
|
||||||
SidebarRail,
|
SidebarRail,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuItem,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupContent,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
|
|
||||||
import { RecentChatList } from "./recent-chat-list";
|
import { RecentChatList } from "./recent-chat-list";
|
||||||
import { WorkspaceHeader } from "./workspace-header";
|
import { WorkspaceHeader } from "./workspace-header";
|
||||||
@@ -13,6 +21,7 @@ import { WorkspaceNavMenu } from "./workspace-nav-menu";
|
|||||||
export function WorkspaceSidebar({
|
export function WorkspaceSidebar({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Sidebar>) {
|
}: React.ComponentProps<typeof Sidebar>) {
|
||||||
|
const { t } = useI18n();
|
||||||
return (
|
return (
|
||||||
<Sidebar variant="sidebar" collapsible="icon" {...props}>
|
<Sidebar variant="sidebar" collapsible="icon" {...props}>
|
||||||
<SidebarHeader className="py-0">
|
<SidebarHeader className="py-0">
|
||||||
@@ -22,7 +31,22 @@ export function WorkspaceSidebar({
|
|||||||
<WorkspaceNavMenu />
|
<WorkspaceNavMenu />
|
||||||
<RecentChatList />
|
<RecentChatList />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter></SidebarFooter>
|
<SidebarFooter>
|
||||||
|
<SidebarGroup className="px-0">
|
||||||
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton asChild>
|
||||||
|
<div className="text-muted-foreground cursor-pointer">
|
||||||
|
<SettingsIcon size={16} />
|
||||||
|
<span>{t.common.settings}</span>
|
||||||
|
</div>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
</SidebarFooter>
|
||||||
<SidebarRail />
|
<SidebarRail />
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
|
|||||||
57
frontend/src/core/i18n/hooks.ts
Normal file
57
frontend/src/core/i18n/hooks.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { enUS } from "./locales/en-US";
|
||||||
|
import { zhCN } from "./locales/zh-CN";
|
||||||
|
|
||||||
|
import { detectLocale, type Locale, type Translations } from "./index";
|
||||||
|
|
||||||
|
const translations: Record<Locale, Translations> = {
|
||||||
|
"en-US": enUS,
|
||||||
|
"zh-CN": zhCN,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useI18n() {
|
||||||
|
const [locale, setLocale] = useState<Locale>(() => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return "en-US";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get from localStorage first
|
||||||
|
const saved = localStorage.getItem("locale") as Locale | null;
|
||||||
|
if (saved && (saved === "en-US" || saved === "zh-CN")) {
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise detect from browser
|
||||||
|
return detectLocale();
|
||||||
|
});
|
||||||
|
|
||||||
|
const t = translations[locale];
|
||||||
|
|
||||||
|
const changeLocale = (newLocale: Locale) => {
|
||||||
|
setLocale(newLocale);
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
localStorage.setItem("locale", newLocale);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize locale on mount
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
const saved = localStorage.getItem("locale") as Locale | null;
|
||||||
|
if (!saved) {
|
||||||
|
const detected = detectLocale();
|
||||||
|
setLocale(detected);
|
||||||
|
localStorage.setItem("locale", detected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
t,
|
||||||
|
changeLocale,
|
||||||
|
};
|
||||||
|
}
|
||||||
23
frontend/src/core/i18n/index.ts
Normal file
23
frontend/src/core/i18n/index.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export { enUS } from "./locales/en-US";
|
||||||
|
export { zhCN } from "./locales/zh-CN";
|
||||||
|
export type { Translations } from "./locales/types";
|
||||||
|
|
||||||
|
export type Locale = "en-US" | "zh-CN";
|
||||||
|
|
||||||
|
// Helper function to detect browser locale
|
||||||
|
export function detectLocale(): Locale {
|
||||||
|
// if (typeof window === "undefined") {
|
||||||
|
// return "en-US";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const browserLang =
|
||||||
|
// navigator.language ||
|
||||||
|
// (navigator as unknown as { userLanguage: string }).userLanguage;
|
||||||
|
|
||||||
|
// // Check if browser language is Chinese (zh, zh-CN, zh-TW, etc.)
|
||||||
|
// if (browserLang.toLowerCase().startsWith("zh")) {
|
||||||
|
// return "zh-CN";
|
||||||
|
// }
|
||||||
|
|
||||||
|
return "en-US";
|
||||||
|
}
|
||||||
86
frontend/src/core/i18n/locales/en-US.ts
Normal file
86
frontend/src/core/i18n/locales/en-US.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import type { Translations } from "./types";
|
||||||
|
|
||||||
|
export const enUS: Translations = {
|
||||||
|
// Common
|
||||||
|
common: {
|
||||||
|
home: "Home",
|
||||||
|
settings: "Settings",
|
||||||
|
delete: "Delete",
|
||||||
|
openInNewWindow: "Open in new window",
|
||||||
|
close: "Close",
|
||||||
|
more: "More",
|
||||||
|
search: "Search",
|
||||||
|
download: "Download",
|
||||||
|
thinking: "Thinking",
|
||||||
|
artifacts: "Artifacts",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Welcome
|
||||||
|
welcome: {
|
||||||
|
greeting: "👋 Hello, again!",
|
||||||
|
description:
|
||||||
|
"Welcome to 🦌 DeerFlow, an open source super agent. With built-in and custom skills, DeerFlow helps you search on the web, analyze data, and generate artifacts like slides, web pages and do almost anything.",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Clipboard
|
||||||
|
clipboard: {
|
||||||
|
copyToClipboard: "Copy to clipboard",
|
||||||
|
copiedToClipboard: "Copied to clipboard",
|
||||||
|
failedToCopyToClipboard: "Failed to copy to clipboard",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Input Box
|
||||||
|
inputBox: {
|
||||||
|
placeholder: "How can I assist you today?",
|
||||||
|
thinkingEnabled: "Thinking is enabled",
|
||||||
|
thinkingDisabled: "Thinking is disabled",
|
||||||
|
clickToDisableThinking: "Click to disable thinking",
|
||||||
|
clickToEnableThinking: "Click to enable thinking",
|
||||||
|
searchModels: "Search models...",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sidebar
|
||||||
|
sidebar: {
|
||||||
|
newChat: "New chat",
|
||||||
|
chats: "Chats",
|
||||||
|
recentChats: "Recent chats",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Breadcrumb
|
||||||
|
breadcrumb: {
|
||||||
|
workspace: "Workspace",
|
||||||
|
chats: "Chats",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Workspace
|
||||||
|
workspace: {
|
||||||
|
githubTooltip: "DeerFlow on Github",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Conversation
|
||||||
|
conversation: {
|
||||||
|
noMessages: "No messages yet",
|
||||||
|
startConversation: "Start a conversation to see messages here",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Chats
|
||||||
|
chats: {
|
||||||
|
searchChats: "Search chats",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tool calls
|
||||||
|
toolCalls: {
|
||||||
|
moreSteps: (count: number) => `${count} more step${count === 1 ? "" : "s"}`,
|
||||||
|
lessSteps: "Less steps",
|
||||||
|
executeCommand: "Execute command",
|
||||||
|
presentFiles: "Present files",
|
||||||
|
needYourHelp: "Need your help",
|
||||||
|
useTool: (toolName: string) => `Use "${toolName}" tool`,
|
||||||
|
searchForRelatedInfo: "Search for related information",
|
||||||
|
searchOnWebFor: (query: string) => `Search on the web for "${query}"`,
|
||||||
|
viewWebPage: "View web page",
|
||||||
|
listFolder: "List folder",
|
||||||
|
readFile: "Read file",
|
||||||
|
writeFile: "Write file",
|
||||||
|
},
|
||||||
|
};
|
||||||
3
frontend/src/core/i18n/locales/index.ts
Normal file
3
frontend/src/core/i18n/locales/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { enUS } from "./en-US";
|
||||||
|
export { zhCN } from "./zh-CN";
|
||||||
|
export type { Translations } from "./types";
|
||||||
83
frontend/src/core/i18n/locales/types.ts
Normal file
83
frontend/src/core/i18n/locales/types.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
export interface Translations {
|
||||||
|
// Common
|
||||||
|
common: {
|
||||||
|
home: string;
|
||||||
|
settings: string;
|
||||||
|
delete: string;
|
||||||
|
openInNewWindow: string;
|
||||||
|
close: string;
|
||||||
|
more: string;
|
||||||
|
search: string;
|
||||||
|
download: string;
|
||||||
|
thinking: string;
|
||||||
|
artifacts: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Welcome
|
||||||
|
welcome: {
|
||||||
|
greeting: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clipboard
|
||||||
|
clipboard: {
|
||||||
|
copyToClipboard: string;
|
||||||
|
copiedToClipboard: string;
|
||||||
|
failedToCopyToClipboard: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Input Box
|
||||||
|
inputBox: {
|
||||||
|
placeholder: string;
|
||||||
|
thinkingEnabled: string;
|
||||||
|
thinkingDisabled: string;
|
||||||
|
clickToDisableThinking: string;
|
||||||
|
clickToEnableThinking: string;
|
||||||
|
searchModels: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sidebar
|
||||||
|
sidebar: {
|
||||||
|
recentChats: string;
|
||||||
|
newChat: string;
|
||||||
|
chats: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Breadcrumb
|
||||||
|
breadcrumb: {
|
||||||
|
workspace: string;
|
||||||
|
chats: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Workspace
|
||||||
|
workspace: {
|
||||||
|
githubTooltip: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Conversation
|
||||||
|
conversation: {
|
||||||
|
noMessages: string;
|
||||||
|
startConversation: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chats
|
||||||
|
chats: {
|
||||||
|
searchChats: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tool calls
|
||||||
|
toolCalls: {
|
||||||
|
moreSteps: (count: number) => string;
|
||||||
|
lessSteps: string;
|
||||||
|
executeCommand: string;
|
||||||
|
presentFiles: string;
|
||||||
|
needYourHelp: string;
|
||||||
|
useTool: (toolName: string) => string;
|
||||||
|
searchForRelatedInfo: string;
|
||||||
|
searchOnWebFor: (query: string) => string;
|
||||||
|
viewWebPage: string;
|
||||||
|
listFolder: string;
|
||||||
|
readFile: string;
|
||||||
|
writeFile: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
86
frontend/src/core/i18n/locales/zh-CN.ts
Normal file
86
frontend/src/core/i18n/locales/zh-CN.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import type { Translations } from "./types";
|
||||||
|
|
||||||
|
export const zhCN: Translations = {
|
||||||
|
// Common
|
||||||
|
common: {
|
||||||
|
home: "首页",
|
||||||
|
settings: "设置",
|
||||||
|
delete: "删除",
|
||||||
|
openInNewWindow: "在新窗口打开",
|
||||||
|
close: "关闭",
|
||||||
|
more: "更多",
|
||||||
|
search: "搜索",
|
||||||
|
download: "下载",
|
||||||
|
thinking: "思考",
|
||||||
|
artifacts: "文件",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Welcome
|
||||||
|
welcome: {
|
||||||
|
greeting: "👋 你好,欢迎回来!",
|
||||||
|
description:
|
||||||
|
"欢迎使用 🦌 DeerFlow,一个完全开源的超级智能体。通过内置和\n自定义的 Skills,DeerFlow 可以帮你搜索网络、分析数据,\n还能为你生成幻灯片、网页等作品,几乎可以做任何事情。",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Clipboard
|
||||||
|
clipboard: {
|
||||||
|
copyToClipboard: "复制到剪贴板",
|
||||||
|
copiedToClipboard: "已复制到剪贴板",
|
||||||
|
failedToCopyToClipboard: "复制到剪贴板失败",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Input Box
|
||||||
|
inputBox: {
|
||||||
|
placeholder: "今天我能为你做些什么?",
|
||||||
|
thinkingEnabled: "思考功能已启用",
|
||||||
|
thinkingDisabled: "思考功能已禁用",
|
||||||
|
clickToDisableThinking: "点击禁用思考功能",
|
||||||
|
clickToEnableThinking: "点击启用思考功能",
|
||||||
|
searchModels: "搜索模型...",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sidebar
|
||||||
|
sidebar: {
|
||||||
|
newChat: "新对话",
|
||||||
|
chats: "对话",
|
||||||
|
recentChats: "最近的聊天",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Breadcrumb
|
||||||
|
breadcrumb: {
|
||||||
|
workspace: "工作区",
|
||||||
|
chats: "对话",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Workspace
|
||||||
|
workspace: {
|
||||||
|
githubTooltip: "DeerFlow 在 Github",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Conversation
|
||||||
|
conversation: {
|
||||||
|
noMessages: "还没有消息",
|
||||||
|
startConversation: "开始新的对话以查看消息",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Chats
|
||||||
|
chats: {
|
||||||
|
searchChats: "搜索对话",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tool calls
|
||||||
|
toolCalls: {
|
||||||
|
moreSteps: (count: number) => `查看其他 ${count} 个步骤`,
|
||||||
|
lessSteps: "隐藏步骤",
|
||||||
|
executeCommand: "执行命令",
|
||||||
|
presentFiles: "展示文件",
|
||||||
|
needYourHelp: "需要你的协助",
|
||||||
|
useTool: (toolName: string) => `使用 “${toolName}” 工具`,
|
||||||
|
searchForRelatedInfo: "搜索相关信息",
|
||||||
|
searchOnWebFor: (query: string) => `在网络上搜索 “${query}”`,
|
||||||
|
viewWebPage: "查看网页",
|
||||||
|
listFolder: "列出文件夹",
|
||||||
|
readFile: "读取文件",
|
||||||
|
writeFile: "写入文件",
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user