@teammy/server 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/main.js +43 -46
- package/dist/runtime/README.md +17 -0
- package/dist/web/assets/Dashboard-wKygMUMn.js +1 -0
- package/dist/web/assets/Files-C9kRlKdR.js +1 -0
- package/dist/web/assets/Markdown-DQn2KtR3.js +29 -0
- package/dist/web/assets/Sessions-HD72N7MF.js +1 -0
- package/dist/web/assets/{index-hjEc8Pme.css → index-BgzSGwum.css} +1 -1
- package/dist/web/assets/index-Db4kzLA8.js +66 -0
- package/dist/web/index.html +3 -3
- package/package.json +1 -1
- package/dist/web/assets/Dashboard-CAyrJ3RP.js +0 -1
- package/dist/web/assets/Files-B9FJqcJ4.js +0 -1
- package/dist/web/assets/Markdown-DPF_hcrA.js +0 -29
- package/dist/web/assets/Sessions-DAIRx5CI.js +0 -1
- package/dist/web/assets/index-CETlKRD0.js +0 -66
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
中心服务器上的 Teammy 服务,需要 Node.js 22.19 或更新版本。
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm install -g @teammy/server@0.0.
|
|
6
|
+
npm install -g @teammy/server@0.0.10
|
|
7
7
|
teammy-server --help
|
|
8
8
|
teammy-server --version
|
|
9
9
|
teammy-server --host 127.0.0.1 --port 4317
|
|
@@ -11,7 +11,7 @@ teammy-server --host 127.0.0.1 --port 4317
|
|
|
11
11
|
|
|
12
12
|
默认监听 `127.0.0.1:4317`;内网部署时可指定 `--host 0.0.0.0` 或服务器的内网地址。收到 SIGINT 或 SIGTERM 时正常关闭。
|
|
13
13
|
|
|
14
|
-
当前提供健康检查、协议能力发现、带认证的设备绑定和成员查询,以及工作区隔离的内容传输、幂等文件版本提交、历史清单和增量查询。支持规范化会话快照、历史版本读取、列表及中文/技术标识符正文搜索;服务地址同时提供 Web Wizard、ID
|
|
14
|
+
当前提供健康检查、协议能力发现、带认证的设备绑定和成员查询,以及工作区隔离的内容传输、幂等文件版本提交、历史清单和增量查询。支持规范化会话快照、历史版本读取、列表及中文/技术标识符正文搜索;服务地址同时提供 Web Wizard、ID 登录、文件只读浏览、成员管理与公共设置。提供自动维护的任务进展看板、目录浏览、只读会话检索和模型配置;任务整理需另起 Worker。0.0.10 移除手动分析与整理入口,看板自动刷新结果。
|
|
15
15
|
|
|
16
16
|
成员设备使用独立的 `@teammy/cli` 包。服务器启动入口已从原有的 `teammy server` 迁移到 `teammy-server`。
|
|
17
17
|
|
|
@@ -27,15 +27,15 @@ teammy-server --host 127.0.0.1 --port 4317
|
|
|
27
27
|
teammy-server ai-worker --data-dir /absolute/path/teammy-data
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
与 HTTP 服务使用同一数据目录。浏览器“全站设置 →
|
|
30
|
+
与 HTTP 服务使用同一数据目录。浏览器“全站设置 → 模型接入”维护私有配置,工作区文件发布后由 Worker 自动维护任务看板。执行并发为 1。0.0.9 的任务整理与目录分析需要 Linux Worker、Bubblewrap 和 ripgrep:整个 Pi 在独立沙箱内运行,原生文件工具和 Bash 只能看到本轮只读资料及临时目录,模型请求由父 Worker 代发。Docker 镜像包含系统工具,容器使用 compose.yaml 中的 Worker 隔离配置;非 Linux 宿主请在 Linux 容器或开发机运行 Worker。API、Web 与客户端仍可在 macOS 上运行。
|
|
31
31
|
|
|
32
|
-
当前源码支持工作区任务冷启动与自动增量维护:配置全站或项目默认模型后,成员首次非空发布及后续增删改稳定约 60 秒自动整理。仅重跑发生变化的成员,通过 query_tasks 查询已有任务,结合文件变化提交 upsert/archive,未提及卡片保持原样。目录清空时不调用模型,也不自动删除旧卡片。看板显示计划、进行中、待验收和已完成;needsConfirmation
|
|
32
|
+
当前源码支持工作区任务冷启动与自动增量维护:配置全站或项目默认模型后,成员首次非空发布及后续增删改稳定约 60 秒自动整理。仅重跑发生变化的成员,通过 query_tasks 查询已有任务,结合文件变化提交 upsert/archive,未提及卡片保持原样。目录清空时不调用模型,也不自动删除旧卡片。看板显示计划、进行中、待验收和已完成;needsConfirmation 标记需要确认的完整任务,支持查看来源、修订表达及确认,不改原文。看板自动刷新任务列表,不提供手动分析、整理或刷新按钮。设计见 [文件任务整理](../../docs/task-board-analysis.md)。
|
|
33
33
|
|
|
34
34
|
提示词、工具说明与工作流预算随包保存在 `dist/runtime/`。可将完整目录复制到服务器专用位置,通过 `TEAMMY_AI_RUNTIME_DIR=/absolute/path/runtime` 指定;容器可只读挂载。API 和 Worker 使用同一目录配置。新建作业加载并固定资源,已有作业及重试不受编辑影响。模型密钥仍由网页接入点设置维护,不写入运行资源。详见资源目录 README。
|
|
35
35
|
|
|
36
|
-
本版任务提示词为 v10
|
|
36
|
+
本版任务提示词为 v10:关注后续验收与遗留事项,简洁表达进展,并区分明确工作与讨论中的可能性。任务失败可自动重试三次;资料变化会取消过期任务,待新版本稳定后重新整理。提示词更新不自动重跑未发生变化的历史项目,下次有效文件更新时使用新提示词。
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
安装包随附全部服务端运行依赖,包含 Pi 1.4.0 及修复后的 undici 8.12.2,普通 npm 安装即可使用,无需配置 overrides。服务端包包含 Web 静态资源;客户端不安装 Pi 或 Web 依赖。Bubblewrap、ripgrep 等系统工具由 Linux Worker 环境提供。
|
|
39
39
|
|
|
40
40
|
备份必须包含 SQLite、blobs 和 `ai-secret.key`。API 与 Worker 停止后备份整个目录。
|
|
41
41
|
|
package/dist/main.js
CHANGED
|
@@ -3,6 +3,14 @@ import { createHash as createHash4 } from "node:crypto";
|
|
|
3
3
|
|
|
4
4
|
// apps/server/files.ts
|
|
5
5
|
import { createHash as createHash3 } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
// packages/protocol/browse-order.ts
|
|
8
|
+
var names = new Intl.Collator("zh-CN", { numeric: true, sensitivity: "base" });
|
|
9
|
+
function compareNames(a, b) {
|
|
10
|
+
return names.compare(a, b) || (a < b ? -1 : a > b ? 1 : 0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// apps/server/files.ts
|
|
6
14
|
import { join as join2 } from "node:path";
|
|
7
15
|
|
|
8
16
|
// packages/storage/local-blob-store.ts
|
|
@@ -966,7 +974,7 @@ var FileRepository = class {
|
|
|
966
974
|
if (target > head)
|
|
967
975
|
throw new HubError(400, "INVALID_REVISION", "Requested revision has not been published.");
|
|
968
976
|
const prefix = path ? path + "/" : "";
|
|
969
|
-
|
|
977
|
+
let rows = this.hub.db.prepare(
|
|
970
978
|
`
|
|
971
979
|
WITH latest AS (
|
|
972
980
|
SELECT path, MAX(revision) revision FROM file_events
|
|
@@ -979,7 +987,7 @@ var FileRepository = class {
|
|
|
979
987
|
SELECT *,CASE WHEN instr(tail,'/')=0 THEN tail ELSE substr(tail,1,instr(tail,'/')-1) END name FROM live
|
|
980
988
|
)
|
|
981
989
|
SELECT name,MAX(instr(tail,'/')>0) directory,COUNT(*) count,MIN(hash) hash,MIN(size) size,MAX(revision) revision
|
|
982
|
-
FROM children
|
|
990
|
+
FROM children GROUP BY name
|
|
983
991
|
`
|
|
984
992
|
).all(
|
|
985
993
|
identity.workspaceId,
|
|
@@ -989,10 +997,15 @@ var FileRepository = class {
|
|
|
989
997
|
identity.workspaceId,
|
|
990
998
|
owner,
|
|
991
999
|
prefix,
|
|
992
|
-
prefix
|
|
993
|
-
after,
|
|
994
|
-
limit + 1
|
|
1000
|
+
prefix
|
|
995
1001
|
);
|
|
1002
|
+
rows.sort(
|
|
1003
|
+
(a, b) => Number(b.directory) - Number(a.directory) || compareNames(String(a.name), String(b.name))
|
|
1004
|
+
);
|
|
1005
|
+
const offset = after ? rows.findIndex((row) => row.name === after) + 1 : 0;
|
|
1006
|
+
if (after && !offset)
|
|
1007
|
+
throw new HubError(400, "INVALID_CURSOR", "Directory cursor is not in this snapshot.");
|
|
1008
|
+
rows = rows.slice(offset, offset + limit + 1);
|
|
996
1009
|
return {
|
|
997
1010
|
revision: target,
|
|
998
1011
|
path,
|
|
@@ -1600,11 +1613,6 @@ var TaskBoardSchema = z5.object({
|
|
|
1600
1613
|
),
|
|
1601
1614
|
modelAvailable: z5.boolean()
|
|
1602
1615
|
});
|
|
1603
|
-
var TaskBootstrapRequestSchema = z5.object({ operationId: z5.uuid(), owner: NameSchema }).strict();
|
|
1604
|
-
var TaskBootstrapResponseSchema = z5.object({
|
|
1605
|
-
jobId: z5.uuid().nullable(),
|
|
1606
|
-
empty: z5.boolean()
|
|
1607
|
-
});
|
|
1608
1616
|
|
|
1609
1617
|
// packages/protocol/ai.ts
|
|
1610
1618
|
var AiProfileSchema = z6.object({
|
|
@@ -3804,18 +3812,6 @@ async function bootstrapTasks(job, reader, key, signal, progress, runtime) {
|
|
|
3804
3812
|
}
|
|
3805
3813
|
|
|
3806
3814
|
// apps/server/ai/bootstrap.ts
|
|
3807
|
-
function requestTaskBootstrap(hub, session, owner, operationId) {
|
|
3808
|
-
requireAnalysisAccess(hub, session, owner);
|
|
3809
|
-
if (!new FileRepository(hub).manifest(session, owner, void 0, "", 1).files.length && !hub.db.prepare("SELECT 1 FROM task_board_members WHERE workspace_id=? AND owner=?").get(session.workspaceId, owner))
|
|
3810
|
-
return { jobId: null, empty: true };
|
|
3811
|
-
const { job } = new AiJobs(hub).create(session, {
|
|
3812
|
-
operationId,
|
|
3813
|
-
workflow: "task-bootstrap",
|
|
3814
|
-
question: "",
|
|
3815
|
-
selection: { owner, kind: "directory", path: "" }
|
|
3816
|
-
});
|
|
3817
|
-
return { jobId: job.id, empty: false };
|
|
3818
|
-
}
|
|
3819
3815
|
function scheduleTaskBootstraps(hub, at = Date.now()) {
|
|
3820
3816
|
const jobs = new AiJobs(hub);
|
|
3821
3817
|
jobs.cancelStaleTaskJobs();
|
|
@@ -4041,10 +4037,6 @@ function registerAiJobRoutes(server, hub) {
|
|
|
4041
4037
|
}).parse(req.query);
|
|
4042
4038
|
return jobs.list(session.workspaceId, query.after);
|
|
4043
4039
|
});
|
|
4044
|
-
server.post("/ai/jobs", async (req) => {
|
|
4045
|
-
webMutation(req);
|
|
4046
|
-
return jobs.create(authorizeWeb(hub, req), AiJobCreateSchema.parse(req.body));
|
|
4047
|
-
});
|
|
4048
4040
|
server.get(
|
|
4049
4041
|
"/ai/jobs/:id",
|
|
4050
4042
|
async (req) => jobs.get(authorizeWeb(hub, req).workspaceId, id(req.params))
|
|
@@ -4057,14 +4049,6 @@ function registerAiJobRoutes(server, hub) {
|
|
|
4057
4049
|
webMutation(req);
|
|
4058
4050
|
return jobs.cancel(authorizeWeb(hub, req), id(req.params));
|
|
4059
4051
|
});
|
|
4060
|
-
server.post("/ai/jobs/:id/retry", async (req) => {
|
|
4061
|
-
webMutation(req);
|
|
4062
|
-
return jobs.retry(
|
|
4063
|
-
authorizeWeb(hub, req),
|
|
4064
|
-
id(req.params),
|
|
4065
|
-
z13.object({ operationId: z13.uuid() }).strict().parse(req.body).operationId
|
|
4066
|
-
);
|
|
4067
|
-
});
|
|
4068
4052
|
server.get("/ai/jobs/:id/source", async (req) => {
|
|
4069
4053
|
const session = authorizeWeb(hub, req), jobId = id(req.params);
|
|
4070
4054
|
jobs.get(session.workspaceId, jobId);
|
|
@@ -4085,11 +4069,6 @@ function registerTaskRoutes(server, hub) {
|
|
|
4085
4069
|
const store = new TaskStore(hub);
|
|
4086
4070
|
const id = (params) => z14.object({ id: z14.uuid() }).parse(params).id;
|
|
4087
4071
|
server.get("/tasks", async (req) => store.board(authorizeWeb(hub, req)));
|
|
4088
|
-
server.post("/tasks/bootstrap", async (req) => {
|
|
4089
|
-
webMutation(req);
|
|
4090
|
-
const session = authorizeWeb(hub, req), input = TaskBootstrapRequestSchema.parse(req.body);
|
|
4091
|
-
return requestTaskBootstrap(hub, session, input.owner, input.operationId);
|
|
4092
|
-
});
|
|
4093
4072
|
server.patch("/tasks/:id/correction", async (req) => {
|
|
4094
4073
|
webMutation(req);
|
|
4095
4074
|
return store.correct(
|
|
@@ -4730,11 +4709,26 @@ var SessionRepository = class {
|
|
|
4730
4709
|
).get(identity.workspaceId)
|
|
4731
4710
|
);
|
|
4732
4711
|
}
|
|
4733
|
-
list(identity, after, limit, owner) {
|
|
4734
|
-
const
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4712
|
+
list(identity, after, limit, owner, order = "created") {
|
|
4713
|
+
const byTitle = order === "title";
|
|
4714
|
+
let rows = this.hub.db.prepare(
|
|
4715
|
+
`SELECT s.id, s.title, s.native_id, r.info_json FROM sessions s JOIN session_revisions r USING(workspace_id, owner, session_key, revision)
|
|
4716
|
+
WHERE s.workspace_id=? ${byTitle ? "" : "AND s.id>?"} ${owner ? "AND s.owner=?" : ""} ${byTitle ? "" : "ORDER BY s.id LIMIT ?"}`
|
|
4717
|
+
).all(
|
|
4718
|
+
identity.workspaceId,
|
|
4719
|
+
...byTitle ? [] : [after],
|
|
4720
|
+
...owner ? [owner] : [],
|
|
4721
|
+
...byTitle ? [] : [limit + 1]
|
|
4722
|
+
);
|
|
4723
|
+
if (byTitle) {
|
|
4724
|
+
rows.sort(
|
|
4725
|
+
(a, b) => compareNames(String(a.title || a.native_id), String(b.title || b.native_id)) || Number(a.id) - Number(b.id)
|
|
4726
|
+
);
|
|
4727
|
+
const offset = after ? rows.findIndex((row) => row.id === after) + 1 : 0;
|
|
4728
|
+
if (after && !offset)
|
|
4729
|
+
throw new HubError(400, "INVALID_CURSOR", "Session cursor is not in this scope.");
|
|
4730
|
+
rows = rows.slice(offset, offset + limit + 1);
|
|
4731
|
+
}
|
|
4738
4732
|
return {
|
|
4739
4733
|
sessions: rows.slice(0, limit).map((row) => SessionInfoSchema.parse(JSON.parse(String(row.info_json)))),
|
|
4740
4734
|
nextCursor: rows.length > limit ? Number(rows[limit - 1].id) : null
|
|
@@ -4800,8 +4794,11 @@ function registerSessionRoutes(server, store) {
|
|
|
4800
4794
|
)
|
|
4801
4795
|
);
|
|
4802
4796
|
server.get("/sessions", async (request) => {
|
|
4803
|
-
const q = pagination.extend({
|
|
4804
|
-
|
|
4797
|
+
const q = pagination.extend({
|
|
4798
|
+
owner: NameSchema.optional(),
|
|
4799
|
+
order: z20.enum(["created", "title"]).default("created")
|
|
4800
|
+
}).parse(request.query);
|
|
4801
|
+
return sessions.list(authorizeRead(store, request), q.after, q.limit, q.owner, q.order);
|
|
4805
4802
|
});
|
|
4806
4803
|
server.get("/sessions/stats", async (request) => sessions.stats(authorizeRead(store, request)));
|
|
4807
4804
|
server.get("/sessions/search", async (request) => {
|
package/dist/runtime/README.md
CHANGED
|
@@ -15,3 +15,20 @@
|
|
|
15
15
|
任务整理默认 256 轮/30 分钟;目录分析与单文件摘要配置为 128 轮/20 分钟,单文件实际只有一次模型请求。各工作流单次请求 5 分钟,不限制工具调用次数,不配置累计正文读取预算。可配置上限为 384 轮、35 分钟总时限、5 分钟单次请求。旧冻结资源中的 maxReadBytes 仅供旧快照工具使用。移除 prompt 长度上限和读取覆盖门禁,输出形状仍由代码校验。详细说明见 [AI 执行规则](../../../../docs/ai-runtime-policy.md)。
|
|
16
16
|
|
|
17
17
|
真实模型效果检查按需进行,不在默认单测中调用外部模型。建议检查空目录、专题模板、自定义 README、单文档多事项和存在矛盾的资料;关注任务边界、状态依据及来源,而非固定措辞。
|
|
18
|
+
|
|
19
|
+
## 业务工具 query_tasks
|
|
20
|
+
|
|
21
|
+
当前原生 Agent 注册的 Teammy 业务工具只有 `query_tasks`,查询已有任务,不修改任务或文件。文件操作使用 Pi 原生工具。
|
|
22
|
+
|
|
23
|
+
所有参数均可省略,组合使用时取交集:
|
|
24
|
+
|
|
25
|
+
- `id`:任务 UUID,精确匹配。
|
|
26
|
+
- `source`:来源文件或目录,相对 `workspaces/`,包含成员前缀,例如 `alice/tasks`。匹配该路径及其下级路径。
|
|
27
|
+
- `query`:标题、目标、进展或来源路径的子串,不区分大小写。
|
|
28
|
+
- `visibility`:`active`、`archived` 或 `all`。默认 `active`;指定 `id` 时默认 `all`。`active` 表示未归档,也包含已完成任务。
|
|
29
|
+
|
|
30
|
+
返回 `{ items, total, omitted, note? }`。`total` 为匹配总数,`items` 最多 40 项,超出时给出省略数与提示,可缩小查询范围。每项包含 `taskId`、`title`、`goal`、`status`、`progress`、`needsConfirmation`、`sources`、`active`;来源包含 `path`、`sha256`、`revision` 与 `syncedAt`。任务表达已合并保存的人工修订。
|
|
31
|
+
|
|
32
|
+
当前数据范围是**作业创建时冻结的单成员任务**,不是实时的全项目查询;参数不支持项目或成员切换。调用范围由宿主提供的 `TaskContext` 和成员身份决定,工具自身不访问数据库,也没有同名 HTTP 路由。
|
|
33
|
+
|
|
34
|
+
参数定义见 [agent-protocol.ts](../agent-protocol.ts),查询实现见 [task-query.ts](../task-query.ts),上下文来源见 [task-context.ts](../task-context.ts),Pi 注册见 [native-pi-entry.ts](../native-pi-entry.ts),工具描述见 [shared/tools.json](shared/tools.json)。这些是当前可复用的定义与实现,不包含问答应用的接入方案。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{A as e,I as t,M as n,N as r,P as i,_ as a,b as o,c as s,d as c,f as l,h as u,m as d,n as f,o as p,p as m,t as h,u as g,v as _,x as v,y}from"./index-Db4kzLA8.js";var b=t(),x=_(),S=[{status:`planned`,label:`计划`},{status:`in_progress`,label:`进行中`},{status:`in_review`,label:`待验收`},{status:`completed`,label:`已完成`},{status:null,label:`未明确`}];function C({members:e}){let{request:t}=p(),n=(0,b.useCallback)(e=>t(`tasks`,h,{signal:e}),[t]),r=o(n,3e3),[i,a]=(0,b.useState)(),[l,f]=(0,b.useState)(),m=r.data,g=m?.members.some(e=>[`queued`,`running`].includes(e.status)),_=new Map(e.map(e=>[e.id,e.displayName||e.id])),v=m?.members.filter(e=>e.sourceChanged||e.uncertainties.length||[`failed`,`cancelled`].includes(e.status))??[];return(0,x.jsxs)(`div`,{className:`task-board`,children:[(0,x.jsx)(u,{title:`工作看板`}),(0,x.jsx)(`span`,{className:`sr-only`,role:`status`,children:g?`正在整理工作区`:r.loading&&!m?`正在读取任务`:``}),(0,x.jsx)(`section`,{className:`task-board-lanes`,"aria-label":`任务进度`,"data-testid":`progress-board`,children:S.filter(e=>e.status!==null||m?.tasks.some(e=>e.status===null)).map(e=>{let t=m?.tasks.filter(t=>t.status===e.status)??[];return(0,x.jsxs)(`section`,{className:`task-lane`,"aria-label":e.label,"data-stage":e.status??`unknown`,children:[(0,x.jsxs)(`h2`,{children:[(0,x.jsx)(`span`,{className:`lane-dot`}),e.label,(0,x.jsx)(s,{children:m?t.length:`—`})]}),(0,x.jsx)(`div`,{className:`task-lane-content`,children:t.length?t.map(e=>(0,x.jsx)(w,{task:e,names:_,onEdit:()=>a(e),onSource:t=>f({jobId:e.jobId,path:t})},e.id)):(0,x.jsx)(`p`,{className:`task-lane-empty`,children:m?`暂无任务`:`读取中…`})})]},e.label)})}),(0,x.jsxs)(`details`,{className:`board-activity`,"data-testid":`task-activity`,open:r.error?!0:void 0,children:[(0,x.jsxs)(`summary`,{children:[(0,x.jsx)(c,{name:`notifications`}),(0,x.jsx)(`span`,{children:`活动与提醒`}),(v.length>0||m&&!m.modelAvailable)&&(0,x.jsx)(s,{children:v.length+(m&&!m.modelAvailable?1:0)}),(0,x.jsx)(c,{name:`chevronDown`,size:`small`})]}),(0,x.jsxs)(`div`,{className:`board-activity-content`,children:[(0,x.jsx)(d,{error:!0,children:r.error}),m&&!m.modelAvailable&&(0,x.jsxs)(`p`,{children:[`配置模型后将自动整理工作区。`,(0,x.jsx)(`a`,{href:`/site-settings`,children:`配置模型`})]}),v.map(e=>(0,x.jsxs)(`div`,{className:`task-board-member-note`,children:[(0,x.jsxs)(`div`,{children:[(0,x.jsx)(`strong`,{children:_.get(e.owner)??e.owner}),(0,x.jsx)(`p`,{children:e.status===`failed`?`整理未完成`:e.status===`cancelled`?`整理已取消`:e.sourceChanged?`资料已有更新`:`分析备注`})]}),e.uncertainties.map((e,t)=>(0,x.jsx)(`p`,{className:`task-board-note-detail`,children:e},t))]},e.owner)),m?.modelAvailable&&!v.length&&!r.error&&(0,x.jsx)(`p`,{children:`暂无提醒`})]})]}),i&&(0,x.jsx)(E,{task:i,onClose:()=>a(void 0),onSaved:()=>{a(void 0),r.reload()}},i.id),l&&(0,x.jsx)(O,{source:l,onClose:()=>f(void 0)},`${l.jobId}:${l.path}`)]})}function w({task:e,names:t,onEdit:n,onSource:r}){return(0,x.jsxs)(`article`,{className:`task-card`,"data-testid":`task-card`,"data-task-id":e.id,"data-owner":e.owner,"data-status":e.status??`unknown`,children:[(0,x.jsxs)(`div`,{className:`task-card-heading`,children:[(0,x.jsx)(`h3`,{children:e.title}),(0,x.jsx)(l,{icon:`edit`,className:`task-card-edit`,label:`纠正任务:${e.title}`,"data-testid":`task-correct`,onClick:n})]}),(0,x.jsxs)(`small`,{children:[t.get(e.owner)??e.owner,e.corrected?` · 已纠正`:``,e.needsConfirmation?` · 待确认`:``]}),(0,x.jsx)(`p`,{className:`task-progress`,children:(0,x.jsx)(T,{value:e.progress,names:t})}),(0,x.jsxs)(`details`,{className:`task-card-details`,children:[(0,x.jsxs)(`summary`,{children:[`查看详情`,(0,x.jsx)(c,{name:`chevronDown`,size:`small`})]}),e.needsReview&&(0,x.jsx)(`p`,{children:`本次整理未能明确对应,请核对来源。`}),(0,x.jsx)(`p`,{children:e.goal?(0,x.jsx)(T,{value:e.goal,names:t}):`目标未明确`}),(0,x.jsx)(`div`,{className:`task-card-sources`,children:e.sources.map(e=>(0,x.jsxs)(`button`,{"data-testid":`task-source`,onClick:()=>r(e.path),children:[(0,x.jsx)(c,{name:`file`,size:`small`}),e.path]},e.path))}),e.syncedAt&&(0,x.jsxs)(`small`,{children:[`资料同步于 `,(0,x.jsx)(a,{value:e.syncedAt})]})]})]})}function T({value:e,names:t}){return e.split(/(@[a-z][a-z0-9_-]*)/gi).map((e,n)=>e.startsWith(`@`)&&t.has(e.slice(1))?(0,x.jsx)(`mark`,{className:`task-mention`,children:e},n):e)}function E({task:e,onClose:t,onSaved:n}){let{request:r}=p(),i=y(),[a,o]=(0,b.useState)(e.title),[s,c]=(0,b.useState)(e.status??``),[l,u]=(0,b.useState)(e.goal??``),[h,_]=(0,b.useState)(e.progress),[C,w]=(0,b.useState)(e.needsConfirmation),[T,E]=(0,b.useState)(!1),[D,O]=(0,b.useState)(``),k={...a===e.title?{}:{title:a},...s===(e.status??``)?{}:{status:s||null},...l===(e.goal??``)?{}:{goal:l||null},...h===e.progress?{}:{progress:h},...C===e.needsConfirmation?{}:{needsConfirmation:C}};async function A(t=!1){if(!(T||!t&&!Object.keys(k).length)){E(!0),O(``);try{await r(`tasks/${e.id}/correction`,f,{method:t?`DELETE`:`PATCH`,signal:i,...t?{}:{body:k}}),i.aborted||n()}catch(e){i.aborted||O(v(e))}finally{E(!1)}}}return(0,x.jsx)(m,{id:`task-correction`,title:`纠正任务`,onClose:t,children:(0,x.jsxs)(`form`,{className:`stack`,"aria-busy":T,onSubmit:e=>{e.preventDefault(),A()},children:[(0,x.jsx)(g,{id:`task-title`,label:`标题`,children:(0,x.jsx)(`input`,{value:a,required:!0,onChange:e=>o(e.target.value)})}),(0,x.jsx)(g,{id:`task-status`,label:`状态`,children:(0,x.jsx)(`select`,{value:s,onChange:e=>c(e.target.value),children:S.map(e=>(0,x.jsx)(`option`,{value:e.status??``,children:e.label},e.label))})}),(0,x.jsx)(g,{id:`task-goal`,label:`目标`,children:(0,x.jsx)(`textarea`,{value:l,onChange:e=>u(e.target.value)})}),(0,x.jsx)(g,{id:`task-progress`,label:`当前进展`,children:(0,x.jsx)(`textarea`,{value:h,onChange:e=>_(e.target.value)})}),(0,x.jsxs)(`label`,{className:`row`,children:[(0,x.jsx)(`input`,{type:`checkbox`,checked:C,onChange:e=>w(e.target.checked)}),`需要确认`]}),(0,x.jsx)(`small`,{children:`仅调整看板,不修改原始文件。`}),(0,x.jsx)(d,{error:!0,children:D}),(0,x.jsxs)(`div`,{className:`row wrap`,children:[(0,x.jsx)(`button`,{type:`submit`,disabled:T||!Object.keys(k).length,"data-testid":`task-correction-save`,children:`保存`}),e.corrected&&(0,x.jsx)(`button`,{type:`button`,disabled:T,"data-testid":`task-correction-reset`,onClick:()=>void A(!0),children:`恢复自动结果`})]})]})})}var D=r({content:i(),endLine:n(),hasMore:e()});function O({source:e,onClose:t}){let{request:n}=p(),[r,i]=(0,b.useState)(1),[a,s]=(0,b.useState)([]),c=(0,b.useCallback)(t=>n(`ai/jobs/${e.jobId}/source?${new URLSearchParams({path:e.path,startLine:String(r)})}`,D,{signal:t}),[n,e.jobId,e.path,r]),l=o(c);return(0,x.jsxs)(m,{id:`task-source-dialog`,title:e.path,onClose:t,children:[(0,x.jsx)(`small`,{children:`整理时的资料版本`}),(0,x.jsx)(d,{error:!0,children:l.error}),(0,x.jsx)(`pre`,{className:`raw task-source-content`,"data-testid":`task-source-content`,children:l.loading?`加载中…`:l.data?.content}),(0,x.jsxs)(`div`,{className:`row`,children:[(0,x.jsx)(`button`,{disabled:l.loading||!a.length,onClick:()=>{i(a.at(-1)),s(e=>e.slice(0,-1))},children:`上一段`}),(0,x.jsx)(`button`,{disabled:l.loading||!!l.error||!l.data?.hasMore,onClick:()=>{s(e=>[...e,r]),i(l.data.endLine+1)},children:`下一段`})]})]})}function k({members:e}){return(0,x.jsx)(C,{members:e})}export{k as Dashboard};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{I as e,S as t,a as n,b as r,d as i,f as a,g as o,i as s,l as c,m as l,o as u,r as d,v as f,w as p}from"./index-Db4kzLA8.js";import{n as m,r as h,t as g}from"./Markdown-DQn2KtR3.js";var _=e();function v(e,t){if(!t||/^(?:[a-z][a-z0-9+.-]*:|\/)/i.test(t))return null;try{let n=t.split(/[?#]/,1)[0],r=n?e.split(`/`).slice(0,-1):e.split(`/`);for(let e of n.split(`/`)){let t=decodeURIComponent(e);if(t&&t!==`.`){if(t===`..`){if(!r.length)return null;r.pop()}else{if(t.includes(`/`)||t.includes(`\\`))return null;r.push(t)}}}return{path:r.length?p.parse(r.join(`/`)):``,directory:n.endsWith(`/`),anchor:t.includes(`#`)?decodeURIComponent(t.slice(t.indexOf(`#`)+1)):``}}catch{return null}}var y=f();function b({members:e,me:i,onRefresh:o}){let{api:f}=u(),[p,g]=(0,_.useState)(()=>new Set([h(i,``)])),[b,S]=(0,_.useState)(),[C,w]=(0,_.useState)(),[T,E]=(0,_.useState)(0),[D,O]=(0,_.useState)(``),k=(0,_.useCallback)(async e=>b?{owner:b.owner,...await f.file(b.owner,b.path,void 0,e)}:void 0,[b?.owner,b?.path,T]),A=r(k),j=A.data?.owner===b?.owner&&A.data?.file.path===b?.path?A.data:void 0;function M(e,t){let n=t.split(`/`).filter(Boolean);g(t=>{let r=new Set(t);for(let t=0;t<=n.length;t++)r.add(h(e,n.slice(0,t).join(`/`)));return r})}function N(e,t){O(``),M(e,t.split(`/`).slice(0,-1).join(`/`)),S({owner:e,path:t}),w({owner:e,path:t,kind:`file`})}function P(t){if(!b)return!1;let n=v(b.owner===`_project`?b.path:`${b.owner}/${b.path}`,t);if(!n)return!/^[a-z][a-z0-9+.-]*:/i.test(t)&&!t.startsWith(`//`)&&(O(`该链接超出工作区,请在原仓库查看。`),!0);if(s(n.path))return O(`配置文件不在文件浏览中显示。`),!0;let[r,...i]=n.path.split(`/`),a=e.some(e=>e.id===r),o=a?r:d,c=a?i.join(`/`):n.path;return O(``),n.directory?(M(o,c),w({owner:o,path:c,kind:`directory`})):o===b.owner&&c===b.path?n.anchor&&[...document.querySelectorAll(`.markdown h1,.markdown h2,.markdown h3,.markdown h4`)].find(e=>e.textContent?.trim().toLowerCase().replace(/\s+/g,`-`)===n.anchor.toLowerCase())?.scrollIntoView({block:`center`}):N(o,c),!0}return(0,y.jsxs)(`div`,{className:`split-layout file-explorer`,"data-testid":`file-browser`,children:[(0,y.jsxs)(`aside`,{className:`file-tree-pane`,"aria-label":`文件资源管理器`,children:[(0,y.jsx)(n,{title:`文件`,icon:`files`,children:(0,y.jsx)(a,{icon:`refresh`,label:`刷新文件`,"data-testid":`files-refresh`,onClick:()=>{O(``),E(e=>e+1),o()}})}),(0,y.jsx)(m,{members:e,expanded:p,selected:C,revealed:!A.loading&&!A.error&&j?{owner:j.owner,path:j.file.path,file:j.file}:void 0,onSelect:N,onToggle:(e,t)=>{w({owner:e,path:t,kind:`directory`}),g(n=>{let r=new Set(n),i=h(e,t);return r.has(i)?r.delete(i):r.add(i),r})}},T)]}),(0,y.jsx)(`section`,{className:`file-detail`,"data-testid":`file-preview`,"data-owner-id":b?.owner,"data-file-path":b?.path,"aria-label":t.filePreview,children:b?(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(l,{error:!0,children:D||!A.loading&&A.error}),A.error&&(0,y.jsx)(`button`,{className:`file-entry-retry`,"data-testid":`files-retry`,onClick:()=>void A.reload(),children:t.retry}),A.loading?(0,y.jsx)(`p`,{className:`preview-body`,role:`status`,children:t.loading}):!A.error&&j&&(0,y.jsx)(x,{owner:j.owner,file:j.file,onLink:P},`${j.owner}:${j.file.path}:${j.file.sha256}`)]}):(0,y.jsx)(c,{mascot:!0,title:`从一份资料开始`,description:t.pickFile})})]})}function x({owner:e,file:n,onLink:s}){let{readTextBlob:c,blobUrl:d}=u(),[f,p]=(0,_.useState)(!1),m=n.size>524288,h=/\.(md|markdown)$/i.test(n.path),v=(0,_.useCallback)(e=>m?Promise.resolve(void 0):c(n,e),[n.sha256,n.size,m]),b=r(v);return(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(`header`,{className:`file-detail-header`,children:[(0,y.jsxs)(`span`,{className:`file-detail-path`,title:`${e===`_project`?`workspaces`:e}/${n.path}`,children:[(0,y.jsxs)(`span`,{children:[e===`_project`?`workspaces`:e,` / `]}),n.path]}),(0,y.jsxs)(o,{label:`文件操作`,children:[h&&(0,y.jsx)(a,{icon:f?`preview`:`code`,label:f?`切换为预览`:`切换为原文`,"data-testid":`file-toggle-raw`,"aria-pressed":f,onClick:()=>p(!f)}),(0,y.jsx)(`a`,{className:`icon-button`,"data-testid":`file-download`,"aria-label":t.download,title:t.download,href:d(n.sha256),download:n.path.split(`/`).at(-1),children:(0,y.jsx)(i,{name:`download`})})]})]}),m?(0,y.jsx)(`div`,{className:`empty`,children:(0,y.jsx)(`p`,{children:t.previewLarge})}):b.error?(0,y.jsxs)(`div`,{className:`preview-body`,children:[(0,y.jsx)(l,{error:!0,children:b.error}),(0,y.jsx)(`button`,{"data-testid":`file-preview-retry`,onClick:()=>void b.reload(),children:t.retry})]}):b.loading?(0,y.jsx)(`p`,{className:`preview-body`,role:`status`,children:t.loading}):(0,y.jsx)(`div`,{className:`preview-body`,children:h&&!f?(0,y.jsx)(`article`,{className:`markdown`,"data-testid":`file-content`,"data-format":`markdown`,children:(0,y.jsx)(g,{onLink:s,children:b.data??``})}):(0,y.jsx)(`pre`,{className:`raw`,"data-testid":`file-content`,"data-format":`text`,children:b.data})})]})}export{b as Files};
|