- 本次执行的任务说明
+ {tr("本次执行的任务说明")}
{brief}
diff --git a/example/parsar/src/SessionFiles.tsx b/example/parsar/src/SessionFiles.tsx
index af7da853b..fb0da6558 100644
--- a/example/parsar/src/SessionFiles.tsx
+++ b/example/parsar/src/SessionFiles.tsx
@@ -1,3 +1,4 @@
+import { tr } from "./lib/i18n";
import { useRef } from "react";
import { useInfiniteQuery, useMutation } from "@tanstack/react-query";
import { Upload, Download } from "lucide-react";
@@ -11,11 +12,9 @@ import {
DialogTitle,
} from "./components/ui/dialog";
import { ErrorNotice, Help } from "./components/shared";
-
const maxUploadBytes = 5 * 1024 * 1024;
-
async function upload(environmentId: string, file: File) {
- if (file.size > maxUploadBytes) throw new Error("文件不能超过 5 MiB。");
+ if (file.size > maxUploadBytes) throw new Error(tr("文件不能超过 5 MiB。"));
let name = "";
const encoder = new TextEncoder();
for (const character of file.name.replace(
@@ -30,7 +29,7 @@ async function upload(environmentId: string, file: File) {
const data = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result).split(",")[1] || "");
- reader.onerror = () => reject(new Error("无法读取文件,请重新选择。"));
+ reader.onerror = () => reject(new Error(tr("无法读取文件,请重新选择。")));
reader.readAsDataURL(file);
});
try {
@@ -40,14 +39,16 @@ async function upload(environmentId: string, file: File) {
data,
});
} catch (error) {
- const reason = error instanceof Error ? error.message : "请求失败";
+ const reason = error instanceof Error ? error.message : tr("请求失败");
throw new Error(
- `${reason} 上传未确认,文件可能已写入 ${path.replace(/^\/workspace\//, "./")}。请先检查该路径再重新上传。`,
+ tr(
+ "{{value0}} 上传未确认,文件可能已写入 {{value1}}。请先检查该路径再重新上传。",
+ { value0: reason, value1: path.replace(/^\/workspace\//, "./") },
+ ),
);
}
return path;
}
-
export function SessionFiles({
sessionId,
environmentId,
@@ -77,7 +78,7 @@ export function SessionFiles({
);
const value = await response.json();
if (!response.ok)
- throw new Error(value.error?.message || "无法读取产物。");
+ throw new Error(value.error?.message || tr("无法读取产物。"));
return value;
},
getNextPageParam: (page) =>
@@ -97,7 +98,7 @@ export function SessionFiles({
);
if (!response.ok) {
const value = await response.json();
- throw new Error(value.error?.message || "下载失败,请重试。");
+ throw new Error(value.error?.message || tr("下载失败,请重试。"));
}
const url = URL.createObjectURL(await response.blob());
const link = document.createElement("a");
@@ -117,7 +118,7 @@ export function SessionFiles({
>
- 会话文件
+ {tr("会话文件")}
- 每次上传一个文件,最大 5
- MiB。文件进入当前工作区,路径会插入消息草稿;发送后 Agent
- 才会处理。执行期间不能上传。让 Agent 将结果保存到当前工作目录的
- outputs/,执行完成后可在这里下载。
+ {tr(
+ "每次上传一个文件,最大 5 MiB。文件进入当前工作区,路径会插入消息草稿;发送后 Agent 才会处理。执行期间不能上传。让 Agent 将结果保存到当前工作目录的 outputs/,执行完成后可在这里下载。",
+ )}
{!writable && (
- 会话空闲且运行环境就绪后可以上传。
+ {tr("会话空闲且运行环境就绪后可以上传。")}
)}
-
生成的文件
+
{tr("生成的文件")}
{artifacts.isPending ? (
-
正在读取…
+
{tr("正在读取\u2026")}
) : !files.length && !artifacts.error ? (
-
还没有生成的文件。
+
+ {tr("还没有生成的文件。")}
+
) : null}
{files.map((file) => (
download.mutate(file)}
- aria-label={`下载 ${file.path.split("/").at(-1)}`}
+ aria-label={tr("下载 {{value0}}", {
+ value0: file.path.split("/").at(-1),
+ })}
>
@@ -198,7 +202,7 @@ export function SessionFiles({
disabled={artifacts.isFetchingNextPage}
onClick={() => void artifacts.fetchNextPage()}
>
- 加载更多
+ {tr("加载更多")}
)}
diff --git a/example/parsar/src/SessionPage.tsx b/example/parsar/src/SessionPage.tsx
index 90a3cf38b..674e273e1 100644
--- a/example/parsar/src/SessionPage.tsx
+++ b/example/parsar/src/SessionPage.tsx
@@ -1,3 +1,5 @@
+import { SingleSlot } from "./components/plugin/SlotRenderer";
+import { tr } from "./lib/i18n";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { product, type SessionRecord } from "./lib/product";
import { SessionDetail } from "./SessionDetail";
@@ -17,15 +19,22 @@ export function SessionPage({ id }: { id: string }) {
void cache.invalidateQueries({ queryKey: ["sessions"] });
},
});
+ if (query.data?.deleted_at) return ;
if (query.data?.core_session_id)
return (
-
+ }
/>
);
return (
@@ -38,14 +47,16 @@ export function SessionPage({ id }: { id: string }) {
: `#/agents/${query.data?.agent_id || ""}`
}
>
- {query.data?.task_id ? "返回任务" : "返回 Agent"}
+ {query.data?.task_id ? tr("返回任务") : tr("返回 Agent")}
-
+
{query.data && (
)}
diff --git a/example/parsar/src/Settings.tsx b/example/parsar/src/Settings.tsx
new file mode 100644
index 000000000..5ab0f4b06
--- /dev/null
+++ b/example/parsar/src/Settings.tsx
@@ -0,0 +1,73 @@
+import { tr, setLanguage, currentLanguage } from "./lib/i18n";
+import { useState } from "react";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { product } from "./lib/product";
+import { PageHeader, ErrorNotice, Field } from "./components/shared";
+import { Input } from "./components/ui/input";
+import { Button } from "./components/ui/button";
+export interface WorkspaceSettings {
+ name: string;
+ revision: number;
+}
+export function Settings() {
+ const query = useQuery({
+ queryKey: ["settings"],
+ queryFn: () => product
("settings"),
+ });
+ return (
+ <>
+
+
+ {tr("界面语言")}
+
+
+
+
+ {query.data && (
+
+ )}
+ >
+ );
+}
+function SettingsForm({ value }: { value: WorkspaceSettings }) {
+ const [name, setName] = useState(value.name),
+ cache = useQueryClient();
+ const save = useMutation({
+ mutationFn: () => product("settings", "PUT", { ...value, name }),
+ onSuccess: () => cache.invalidateQueries({ queryKey: ["settings"] }),
+ });
+ return (
+
+ );
+}
diff --git a/example/parsar/src/Skills.tsx b/example/parsar/src/Skills.tsx
index 029ba3af9..cc2dcd1d1 100644
--- a/example/parsar/src/Skills.tsx
+++ b/example/parsar/src/Skills.tsx
@@ -1,3 +1,7 @@
+import { product } from "./lib/product";
+import { Select, SelectOption } from "./components/ui/select";
+import { unzipSync, strFromU8 } from "fflate";
+import { tr } from "./lib/i18n";
import { useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { BookOpen, Plus, Upload, Trash2 } from "lucide-react";
@@ -26,6 +30,7 @@ export function Skills() {
const cache = useQueryClient();
const [selected, setSelected] = useState(null);
const [creating, setCreating] = useState(false);
+ const [directory, setDirectory] = useState(false);
const [search, setSearch] = useState("");
const upload = useMutation({
mutationFn: (file: File) =>
@@ -35,16 +40,20 @@ export function Skills() {
return (
<>
+
- Skills 是可复用的执行方法。上传 ZIP,或用 SKILL.md 创建。绑定到 Agent
- 后,托管任务会加载这些技能;纯文本环境不支持。
+ {tr(
+ "Skills 是可复用的执行方法。上传 ZIP,或用 SKILL.md 创建。绑定到 Agent 后,托管任务会加载这些技能;纯文本环境不支持。",
+ )}
-
+
setSearch(e.target.value)}
className="max-w-sm"
/>
-
-
+
+
{query.data
?.filter((skill) =>
`${skill.name} ${skill.description}`
@@ -84,27 +93,28 @@ export function Skills() {
setSelected(skill)}
- className="rounded-xl border border-line p-5 text-left transition-colors hover:bg-surface-subtle focus-visible:ring-2 focus-visible:ring-accent"
+ className="flex min-h-9 items-center gap-6 px-6 text-left transition-colors hover:bg-surface-subtle focus-visible:ring-2 focus-visible:ring-accent"
>
-
-
-
+
+
+
{skill.name}
v{skill.default_version}
-
+
{skill.description}
))}
{!query.isPending && !query.data?.length && (
-
+
)}
+ {directory &&
setDirectory(false)} />}
{creating && setCreating(false)} />}
{selected && (
setSelected(null)} />
@@ -150,7 +160,7 @@ function SkillCreate({ close }: { close: () => void }) {
className="max-h-[90dvh] overflow-y-auto"
>
- 创建 Skill
+ {tr("创建 Skill")}
- 保存
+ {tr("保存")}
@@ -203,6 +213,7 @@ function SkillCreate({ close }: { close: () => void }) {
}
function SkillDetail({ skill, close }: { skill: Skill; close: () => void }) {
const cache = useQueryClient();
+ const [preview, setPreview] = useState("");
const versions = useQuery({
queryKey: ["skill-versions", skill.id],
queryFn: ({ signal }) =>
@@ -263,7 +274,7 @@ function SkillDetail({ skill, close }: { skill: Skill; close: () => void }) {
{current.data.name}
{current.data.description}
- 版本
+ {tr("版本")}
void }) {
className="flex items-center justify-between rounded-lg border border-line p-3"
>
v{version.version}
+ setPreview(version.version)}
+ >
+ {tr("查看文件")}
+
{version.version === current.data.default_version ? (
- 默认版本
+ {tr("默认版本")}
) : (
change.mutate(version.version)}
>
- 设为默认
+ {tr("设为默认")}
)}
))}
+ {preview &&
}
- 新任务解析默认版本,已启动任务保留技能快照。删除会影响绑定此 Skill
- 的新任务,请先解除 Agent 中的绑定。
+ {tr(
+ "新任务解析默认版本,已启动任务保留技能快照。删除会影响绑定此 Skill 的新任务,请先解除 Agent 中的绑定。",
+ )}
{
- if (confirm("删除此 Skill 及全部版本?请先解除 Agent 中的绑定。"))
+ if (
+ confirm(
+ tr("删除此 Skill 及全部版本?请先解除 Agent 中的绑定。"),
+ )
+ )
remove.mutate();
}}
>
@@ -313,9 +336,9 @@ function SkillDetail({ skill, close }: { skill: Skill; close: () => void }) {