@teammy/server 0.1.1 → 0.1.2

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 CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  中心服务器上的 Teammy 服务,需要 Node.js 22.19 或更新版本。
4
4
 
5
- 0.1.1 增加镜像部署初始化、带路径前缀的访问地址、项目 SSH 密钥、仓库及子模块冷启动,并更新 instant Agent 和工作台交互。升级时同时更新 API、Worker 与成员客户端。
5
+ 0.1.2 增加 CLI 网页确认身份、提百事独立对话、页面缓存和项目状态灯,修复日志导出误报。保留镜像初始化、项目 SSH 密钥与仓库冷启动;升级时同时更新 API、Worker 与成员客户端。
6
6
 
7
7
  ```sh
8
- npm install -g @teammy/server@0.1.1
8
+ npm install -g @teammy/server@0.1.2
9
9
  teammy-server --help
10
10
  teammy-server --version
11
11
  teammy-server --host 127.0.0.1 --port 4317
package/dist/main.js CHANGED
@@ -634,6 +634,14 @@ var AgentRunSchema = z7.object({
634
634
  error: z7.string().nullable(),
635
635
  createdAt: z7.string()
636
636
  });
637
+ var AgentChatSummarySchema = z7.object({
638
+ id: z7.string(),
639
+ title: z7.string(),
640
+ createdAt: z7.string(),
641
+ updatedAt: z7.string(),
642
+ state: AgentRunSchema.shape.state.nullable()
643
+ });
644
+ var AgentChatListSchema = z7.object({ chats: z7.array(AgentChatSummarySchema) });
637
645
  var AgentChatSchema = z7.object({
638
646
  id: z7.string(),
639
647
  runs: z7.array(AgentRunSchema),
@@ -1289,36 +1297,46 @@ ${input.owner ? `\u76F8\u5173\u6210\u5458\uFF1A${input.owner}
1289
1297
  }
1290
1298
  flush() {
1291
1299
  if (this.hub.db.isTransaction) return;
1292
- const rows = this.hub.db.prepare(
1293
- "SELECT id,file_name,record_json FROM diagnostic_records WHERE exported=0 ORDER BY rowid"
1294
- ).all();
1295
1300
  try {
1301
+ const rows = this.hub.db.prepare(
1302
+ "SELECT id,file_name,record_json FROM diagnostic_records WHERE exported=0 ORDER BY rowid"
1303
+ ).all();
1296
1304
  for (const row of rows) {
1297
1305
  const path = join3(this.hub.directory, String(row.file_name)), temporary = `${path}.${randomUUID2()}.tmp`;
1298
- mkdirSync2(dirname3(path), { recursive: true, mode: 448 });
1299
1306
  try {
1300
- writeFileSync2(temporary, String(row.record_json), {
1301
- flag: "wx",
1302
- mode: 384,
1303
- flush: true
1304
- });
1307
+ mkdirSync2(dirname3(path), { recursive: true, mode: 448 });
1305
1308
  try {
1306
- linkSync(temporary, path);
1307
- } catch (error) {
1308
- if (error.code !== "EEXIST") throw error;
1309
- }
1310
- } finally {
1311
- try {
1312
- unlinkSync(temporary);
1313
- } catch (error) {
1314
- if (error.code !== "ENOENT") throw error;
1309
+ writeFileSync2(temporary, String(row.record_json), {
1310
+ flag: "wx",
1311
+ mode: 384,
1312
+ flush: true
1313
+ });
1314
+ try {
1315
+ linkSync(temporary, path);
1316
+ } catch (error) {
1317
+ if (error.code !== "EEXIST") throw error;
1318
+ }
1319
+ } finally {
1320
+ try {
1321
+ unlinkSync(temporary);
1322
+ } catch (error) {
1323
+ if (error.code !== "ENOENT") throw error;
1324
+ }
1315
1325
  }
1326
+ } catch (error) {
1327
+ this.condition("log-export", { code: "LOG_EXPORT_FAILED", error });
1328
+ return;
1316
1329
  }
1317
1330
  this.hub.db.prepare("UPDATE diagnostic_records SET exported=1 WHERE id=?").run(row.id);
1318
1331
  }
1319
- this.condition("log-export", null);
1332
+ if (this.hub.db.prepare(
1333
+ "SELECT 1 FROM diagnostic_conditions WHERE scope='log-export' AND fingerprint!=''"
1334
+ ).get())
1335
+ this.condition("log-export", null);
1320
1336
  } catch (error) {
1321
- this.condition("log-export", { code: "LOG_EXPORT_FAILED", error });
1337
+ if (error instanceof Error && "code" in error && error.code === "ERR_SQLITE_ERROR" && "errcode" in error && typeof error.errcode === "number" && [5, 6].includes(error.errcode & 255))
1338
+ return;
1339
+ throw error;
1322
1340
  }
1323
1341
  }
1324
1342
  };
@@ -1574,13 +1592,16 @@ var AgentStore = class {
1574
1592
  }
1575
1593
  hub;
1576
1594
  conversation(project, role, owner) {
1595
+ const prior = this.hub.db.prepare(
1596
+ "SELECT * FROM project_agent_conversations WHERE project_id=? AND role=? AND owner=? ORDER BY rowid DESC LIMIT 1"
1597
+ ).get(project, role, owner);
1598
+ if (prior) return prior;
1599
+ const id = randomUUID3();
1577
1600
  this.hub.db.prepare(
1578
1601
  `INSERT INTO project_agent_conversations(id,project_id,role,owner,created_at)
1579
- VALUES(?,?,?,?,?) ON CONFLICT(project_id,role,owner) DO NOTHING`
1580
- ).run(randomUUID3(), project, role, owner, now());
1581
- return this.hub.db.prepare(
1582
- "SELECT * FROM project_agent_conversations WHERE project_id=? AND role=? AND owner=?"
1583
- ).get(project, role, owner);
1602
+ VALUES(?,?,?,?,?)`
1603
+ ).run(id, project, role, owner, now());
1604
+ return this.hub.db.prepare("SELECT * FROM project_agent_conversations WHERE id=?").get(id);
1584
1605
  }
1585
1606
  runView(row) {
1586
1607
  return AgentRunSchema.parse({
@@ -1592,9 +1613,45 @@ var AgentStore = class {
1592
1613
  createdAt: row.created_at
1593
1614
  });
1594
1615
  }
1595
- chat(identity) {
1616
+ ownedChat(identity, id) {
1617
+ const row = this.hub.db.prepare(
1618
+ "SELECT * FROM project_agent_conversations WHERE id=? AND project_id=? AND owner=? AND role='instant'"
1619
+ ).get(id, identity.workspaceId, identity.memberId);
1620
+ if (!row) throw new HubError(404, "AGENT_CHAT_NOT_FOUND", "\u5BF9\u8BDD\u4E0D\u5B58\u5728\u3002");
1621
+ return row;
1622
+ }
1623
+ chats(identity) {
1624
+ return {
1625
+ chats: this.hub.db.prepare(
1626
+ `SELECT c.*,
1627
+ (SELECT text FROM project_agent_runs r WHERE r.conversation_id=c.id ORDER BY r.rowid LIMIT 1) title,
1628
+ (SELECT state FROM project_agent_runs r WHERE r.conversation_id=c.id
1629
+ ORDER BY CASE WHEN state='running' THEN 0 WHEN state='queued' THEN 1 ELSE 2 END,r.rowid DESC LIMIT 1) state,
1630
+ (SELECT MAX(updated_at) FROM project_agent_runs r WHERE r.conversation_id=c.id) updated_at
1631
+ FROM project_agent_conversations c WHERE project_id=? AND owner=? AND role='instant'
1632
+ ORDER BY COALESCE(updated_at,created_at) DESC,c.rowid DESC`
1633
+ ).all(identity.workspaceId, identity.memberId).map((row) => ({
1634
+ id: String(row.id),
1635
+ title: row.title == null ? "\u65E0\u6807\u9898" : String(row.title).replace(/\s+/g, " ").trim(),
1636
+ state: row.state ?? null,
1637
+ createdAt: String(row.created_at),
1638
+ updatedAt: String(row.updated_at ?? row.created_at)
1639
+ }))
1640
+ };
1641
+ }
1642
+ createChat(identity) {
1596
1643
  return transaction(this.hub.db, () => {
1597
- const conversation = this.conversation(identity.workspaceId, "instant", identity.memberId);
1644
+ const id = randomUUID3();
1645
+ this.hub.db.prepare(
1646
+ `INSERT INTO project_agent_conversations(id,project_id,role,owner,created_at)
1647
+ VALUES(?,?,'instant',?,?)`
1648
+ ).run(id, identity.workspaceId, identity.memberId, now());
1649
+ return this.chatView(id);
1650
+ });
1651
+ }
1652
+ chat(identity, id) {
1653
+ return transaction(this.hub.db, () => {
1654
+ const conversation = id ? this.ownedChat(identity, id) : this.conversation(identity.workspaceId, "instant", identity.memberId);
1598
1655
  return this.chatView(String(conversation.id));
1599
1656
  });
1600
1657
  }
@@ -1617,9 +1674,9 @@ var AgentStore = class {
1617
1674
  "SELECT seq,kind,data FROM project_agent_stream WHERE conversation_id=? AND seq>? ORDER BY seq"
1618
1675
  ).all(conversation, after);
1619
1676
  }
1620
- ask(identity, text) {
1677
+ ask(identity, text, chatId) {
1621
1678
  return transaction(this.hub.db, () => {
1622
- const conversation = this.conversation(identity.workspaceId, "instant", identity.memberId);
1679
+ const conversation = chatId ? this.ownedChat(identity, chatId) : this.conversation(identity.workspaceId, "instant", identity.memberId);
1623
1680
  const id = randomUUID3(), date = now();
1624
1681
  this.hub.db.prepare(
1625
1682
  "INSERT INTO project_agent_runs(id,conversation_id,text,created_at,updated_at) VALUES(?,?,?,?,?)"
@@ -2014,6 +2071,17 @@ var RepositoryStatusSchema = z10.object({
2014
2071
  ]),
2015
2072
  lastSuccessAt: z10.string().nullable()
2016
2073
  });
2074
+ var ProjectActivitySchema = z10.object({
2075
+ worker: WorkerStatusSchema.shape.state,
2076
+ modelAvailable: z10.boolean(),
2077
+ activities: z10.array(
2078
+ z10.object({
2079
+ kind: z10.enum(["repository", "import", "tasks", "sessions", "project", "chat"]),
2080
+ state: z10.enum(["queued", "running", "failed"]),
2081
+ count: z10.number().int().positive()
2082
+ })
2083
+ )
2084
+ });
2017
2085
 
2018
2086
  // packages/protocol/bootstrap.ts
2019
2087
  var SandboxStatusSchema = z11.object({
@@ -3567,6 +3635,24 @@ var instantAgentMigration = `
3567
3635
  UPDATE project_agent_conversations SET role='instant' WHERE role='constant';
3568
3636
  ALTER TABLE project_agent_outbox ADD COLUMN source_run_id TEXT REFERENCES project_agent_runs(id);
3569
3637
  `;
3638
+ var agentChatsMigration = `
3639
+ PRAGMA defer_foreign_keys=ON;
3640
+ CREATE TABLE project_agent_conversations_new(
3641
+ id TEXT PRIMARY KEY,project_id TEXT NOT NULL,role TEXT NOT NULL,owner TEXT NOT NULL,
3642
+ session_file TEXT,created_at TEXT NOT NULL
3643
+ );
3644
+ INSERT INTO project_agent_conversations_new SELECT * FROM project_agent_conversations ORDER BY rowid;
3645
+ DROP TABLE project_agent_conversations;
3646
+ ALTER TABLE project_agent_conversations_new RENAME TO project_agent_conversations;
3647
+ CREATE UNIQUE INDEX project_agent_lifetime_conversation ON project_agent_conversations(project_id,role) WHERE role='lifetime';
3648
+ CREATE INDEX project_agent_chat_owner ON project_agent_conversations(project_id,role,owner);
3649
+ -- Rebuilding the referenced table leaves deferred violation counters behind in SQLite.
3650
+ -- Validate the actual references before clearing those counters; IDs and Pi files stay intact.
3651
+ CREATE TEMP TABLE project_agent_chat_fk_check(violations INTEGER CHECK(violations=0));
3652
+ INSERT INTO project_agent_chat_fk_check SELECT COUNT(*) FROM pragma_foreign_key_check;
3653
+ DROP TABLE project_agent_chat_fk_check;
3654
+ PRAGMA defer_foreign_keys=OFF;
3655
+ `;
3570
3656
 
3571
3657
  // apps/server/diagnostics/schema.ts
3572
3658
  var diagnosticsMigration = `
@@ -3628,6 +3714,16 @@ var workspaceImportMigration = `
3628
3714
  );
3629
3715
  `;
3630
3716
 
3717
+ // apps/server/client-access-schema.ts
3718
+ var clientAccessMigration = `
3719
+ CREATE TABLE device_connect_requests (
3720
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL, device_id TEXT NOT NULL,
3721
+ secret_hash TEXT NOT NULL, fingerprint TEXT NOT NULL, label TEXT NOT NULL,
3722
+ expires_at REAL NOT NULL, approved_by TEXT
3723
+ );
3724
+ CREATE INDEX device_connect_expiry ON device_connect_requests(expires_at);
3725
+ `;
3726
+
3631
3727
  // apps/server/ai/schema.ts
3632
3728
  var aiConfigMigration = `
3633
3729
  CREATE TABLE ai_profiles (
@@ -3699,7 +3795,9 @@ var migrations = [
3699
3795
  workspaceImportMigration,
3700
3796
  runtimeStatusMigration,
3701
3797
  instantAgentMigration,
3702
- sandboxStatusMigration
3798
+ sandboxStatusMigration,
3799
+ agentChatsMigration,
3800
+ clientAccessMigration
3703
3801
  ];
3704
3802
  var HubStore = class {
3705
3803
  constructor(directory) {
@@ -8303,6 +8401,7 @@ function registerWeb(server, root) {
8303
8401
  "/account",
8304
8402
  "/projects",
8305
8403
  "/site-settings",
8404
+ "/connect",
8306
8405
  "/projects/:projectId/dashboard",
8307
8406
  "/projects/:projectId/assistant",
8308
8407
  "/projects/:projectId/files",
@@ -8425,6 +8524,68 @@ function registerControlRoutes(server, store) {
8425
8524
 
8426
8525
  // apps/server/project-routes.ts
8427
8526
  import { z as z29 } from "zod";
8527
+
8528
+ // apps/server/project-activity.ts
8529
+ function projectActivity(hub, identity) {
8530
+ const db = hub.db, project = identity.workspaceId;
8531
+ const activities = [];
8532
+ const add = (kind, state) => {
8533
+ if (state !== "queued" && state !== "running" && state !== "failed") return;
8534
+ const prior = activities.find((item) => item.kind === kind && item.state === state);
8535
+ if (prior) prior.count++;
8536
+ else activities.push({ kind, state, count: 1 });
8537
+ };
8538
+ const runtime = new RuntimeStatus(hub);
8539
+ const url = new AgentConfig(hub).get(project).repositoryUrl;
8540
+ const repository = runtime.repository(project, url ? fingerprint(url) : null);
8541
+ const importing = db.prepare("SELECT state FROM project_workspace_imports WHERE project_id=?").get(project)?.state;
8542
+ if (repository.state === "cloning" || repository.state === "fetching")
8543
+ add("repository", "running");
8544
+ else if (importing === "pending" || importing === "running")
8545
+ add("import", importing === "pending" ? "queued" : "running");
8546
+ else if (importing === "failed") add("import", "failed");
8547
+ else if (repository.state === "failed" || repository.state === "interrupted")
8548
+ add("repository", "failed");
8549
+ const members = db.prepare(
8550
+ `SELECT CASE
8551
+ WHEN j.status IN ('queued','running') THEN j.status
8552
+ WHEN b.job_id IS NULL AND EXISTS(SELECT 1 FROM current_files f WHERE f.workspace_id=m.workspace_id AND f.owner=m.member_id) THEN 'queued'
8553
+ WHEN j.status IN ('succeeded','failed','cancelled') AND
8554
+ (json_extract(j.snapshot_json,'$.revision')<m.file_revision OR
8555
+ COALESCE(json_extract(j.snapshot_json,'$.project.revision'),0)<COALESCE(pf.file_revision,0)) THEN 'queued'
8556
+ WHEN j.status='failed' THEN 'failed' END state
8557
+ FROM members m JOIN users u ON u.id=m.member_id
8558
+ LEFT JOIN task_board_members b ON b.workspace_id=m.workspace_id AND b.owner=m.member_id
8559
+ LEFT JOIN ai_jobs j ON j.id=b.job_id LEFT JOIN project_files pf ON pf.workspace_id=m.workspace_id
8560
+ WHERE m.workspace_id=? AND m.enabled=1 AND u.enabled=1`
8561
+ ).all(project);
8562
+ for (const row of members) add("tasks", row.state);
8563
+ const sessionJobs = db.prepare(
8564
+ `SELECT j.status FROM session_analysis_jobs j
8565
+ JOIN members m ON m.workspace_id=j.workspace_id AND m.member_id=j.owner JOIN users u ON u.id=m.member_id
8566
+ WHERE j.workspace_id=? AND m.enabled=1 AND u.enabled=1 AND j.status!='superseded'
8567
+ AND NOT EXISTS(SELECT 1 FROM session_analysis_jobs n WHERE n.workspace_id=j.workspace_id
8568
+ AND n.owner=j.owner AND n.session_key=j.session_key AND n.kind=j.kind AND n.source_revision>j.source_revision)`
8569
+ ).all(project);
8570
+ for (const row of sessionJobs) add("sessions", row.status);
8571
+ const inputs = db.prepare(
8572
+ `SELECT state FROM project_agent_inbox WHERE project_id=? AND
8573
+ (state IN ('queued','running') OR task_id=(SELECT MAX(task_id) FROM project_agent_inbox WHERE project_id=?))`
8574
+ ).all(project, project);
8575
+ for (const row of inputs) add("project", row.state);
8576
+ const chats = db.prepare(
8577
+ `SELECT r.state FROM project_agent_runs r JOIN project_agent_conversations c ON c.id=r.conversation_id
8578
+ WHERE c.project_id=? AND c.owner=? AND c.role='instant' AND r.state IN ('queued','running')`
8579
+ ).all(project, identity.memberId);
8580
+ for (const row of chats) add("chat", row.state);
8581
+ return ProjectActivitySchema.parse({
8582
+ worker: runtime.worker().state,
8583
+ modelAvailable: new AiDefaults(hub).project(project).available,
8584
+ activities
8585
+ });
8586
+ }
8587
+
8588
+ // apps/server/project-routes.ts
8428
8589
  function registerProjectRoutes(server, store) {
8429
8590
  const project = new ProjectRepository(store), site = new SiteRepository(store);
8430
8591
  server.get("/api/v1/projects", async (request) => ({
@@ -8440,6 +8601,10 @@ function registerProjectRoutes(server, store) {
8440
8601
  }
8441
8602
  function registerProjectScopeRoutes(server, store) {
8442
8603
  const project = new ProjectRepository(store);
8604
+ server.get(
8605
+ "/project/activity",
8606
+ async (request) => projectActivity(store, authorizeWeb(store, request))
8607
+ );
8443
8608
  server.get(
8444
8609
  "/project/workspace-import",
8445
8610
  async (request) => new WorkspaceImports(store).status(authorizeWeb(store, request).workspaceId)
@@ -8987,54 +9152,64 @@ ${text}`
8987
9152
  ).all(identity.workspaceId)
8988
9153
  };
8989
9154
  });
8990
- server.get("/agents/chat", async (req) => store.chat(authorizeWeb(hub, req)));
8991
- server.post("/agents/chat", async (req, reply) => {
9155
+ server.get("/agents/chats", async (req) => store.chats(authorizeWeb(hub, req)));
9156
+ server.post("/agents/chats", async (req, reply) => {
8992
9157
  webMutation(req);
8993
- const identity = authorizeWeb(hub, req);
8994
- new AiDefaults(hub).resolve(identity.workspaceId);
8995
- config.initialize(identity.workspaceId);
8996
- return reply.code(202).send(store.ask(identity, AgentTextSchema.parse(req.body).text));
9158
+ return reply.code(201).send(store.createChat(authorizeWeb(hub, req)));
8997
9159
  });
8998
- server.get("/agents/chat/events", async (req, reply) => {
8999
- const identity = authorizeWeb(hub, req), snapshot = store.chat(identity);
9000
- reply.hijack();
9001
- const response = reply.raw;
9002
- response.writeHead(200, {
9003
- "content-type": "text/event-stream; charset=utf-8",
9004
- "cache-control": "no-store",
9005
- connection: "keep-alive",
9006
- "x-accel-buffering": "no"
9160
+ for (const path of ["/agents/chat", "/agents/chats/:id"]) {
9161
+ const chatId = (params) => path.endsWith(":id") ? messageId(params) : void 0;
9162
+ server.get(path, async (req) => store.chat(authorizeWeb(hub, req), chatId(req.params)));
9163
+ server.post(path, async (req, reply) => {
9164
+ webMutation(req);
9165
+ const identity = authorizeWeb(hub, req);
9166
+ const id = chatId(req.params);
9167
+ if (id) store.chat(identity, id);
9168
+ new AiDefaults(hub).resolve(identity.workspaceId);
9169
+ config.initialize(identity.workspaceId);
9170
+ return reply.code(202).send(store.ask(identity, AgentTextSchema.parse(req.body).text, id));
9007
9171
  });
9008
- streams.add(response);
9009
- response.write(`event: snapshot
9172
+ server.get(`${path}/events`, async (req, reply) => {
9173
+ const identity = authorizeWeb(hub, req), snapshot = store.chat(identity, chatId(req.params));
9174
+ reply.hijack();
9175
+ const response = reply.raw;
9176
+ response.writeHead(200, {
9177
+ "content-type": "text/event-stream; charset=utf-8",
9178
+ "cache-control": "no-store",
9179
+ connection: "keep-alive",
9180
+ "x-accel-buffering": "no"
9181
+ });
9182
+ streams.add(response);
9183
+ response.write(`event: snapshot
9010
9184
  data: ${JSON.stringify(snapshot)}
9011
9185
 
9012
9186
  `);
9013
- let cursor = snapshot.cursor, lastPing = Date.now();
9014
- const timer = setInterval(() => {
9015
- try {
9016
- authorizeWeb(hub, req);
9017
- for (const event of store.events(snapshot.id, cursor)) {
9018
- response.write(`id: ${event.seq}
9187
+ let cursor = snapshot.cursor, lastPing = Date.now();
9188
+ const timer = setInterval(() => {
9189
+ try {
9190
+ authorizeWeb(hub, req);
9191
+ for (const event of store.events(snapshot.id, cursor)) {
9192
+ response.write(`id: ${event.seq}
9019
9193
  event: ${event.kind}
9020
9194
  data: ${event.data}
9021
9195
 
9022
9196
  `);
9023
- cursor = Number(event.seq);
9024
- }
9025
- if (Date.now() - lastPing > 15e3) {
9026
- response.write(": connected\n\n");
9027
- lastPing = Date.now();
9197
+ cursor = Number(event.seq);
9198
+ }
9199
+ if (Date.now() - lastPing > 15e3) {
9200
+ response.write(": connected\n\n");
9201
+ lastPing = Date.now();
9202
+ }
9203
+ } catch {
9204
+ response.end();
9028
9205
  }
9029
- } catch {
9030
- response.end();
9031
- }
9032
- }, 100);
9033
- response.once("close", () => {
9034
- clearInterval(timer);
9035
- streams.delete(response);
9206
+ }, 100);
9207
+ response.once("close", () => {
9208
+ clearInterval(timer);
9209
+ streams.delete(response);
9210
+ });
9036
9211
  });
9037
- });
9212
+ }
9038
9213
  server.get("/agents/outbox", async (req) => store.messages(authorizeWeb(hub, req)));
9039
9214
  server.get("/agents/outbox/unread", async (req) => {
9040
9215
  const identity = authorizeWeb(hub, req);
@@ -9155,6 +9330,109 @@ function registerBootstrapRoutes(server, hub) {
9155
9330
  });
9156
9331
  }
9157
9332
 
9333
+ // apps/server/client-access.ts
9334
+ import { z as z36 } from "zod";
9335
+
9336
+ // packages/protocol/client-access.ts
9337
+ import { z as z35 } from "zod";
9338
+ var ConnectSecretSchema = z35.string().regex(/^[a-f0-9]{64}$/);
9339
+ var BrowserConnectRequestSchema = z35.object({
9340
+ id: z35.uuid(),
9341
+ projectId: NameSchema,
9342
+ deviceId: DeviceIdSchema,
9343
+ secret: ConnectSecretSchema,
9344
+ label: z35.string().min(1).max(255)
9345
+ }).strict();
9346
+ var BrowserConnectStartSchema = z35.object({ id: z35.uuid(), expiresAt: z35.number() });
9347
+ var BrowserConnectPollSchema = z35.discriminatedUnion("state", [
9348
+ z35.object({ state: z35.literal("pending") }),
9349
+ z35.object({ state: z35.literal("approved"), memberId: NameSchema })
9350
+ ]);
9351
+ var BrowserConnectInfoSchema = z35.object({
9352
+ projectName: z35.string(),
9353
+ label: z35.string(),
9354
+ approvedBy: NameSchema.nullable(),
9355
+ expiresAt: z35.number()
9356
+ });
9357
+
9358
+ // apps/server/client-access.ts
9359
+ function registerClientAccess(server, store) {
9360
+ const project = new ProjectRepository(store);
9361
+ const read = (id) => {
9362
+ const row = store.db.prepare("SELECT * FROM device_connect_requests WHERE id=?").get(z36.uuid().parse(id));
9363
+ if (!row || Number(row.expires_at) <= Date.now())
9364
+ throw new HubError(410, "CONNECT_EXPIRED", "\u8FDE\u63A5\u8BF7\u6C42\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C teammy connect\u3002");
9365
+ return row;
9366
+ };
9367
+ server.post("/api/v1/devices/connect", async (request) => {
9368
+ const value = BrowserConnectRequestSchema.parse(request.body);
9369
+ project.config({ workspaceId: value.projectId, memberId: "" });
9370
+ store.db.prepare("DELETE FROM device_connect_requests WHERE expires_at<=?").run(Date.now());
9371
+ const fingerprint2 = digest(JSON.stringify(value));
9372
+ const prior = store.db.prepare("SELECT fingerprint,expires_at FROM device_connect_requests WHERE id=?").get(value.id);
9373
+ if (prior && prior.fingerprint !== fingerprint2)
9374
+ throw new HubError(
9375
+ 409,
9376
+ "OPERATION_MISMATCH",
9377
+ "Connection request was reused with different content."
9378
+ );
9379
+ const expiresAt = prior ? Number(prior.expires_at) : Date.now() + 10 * 6e4;
9380
+ if (!prior)
9381
+ store.db.prepare("INSERT INTO device_connect_requests VALUES (?,?,?,?,?,?,?,NULL)").run(
9382
+ value.id,
9383
+ value.projectId,
9384
+ value.deviceId,
9385
+ digest(value.secret),
9386
+ fingerprint2,
9387
+ value.label,
9388
+ expiresAt
9389
+ );
9390
+ return { id: value.id, expiresAt };
9391
+ });
9392
+ server.post("/api/v1/devices/connect/:id/poll", async (request) => {
9393
+ const { secret } = z36.object({ secret: ConnectSecretSchema }).strict().parse(request.body);
9394
+ const row = read(request.params.id);
9395
+ if (row.secret_hash !== digest(secret))
9396
+ throw new HubError(
9397
+ 403,
9398
+ "CONNECT_SECRET_INVALID",
9399
+ "Connection request does not belong to this client."
9400
+ );
9401
+ if (!row.approved_by) return { state: "pending" };
9402
+ const member = store.db.prepare(
9403
+ "SELECT 1 FROM members m JOIN users u ON u.id=m.member_id WHERE m.workspace_id=? AND m.member_id=? AND m.enabled=1 AND u.enabled=1"
9404
+ ).get(row.project_id, row.approved_by);
9405
+ if (!member) throw new HubError(403, "PROJECT_NOT_FOUND", "Project access was removed.");
9406
+ return { state: "approved", memberId: row.approved_by };
9407
+ });
9408
+ server.get("/api/v1/devices/connect/:id", async (request) => {
9409
+ const row = read(request.params.id);
9410
+ const identity = project.session(cookieToken(request), String(row.project_id));
9411
+ return {
9412
+ projectName: project.config(identity).projectName,
9413
+ label: row.label,
9414
+ approvedBy: row.approved_by,
9415
+ expiresAt: row.expires_at
9416
+ };
9417
+ });
9418
+ server.post(
9419
+ "/api/v1/devices/connect/:id/approve",
9420
+ async (request) => {
9421
+ webMutation(request);
9422
+ const row = read(request.params.id);
9423
+ const identity = project.session(cookieToken(request), String(row.project_id));
9424
+ if (row.approved_by && row.approved_by !== identity.memberId)
9425
+ throw new HubError(409, "CONNECT_APPROVED", "\u6B64\u8FDE\u63A5\u8BF7\u6C42\u5DF2\u7531\u53E6\u4E00\u4F4D\u7528\u6237\u786E\u8BA4\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u3002");
9426
+ const changed = store.db.prepare(
9427
+ "UPDATE device_connect_requests SET approved_by=? WHERE id=? AND (approved_by IS NULL OR approved_by=?)"
9428
+ ).run(identity.memberId, request.params.id, identity.memberId);
9429
+ if (!changed.changes)
9430
+ throw new HubError(409, "CONNECT_APPROVED", "\u6B64\u8FDE\u63A5\u8BF7\u6C42\u5DF2\u7531\u53E6\u4E00\u4F4D\u7528\u6237\u786E\u8BA4\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u3002");
9431
+ return { state: "approved", memberId: identity.memberId };
9432
+ }
9433
+ );
9434
+ }
9435
+
9158
9436
  // apps/server/server.ts
9159
9437
  function buildServer(options) {
9160
9438
  const store = options.dataDir ? new HubStore(options.dataDir) : void 0;
@@ -9251,6 +9529,7 @@ function buildServer(options) {
9251
9529
  "project-config",
9252
9530
  "multi-project",
9253
9531
  "web-identity",
9532
+ "browser-connect",
9254
9533
  "device-status",
9255
9534
  "ai-profiles",
9256
9535
  "ai-tasks",
@@ -9260,6 +9539,7 @@ function buildServer(options) {
9260
9539
  }));
9261
9540
  if (store) {
9262
9541
  registerSiteRoutes(server, store);
9542
+ registerClientAccess(server, store);
9263
9543
  registerBootstrapRoutes(server, store);
9264
9544
  registerProjectRoutes(server, store);
9265
9545
  registerAiRoutes(server, store);
@@ -0,0 +1 @@
1
+ import{C as e,D as t,S as n,at as r,b as i,f as a,it as ee,lt as o,m as s,r as te,u as c,ut as ne,w as l}from"./project-context--9YVXMif.js";import{a as u,i as re,l as ie,n as d,r as f,t as ae,u as p}from"./index-COCTfq_q.js";import{t as m}from"./Markdown-B3gyVKQQ.js";var h=o(),g=l(),_=e=>new Date(e).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`}),v=e=>e.replace(/[#*_`>]/g,``).replace(/\s+/g,` `).trim();function y({mine:e=!1,author:t,time:n,children:r}){return(0,g.jsx)(`div`,{className:`assistant-bubble-row ${e?`mine`:``}`,children:(0,g.jsxs)(`div`,{className:`assistant-bubble`,children:[(0,g.jsxs)(`div`,{className:`assistant-bubble-meta`,children:[(0,g.jsx)(`span`,{children:t}),(0,g.jsx)(`time`,{dateTime:n,children:_(n)})]}),r]})})}function b({me:o}){let{projectId:l,request:b}=te(),S=e(),C=n(l,o,`assistant-selection`),w=n(l,o,`assistant-drafts`),oe=(0,h.useCallback)(e=>b(`agents/outbox`,re,{signal:e}),[b]),T=i(oe,3e3,n(l,o,`outbox`)),se=(0,h.useCallback)(e=>b(`agents/chats`,ae,{signal:e}),[b]),E=i(se,3e3,n(l,o,`chats`)),[D,ce]=(0,h.useState)(()=>{let e=new URLSearchParams(location.search).get(`chat`);return e?`chat:${e}`:S?.get(C)??``}),O=D.startsWith(`chat:`)?D.slice(5):``,k=!D||!!O,[A,j]=(0,h.useState)(()=>S?.get(n(l,o,`chat`,O))),M=A?.id===O?A:S?.get(n(l,o,`chat`,O)),le=E.data?.chats.find(e=>e.id===O),[ue,N]=(0,h.useState)(),[P,de]=(0,h.useState)(()=>S?.get(w)??{}),F=P[D]??``,I=e=>de(t=>({...t,[D]:e})),[L,R]=(0,h.useState)(!1),[z,B]=(0,h.useState)(!1),[fe,V]=(0,h.useState)(``),[H,U]=(0,h.useState)(``),W=(0,h.useRef)(null),G=(0,h.useRef)(!0),K=(0,h.useRef)(D);K.current=D,(0,h.useEffect)(()=>{A&&S?.set(n(l,o,`chat`,A.id),A)},[S,l,o,A]),(0,h.useEffect)(()=>{S?.set(w,P)},[S,w,P]);function q(e){K.current=e,ce(e),S?.set(C,e);let t=new URL(location.href);e.startsWith(`chat:`)?t.searchParams.set(`chat`,e.slice(5)):t.searchParams.delete(`chat`),history.replaceState(history.state,``,t)}(0,h.useEffect)(()=>{!D&&E.data?.chats.length&&q(`chat:${E.data.chats[0].id}`)},[D,E.data]);let J=(T.data?.messages??[]).filter(e=>e.box===`debug`).reverse(),Y=T.data?.messages.find(e=>e.id===(D===`debug`?ue:D)),X=J.filter(e=>!e.readAt).map(e=>e.id).join(`,`);(0,h.useEffect)(()=>{D===`debug`&&X&&Promise.all(X.split(`,`).map(e=>b(`agents/outbox/${e}/read`,u,{method:`POST`}))).then(()=>{window.dispatchEvent(new Event(`teammy:outbox`)),T.reload()}).catch(e=>V(t(e)))},[D,X,b,T.reload]),(0,h.useEffect)(()=>{if(U(``),!O)return;let e=!0,n=new EventSource(ne(`/api/v1/projects/${encodeURIComponent(l)}/agents/chats/${encodeURIComponent(O)}/events`));n.addEventListener(`snapshot`,n=>{if(e)try{j(d.parse(JSON.parse(n.data))),U(``)}catch(e){U(t(e))}}),n.onerror=()=>{e&&U(`对话连接中断,正在重新连接…`)},n.addEventListener(`run`,t=>{if(!e)return;let n=f.parse(JSON.parse(t.data));j(e=>e&&{...e,runs:e.runs.some(e=>e.id===n.id)?e.runs:[...e.runs,n]})});for(let t of[`delta`,`reset`,`state`])n.addEventListener(t,n=>{if(!e)return;let r=JSON.parse(n.data);j(e=>e&&{...e,runs:e.runs.map(e=>e.id===r.id?{...e,answer:t===`delta`?e.answer+(r.text??``):t===`reset`?``:r.answer??e.answer,state:r.state??e.state,error:r.error===void 0?e.error:r.error}:e)})});return()=>{e=!1,n.close()}},[l,O]),(0,h.useEffect)(()=>{G.current&&W.current?.scrollIntoView({block:`end`})},[D,A?.runs,Y?.recipients,J.length]);function Z(){window.dispatchEvent(new Event(`teammy:outbox`)),T.reload()}async function Q(e){if(G.current=!0,q(e),V(``),N(void 0),!(e.startsWith(`chat:`)||e===`debug`))try{await b(`agents/outbox/${e}/read`,u,{method:`POST`}),Z()}catch(e){V(t(e))}}async function pe(){if(!z){B(!0),V(``);try{let e=await b(`agents/chats`,d,{method:`POST`});j(e),await Q(`chat:${e.id}`),E.reload()}catch(e){V(t(e))}finally{B(!1)}}}async function me(e){e.preventDefault();let n=F.trim();if(n&&!L){R(!0),V(``);try{if(k){let e=O;if(!e){let t=await b(`agents/chats`,d,{method:`POST`});e=t.id,K.current===D&&(j(t),q(`chat:${e}`))}let t=await b(`agents/chats/${e}`,f,{method:`POST`,body:{text:n}});j(n=>n?.id===e?{...n,runs:n.runs.some(e=>e.id===t.id)?n.runs:[...n.runs,t]}:n),E.reload()}else await b(`agents/outbox/${Y.id}/reply`,r({taskId:ee()}),{method:`POST`,body:{body:n}}),Z(),N(void 0);I(``)}catch(e){V(t(e))}finally{R(!1)}}}let $=k||!!Y&&!Y.archivedAt&&!Y.recipients.find(e=>e.userId===o)?.reply,he=k&&!M&&(!!O||!E.data);return(0,g.jsxs)(`div`,{className:`split-layout assistant-layout`,"data-testid":`assistant-page`,children:[(0,g.jsxs)(`aside`,{className:`assistant-list`,"aria-label":`消息列表`,children:[(0,g.jsx)(`header`,{className:`assistant-list-heading`,children:(0,g.jsx)(`h1`,{children:`消息`})}),(0,g.jsxs)(`div`,{className:`assistant-message-scroll`,children:[E.loading&&!E.data&&(0,g.jsx)(`p`,{className:`assistant-list-status`,role:`status`,children:`加载中…`}),E.data?.chats.map(e=>(0,g.jsxs)(`button`,{type:`button`,"data-testid":`assistant-chat-${e.id}`,className:`assistant-message-item assistant-chat-item ${O===e.id?`selected`:``}`,"aria-pressed":O===e.id,onClick:()=>void Q(`chat:${e.id}`),children:[(0,g.jsx)(`span`,{className:`assistant-message-dot ${e.state===`running`||e.state===`queued`?`unread`:``}`}),(0,g.jsxs)(`span`,{className:`assistant-message-copy`,children:[(0,g.jsx)(p,{selected:O===e.id,children:(0,g.jsx)(`strong`,{children:e.title})}),(0,g.jsxs)(`time`,{dateTime:e.updatedAt,children:[new Date(e.updatedAt).toLocaleDateString([],{month:`numeric`,day:`numeric`}),` `,_(e.updatedAt)]})]})]},e.id)),!!J.length&&(0,g.jsxs)(`button`,{type:`button`,"data-testid":`debug-outbox`,className:`assistant-message-item ${D===`debug`?`selected`:``}`,onClick:()=>void Q(`debug`),children:[(0,g.jsx)(`span`,{className:`assistant-message-dot ${X?`unread`:``}`}),(0,g.jsxs)(`span`,{className:`assistant-message-copy`,children:[(0,g.jsx)(p,{selected:D===`debug`,children:(0,g.jsx)(`strong`,{children:`运行提醒`})}),(0,g.jsx)(`span`,{children:v(J.at(-1).body)})]})]}),T.data?.messages.filter(e=>e.box!==`debug`).sort((e,t)=>Number(!!e.archivedAt)-Number(!!t.archivedAt)).map(e=>(0,g.jsxs)(`button`,{type:`button`,"data-testid":`outbox-${e.id}`,className:`assistant-message-item ${e.archivedAt?`archived`:``} ${D===e.id?`selected`:``}`,onClick:()=>void Q(e.id),children:[(0,g.jsx)(`span`,{className:`assistant-message-dot ${e.readAt?``:`unread`}`}),(0,g.jsxs)(`span`,{className:`assistant-message-copy`,children:[(0,g.jsx)(p,{selected:D===e.id,children:(0,g.jsx)(`strong`,{children:v(e.body)})}),(0,g.jsxs)(`time`,{dateTime:e.createdAt,children:[new Date(e.createdAt).toLocaleDateString([],{month:`numeric`,day:`numeric`}),` `,_(e.createdAt)]})]})]},e.id))]}),(0,g.jsx)(s,{error:!0,children:T.error||E.error}),(0,g.jsx)(`footer`,{className:`assistant-entry-footer`,children:(0,g.jsxs)(ie,{className:`assistant-launcher`,disabled:z,onClick:()=>void pe(),"data-testid":`assistant-launcher`,children:[(0,g.jsx)(a,{}),(0,g.jsx)(`strong`,{className:`assistant-launcher-copy`,children:`与提百事聊聊`})]})})]}),(0,g.jsxs)(`section`,{className:`assistant-thread`,"aria-label":k?`提百事对话`:`消息内容`,children:[(0,g.jsx)(`header`,{className:`assistant-thread-heading`,children:(0,g.jsx)(`h2`,{children:k?le?.title??(O?`无标题`:`提百事`):D===`debug`?`运行提醒`:`项目消息`})}),(0,g.jsxs)(`div`,{className:`assistant-transcript`,role:`log`,"aria-live":`polite`,"data-testid":`assistant-transcript`,onScroll:e=>{let t=e.currentTarget;G.current=t.scrollHeight-t.scrollTop-t.clientHeight<100},children:[k?he?(0,g.jsxs)(`div`,{className:`assistant-empty`,"data-testid":`assistant-loading`,children:[(0,g.jsx)(a,{}),(0,g.jsx)(`p`,{role:`status`,children:H||E.error?`暂时无法加载对话`:`加载中…`})]}):M?.runs.length?M.runs.map(e=>(0,g.jsxs)(`div`,{className:`assistant-turn`,children:[(0,g.jsx)(y,{mine:!0,author:o,time:e.createdAt,children:(0,g.jsx)(`div`,{className:`markdown`,children:(0,g.jsx)(m,{children:e.text})})}),(0,g.jsxs)(y,{author:`提百事`,time:e.createdAt,children:[e.answer&&(0,g.jsx)(`div`,{className:`markdown`,children:(0,g.jsx)(m,{children:e.answer})}),[`queued`,`running`].includes(e.state)&&(0,g.jsxs)(`span`,{className:`assistant-typing`,"aria-label":`正在回答`,children:[(0,g.jsx)(`i`,{}),(0,g.jsx)(`i`,{}),(0,g.jsx)(`i`,{})]}),e.state===`failed`&&(0,g.jsx)(s,{error:!0,children:e.error||`本次回答未完成。`})]})]},e.id)):(0,g.jsxs)(`div`,{className:`assistant-empty`,children:[(0,g.jsx)(a,{}),(0,g.jsx)(`p`,{children:`有什么项目问题想聊聊?`})]}):!T.data&&T.loading?(0,g.jsx)(`div`,{className:`assistant-empty`,children:(0,g.jsx)(`p`,{role:`status`,children:`加载中…`})}):D===`debug`?J.map(e=>(0,g.jsx)(x,{item:e,me:o,onReply:!e.archivedAt&&!e.recipients.find(e=>e.userId===o)?.reply?()=>{N(e.id),I(``),V(``)}:void 0},e.id)):Y&&(0,g.jsx)(x,{item:Y,me:o}),(0,g.jsx)(`div`,{ref:W})]}),(0,g.jsx)(s,{error:!0,children:fe||(k?H:``)}),$&&!k&&(0,g.jsx)(`p`,{className:`assistant-reply-hint`,id:`assistant-reply-hint`,children:D===`debug`?`回复运行提醒:${v(Y.body).split(`诊断码`)[0]}。发送后将作为新的后台输入。`:`回应「提百事」的问题。注意:只能发送一条消息,发送后「提百事」会恢复工作。`}),$?(0,g.jsxs)(`form`,{className:`assistant-composer`,onSubmit:e=>void me(e),children:[(0,g.jsx)(`textarea`,{value:F,onChange:e=>I(e.target.value),"aria-label":k?`向提百事提问`:`回复消息`,"aria-describedby":k?void 0:`assistant-reply-hint`,placeholder:k?`问问提百事…`:`回复…`,rows:2,onKeyDown:e=>{e.key===`Enter`&&!e.shiftKey&&!e.nativeEvent.isComposing&&(e.preventDefault(),e.currentTarget.form?.requestSubmit())}}),(0,g.jsx)(`button`,{className:`primary assistant-send`,type:`submit`,disabled:L||he||!F.trim(),"aria-label":`发送`,children:(0,g.jsx)(c,{name:`send`})})]}):D!==`debug`&&Y&&(0,g.jsxs)(`div`,{className:`assistant-archived`,children:[(0,g.jsx)(c,{name:`archive`}),`已归档`]})]})]})}function x({item:e,me:t,onReply:n}){return(0,g.jsxs)(`div`,{"data-testid":`outbox-thread-${e.id}`,children:[(0,g.jsxs)(y,{author:e.box===`debug`?`运行提醒`:`提百事`,time:e.createdAt,children:[(0,g.jsx)(`div`,{className:`markdown`,children:(0,g.jsx)(m,{children:e.body})}),n&&(0,g.jsx)(`button`,{type:`button`,className:`assistant-debug-reply`,onClick:n,children:`回复`})]}),[...e.recipients].filter(e=>e.reply).sort((e,t)=>e.reply.createdAt.localeCompare(t.reply.createdAt)).map(e=>(0,g.jsx)(y,{mine:e.userId===t,author:e.userId,time:e.reply.createdAt,children:(0,g.jsx)(`div`,{className:`markdown`,children:(0,g.jsx)(m,{children:e.reply.body})})},e.userId))]})}export{b as Assistant};
@@ -0,0 +1 @@
1
+ main#main[data-page=assistant]{height:100dvh;padding:0}.assistant-layout{grid-template-columns:var(--pane-width) minmax(0, 1fr);height:100%;min-height:0}.assistant-list{border-right:1px solid var(--color-line);background:var(--color-surface);flex-direction:column;min-height:0;display:flex}.assistant-list-heading,.assistant-thread-heading{border-bottom:1px solid var(--color-line);flex:0 0 76px;justify-content:space-between;align-items:center;padding:0 24px;display:flex}.assistant-list-heading h1{margin:0;font-size:20px}.assistant-thread-heading h2{text-overflow:ellipsis;white-space:nowrap;margin:0;font-size:17px;overflow:hidden}.assistant-message-scroll{flex:1;min-height:0;overflow-y:auto}.assistant-list-status{color:var(--color-muted);font-size:var(--text-sm);margin:20px 24px}.assistant-message-item{border:0;border-bottom:1px solid var(--color-line);text-align:left;width:100%;color:var(--color-text);background:0 0;border-radius:0;align-items:center;gap:12px;padding:20px 24px;display:flex}.assistant-message-item:hover{background:var(--color-hover)}.assistant-message-item.selected{background:var(--color-soft)}.assistant-message-item.archived{color:var(--color-muted)}.assistant-message-item.archived strong{font-weight:400}.assistant-entry-footer{border-top:1px solid var(--color-line);background:var(--color-surface);flex-shrink:0}.assistant-launcher{width:100%;min-height:76px;color:var(--color-text);text-align:center;background:0 0;border:0;border-radius:0;justify-content:center;align-items:center;margin:0;padding:0 24px;display:flex}.assistant-launcher:hover{background:var(--color-hover)}.assistant-launcher:focus-visible{outline-offset:-4px}.assistant-launcher .selection-content{padding:12px 18px}.assistant-launcher .mascot{flex-shrink:0;width:31.5px;height:47.7px}.assistant-launcher-copy{white-space:nowrap;font-size:16px}.assistant-message-copy{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.assistant-message-copy>.selection-content{width:fit-content;max-width:100%;color:var(--color-text);margin:-5px -8px;padding:5px 8px;display:block;overflow:visible}.assistant-message-item.archived .selection-content{color:inherit}.assistant-message-copy .selection-inner{min-width:0;display:block}.assistant-message-copy .selection-inner strong{display:block}.assistant-message-copy strong,.assistant-message-copy>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.assistant-message-copy strong{font-size:var(--text-base);font-weight:600}.assistant-message-copy>span,.assistant-message-copy time{font-size:var(--text-xs);color:var(--color-muted)}.assistant-message-dot{border-radius:50%;flex:0 0 7px;width:7px;height:7px}.assistant-message-dot.unread{background:var(--brand-orange)}.assistant-thread{background:var(--color-canvas);flex-direction:column;min-width:0;min-height:0;display:flex}.assistant-transcript{flex:1;min-height:0;padding:28px clamp(20px,4vw,64px);overflow-y:auto}.assistant-bubble-row{margin:0 0 24px;display:flex}.assistant-bubble-row.mine{justify-content:flex-end}.assistant-bubble{border-radius:4px var(--radius-lg) var(--radius-lg);background:var(--color-soft);overflow-wrap:anywhere;min-width:100px;max-width:min(88%,800px);padding:14px 18px}.assistant-bubble-row.mine .assistant-bubble{background:var(--color-accent-soft);border-radius:var(--radius-lg) 4px var(--radius-lg) var(--radius-lg)}.assistant-bubble-meta{font-size:var(--text-xs);color:var(--color-muted);align-items:baseline;gap:16px;margin-bottom:8px;display:flex}.assistant-debug-reply{font-size:var(--text-xs);background:var(--color-surface);margin-top:12px;padding:4px 10px}.assistant-bubble .markdown>:first-child{margin-top:0}.assistant-bubble .markdown>:last-child{margin-bottom:0}.assistant-bubble .markdown{font-size:var(--text-base);line-height:1.75}.assistant-bubble pre{max-width:100%;overflow-x:auto}.assistant-composer{background:var(--color-surface);border:1px solid var(--color-line);border-radius:var(--radius-lg);align-items:flex-end;gap:12px;margin:0 28px 28px;padding:12px;display:flex}.assistant-reply-hint{font-size:var(--text-xs);color:var(--color-muted);margin:0 28px 10px;line-height:1.7}.assistant-composer:focus-within{border-color:var(--color-muted)}.assistant-composer textarea{box-shadow:none;resize:vertical;background:0 0;border:0;flex:1;min-height:56px;max-height:220px;padding:8px}.assistant-composer textarea:focus{box-shadow:none;outline:none}.assistant-send{flex:0 0 40px;place-items:center;width:40px;height:40px;padding:0;display:grid}.assistant-empty{height:100%;min-height:240px;color:var(--color-muted);flex-direction:column;justify-content:center;align-items:center;gap:20px;display:flex}.assistant-empty .mascot{width:96px;height:144px}.assistant-empty p{margin:0}.assistant-archived{color:var(--color-muted);font-size:var(--text-sm);justify-content:center;align-items:center;gap:8px;padding:24px;display:flex}.assistant-thread>.notice{margin:0 28px 12px}.assistant-typing{gap:5px;padding:8px 0;display:inline-flex}.assistant-typing i{background:var(--color-muted);border-radius:50%;width:5px;height:5px;animation:1.4s infinite assistant-pulse}.assistant-typing i:nth-child(2){animation-delay:.15s}.assistant-typing i:nth-child(3){animation-delay:.3s}@keyframes assistant-pulse{0%,70%,to{opacity:.35}35%{opacity:1}}@media (prefers-reduced-motion:reduce){.assistant-typing i{animation:none}}@media (width<=900px){.assistant-layout{grid-template-columns:240px minmax(0,1fr)}.assistant-message-item{padding:16px}.assistant-composer{margin:0 16px 16px}.assistant-reply-hint{margin:0 16px 10px}.assistant-launcher{padding:0 12px}.assistant-bubble{max-width:96%}}@media (width<=600px){.assistant-layout{grid-template-columns:150px minmax(0,1fr)}.assistant-list-heading,.assistant-thread-heading{padding:0 12px}.assistant-launcher{padding:0 4px}.assistant-launcher .selection-content{padding:12px 6px}.assistant-launcher .selection-inner{gap:4px}.assistant-launcher-copy{font-size:12px}.assistant-transcript{padding:16px 10px}.assistant-composer{gap:4px;margin:0 8px 12px;padding:6px}.assistant-reply-hint{margin:0 8px 10px}}
@@ -0,0 +1 @@
1
+ import{D as e,S as t,b as n,d as r,h as i,l as a,lt as o,m as s,p as c,r as l,s as u,u as d,ut as f,v as p,w as m,y as h}from"./project-context--9YVXMif.js";import{d as g,f as _,p as v}from"./index-COCTfq_q.js";import{t as y}from"./project-links-XbF78Bal.js";import"./file-links-BV_YvnFy.js";var b=o(),x=m(),S={repository:`同步仓库`,import:`导入工作区`,tasks:`整理任务`,sessions:`整理对话库`,project:`分析项目`,chat:`回答问题`};function C(e,t){if(t)return{state:`failed`,text:`状态读取失败`};if(!e)return{state:`idle`,text:`读取状态…`};if(e.worker===`offline`)return{state:`failed`,text:`后台离线`};if(e.worker===`unknown`)return{state:`idle`,text:`等待后台启动`};if(e.activities.some(e=>e.state===`failed`))return{state:`failed`,text:`处理异常`};let n=e.activities.filter(e=>e.state===`running`),r=n.reduce((e,t)=>e+t.count,0);return r?{state:`running`,text:r===1?`正在${S[n[0].kind]}`:`正在处理 ${r} 项`}:e.modelAvailable?e.activities.some(e=>e.state===`queued`)?{state:`queued`,text:`等待处理`}:{state:`idle`,text:`空闲`}:{state:`idle`,text:`待配置模型`}}function w(){let{request:e}=l(),t=(0,b.useCallback)(t=>e(`project/activity`,g,{signal:t}),[e]),r=n(t,3e3),i=C(r.data,r.error);return(0,x.jsxs)(`span`,{className:`project-activity`,"data-state":i.state,"data-testid":`project-activity`,role:`status`,"aria-live":`polite`,children:[(0,x.jsx)(`span`,{className:`project-activity-dot`,"aria-hidden":`true`}),i.text]})}var T=[{status:`planned`,label:`计划`},{status:`in_progress`,label:`进行中`},{status:`in_review`,label:`待验收`},{status:`completed`,label:`已完成`},{status:null,label:`未明确`}];function E({members:e,project:r}){let{request:a,projectId:o}=l(),c=(0,b.useCallback)(e=>a(`tasks`,_,{signal:e}),[a]),d=n(c,3e3,t(o,`tasks`)),[f,p]=(0,b.useState)(),m=d.data,h=new Map(e.map(e=>[e.id,e.displayName||e.id]));return(0,x.jsxs)(`div`,{className:`task-board`,children:[(0,x.jsx)(i,{title:r?.projectName.trim()?`${r.projectName.trim()}|工作看板`:`工作看板`,description:r?.description.trim()||``,children:(0,x.jsx)(w,{})}),(0,x.jsx)(s,{error:!0,children:d.error}),(0,x.jsx)(`section`,{className:`task-board-lanes`,"aria-label":`任务进度`,"data-testid":`progress-board`,children:T.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)(u,{children:m?t.length:`—`})]}),(0,x.jsx)(`div`,{className:`task-lane-content`,children:t.length?t.map(e=>(0,x.jsx)(D,{task:e,names:h,onEdit:()=>p(e),onSaved:()=>void d.reload()},e.id)):(0,x.jsx)(`p`,{className:`task-lane-empty`,children:d.error&&!m?`读取失败`:m?`暂无任务`:`读取中…`})})]},e.label)})}),f&&(0,x.jsx)(k,{task:f,onClose:()=>p(void 0),onSaved:()=>{p(void 0),d.reload()}},f.id)]})}function D({task:t,names:n,onEdit:i,onSaved:a}){let{projectId:o,request:c}=l(),u=h(),[m,g]=(0,b.useState)(!1),[_,S]=(0,b.useState)(``);async function C(){if(!m){g(!0),S(``);try{await c(`tasks/${t.id}/correction`,v,{method:`PATCH`,signal:u,body:{needsConfirmation:!1}}),u.aborted||a()}catch(t){u.aborted||S(e(t))}finally{g(!1)}}}return(0,x.jsxs)(`details`,{className:`task-card`,"data-testid":`task-card`,"data-task-id":t.id,"data-owner":t.owner,"data-status":t.status??`unknown`,children:[(0,x.jsxs)(`summary`,{className:`task-card-heading`,"data-testid":`task-toggle`,children:[(0,x.jsx)(`h3`,{children:t.title}),(0,x.jsxs)(`span`,{className:`task-card-meta`,children:[(0,x.jsx)(`small`,{children:n.get(t.owner)??t.owner}),t.needsConfirmation&&(0,x.jsxs)(`span`,{className:`task-confirmation`,"data-testid":`task-confirmation`,children:[(0,x.jsx)(d,{name:`attention`,size:`small`}),`需要确认`]})]})]}),(0,x.jsx)(r,{icon:`edit`,className:`task-card-edit`,label:`纠正任务:${t.title}`,"data-testid":`task-correct`,disabled:m,onClick:i}),(0,x.jsxs)(`div`,{className:`task-card-details`,children:[t.corrected&&!t.needsConfirmation&&(0,x.jsx)(`small`,{children:`已确认`}),t.needsReview&&(0,x.jsx)(`p`,{children:`本次整理未能明确对应,请核对来源。`}),(0,x.jsx)(`h4`,{children:`目标`}),(0,x.jsx)(`p`,{children:t.goal?(0,x.jsx)(O,{value:t.goal,names:n}):`目标未明确`}),(0,x.jsx)(`h4`,{children:`当前进展`}),(0,x.jsx)(`p`,{className:`task-progress`,children:(0,x.jsx)(O,{value:t.progress,names:n})}),(0,x.jsx)(`div`,{className:`task-card-sources`,children:t.sources.map(e=>(0,x.jsxs)(`a`,{"data-testid":`task-source`,href:f(y(o,t.owner,e.path)),children:[(0,x.jsx)(d,{name:`file`,size:`small`}),(0,x.jsx)(`span`,{children:e.path})]},e.path))}),t.syncedAt&&(0,x.jsxs)(`small`,{children:[`资料同步于 `,(0,x.jsx)(p,{value:t.syncedAt})]}),t.needsConfirmation&&(0,x.jsxs)(`div`,{className:`task-card-confirm`,children:[(0,x.jsx)(s,{error:!0,children:_}),(0,x.jsx)(`button`,{className:`primary`,"data-testid":`task-confirm-save`,disabled:m,onClick:()=>void C(),children:m?`正在保存…`:`保存`})]})]})]})}function O({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 k({task:t,onClose:n,onSaved:r}){let{request:i}=l(),o=h(),[u,d]=(0,b.useState)(t.title),[f,p]=(0,b.useState)(t.status??``),[m,g]=(0,b.useState)(t.goal??``),[_,y]=(0,b.useState)(t.progress),[S,C]=(0,b.useState)(!1),[w,E]=(0,b.useState)(``),D={...u===t.title?{}:{title:u},...f===(t.status??``)?{}:{status:f||null},...m===(t.goal??``)?{}:{goal:m||null},..._===t.progress?{}:{progress:_},...t.needsConfirmation?{needsConfirmation:!1}:{}};async function O(n=!1){if(!(S||!n&&!Object.keys(D).length)){C(!0),E(``);try{await i(`tasks/${t.id}/correction`,v,{method:n?`DELETE`:`PATCH`,signal:o,...n?{}:{body:D}}),o.aborted||r()}catch(t){o.aborted||E(e(t))}finally{C(!1)}}}return(0,x.jsx)(c,{id:`task-correction`,title:`纠正任务`,onClose:n,children:(0,x.jsxs)(`form`,{className:`stack`,"aria-busy":S,onSubmit:e=>{e.preventDefault(),O()},children:[(0,x.jsx)(a,{id:`task-title`,label:`标题`,children:(0,x.jsx)(`input`,{value:u,required:!0,onChange:e=>d(e.target.value)})}),(0,x.jsxs)(`fieldset`,{className:`task-status-field`,disabled:S,children:[(0,x.jsx)(`legend`,{children:`状态`}),(0,x.jsx)(`div`,{className:`task-status-options`,"data-testid":`task-status`,children:T.filter(e=>e.status!==null).map(e=>(0,x.jsxs)(`label`,{className:`task-status-choice`,children:[(0,x.jsx)(`input`,{type:`radio`,name:`task-status`,value:e.status,checked:f===e.status,onChange:()=>p(e.status)}),(0,x.jsx)(`span`,{children:e.label})]},e.status))})]}),(0,x.jsx)(a,{id:`task-goal`,label:`目标`,children:(0,x.jsx)(`textarea`,{value:m,onChange:e=>g(e.target.value)})}),(0,x.jsx)(a,{id:`task-progress`,label:`当前进展`,children:(0,x.jsx)(`textarea`,{value:_,onChange:e=>y(e.target.value)})}),(0,x.jsx)(s,{error:!0,children:w}),(0,x.jsxs)(`div`,{className:`row wrap`,children:[(0,x.jsx)(`button`,{type:`submit`,className:`primary`,disabled:S||!Object.keys(D).length,"data-testid":`task-correction-save`,children:`保存`}),t.corrected&&(0,x.jsx)(`button`,{type:`button`,disabled:S,"data-testid":`task-correction-reset`,onClick:()=>void O(!0),children:`恢复自动结果`})]})]})})}function A({members:e,config:t}){return(0,x.jsx)(E,{members:e,project:t})}export{A as Dashboard};
@@ -0,0 +1 @@
1
+ .task-board{min-width:0}.task-board .page-header{align-items:flex-start;margin-bottom:40px}.task-board .page-header>div{min-width:0}.task-board .page-header h1,.task-board .page-header p{overflow-wrap:anywhere}.task-board .page-header .project-activity{margin-top:8px}.project-activity{font-size:var(--text-xs);color:var(--color-muted);flex-shrink:0;align-items:center;gap:7px;font-weight:400;display:inline-flex}.project-activity-dot{background:currentColor;border-radius:50%;width:6px;height:6px}.project-activity[data-state=running],.project-activity[data-state=queued]{color:var(--brand-orange)}.project-activity[data-state=failed]{color:var(--color-danger)}.task-board-lanes{grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));align-items:start;gap:24px;margin-bottom:32px;display:grid}.task-lane{min-width:0}.task-lane h2{font-size:var(--text-base);border-bottom:1px solid var(--color-line);align-items:center;gap:10px;padding:0 0 16px;font-weight:600;display:flex}.task-lane .count{margin-left:auto}.task-lane-content{flex-direction:column;gap:12px;padding-top:16px;display:flex}.lane-dot{border:1.5px solid var(--color-muted);border-radius:50%;width:8px;height:8px}.task-lane[data-stage=in_progress] .lane-dot{border-color:var(--brand-orange);background:var(--brand-orange)}.task-lane[data-stage=in_review] .lane-dot{border-color:var(--brand-black);background:var(--brand-black)}.task-lane[data-stage=completed] .lane-dot{border-color:var(--color-muted);background:var(--color-muted)}.task-lane-empty{font-size:var(--text-xs);color:var(--color-muted);padding:44px 0}.task-card{border:1px solid var(--color-line);border-radius:var(--radius-md);background:var(--color-surface);overflow-wrap:anywhere;min-width:0;position:relative}.task-card>.task-card-heading{border-radius:inherit;padding:16px;list-style:none;position:relative}.task-card-heading::-webkit-details-marker{display:none}.task-card-heading:hover{background:var(--color-soft)}.task-card[open]>.task-card-heading{padding-right:48px}.task-card-meta{flex-wrap:wrap;align-items:center;gap:8px;margin-top:8px;display:flex}.task-confirmation{border-radius:var(--radius-sm);background:var(--color-accent-soft);color:var(--brand-orange);font-size:calc(var(--text-xs) - 1px);align-items:center;gap:3px;padding:0 4px;font-weight:400;line-height:18px;display:inline-flex}.task-confirmation .icon{width:12px;height:12px}.task-card-confirm{flex-direction:column;align-items:flex-end;gap:8px;margin-top:16px;display:flex}.task-card-confirm button{min-height:32px;font-size:var(--text-xs);padding:4px 12px}.task-status-field legend{font-size:var(--text-sm);margin-bottom:8px;font-weight:600}.task-status-options{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;display:grid}.task-status-choice{cursor:pointer;position:relative}.task-status-choice input{opacity:0;width:100%;height:100%;min-height:0;cursor:inherit;position:absolute;inset:0}.task-status-choice span{border:1px solid var(--color-line);border-radius:var(--radius-md);background:var(--color-surface);min-height:36px;font-size:var(--text-sm);justify-content:center;align-items:center;font-weight:550;display:flex}.task-status-choice:hover span{background:var(--color-soft)}.task-status-choice input:checked+span{border-color:var(--brand-black);background:var(--brand-black);color:var(--color-on-dark)}.task-status-choice input:focus-visible+span{outline:2px solid var(--brand-orange);outline-offset:3px}.task-status-choice input:disabled+span{opacity:.45;cursor:default}.task-card-heading h3{font-size:var(--text-base);font-weight:650;line-height:1.55}.task-card-edit{opacity:0;pointer-events:none;position:absolute;top:10px;right:8px}.task-card[open]:hover>.task-card-edit{opacity:1;pointer-events:auto}.task-card[open]:has(:focus-visible)>.task-card-edit{opacity:1;pointer-events:auto}.task-progress{font-size:var(--text-sm);white-space:pre-wrap;line-height:1.85}.task-card small{color:var(--color-muted);font-size:var(--text-xs)}.task-card-details{padding:0 16px 16px}.task-card-details h4{font-size:var(--text-xs);margin:16px 0 4px;font-weight:600}.task-card-details p{font-size:var(--text-sm);margin:0}.task-card-sources{flex-direction:column;align-items:flex-start;gap:8px;margin:16px 0;display:flex}.task-card-sources a{text-align:left;overflow-wrap:anywhere;max-width:100%;font-size:var(--text-xs);min-height:28px;color:var(--color-muted);justify-content:flex-start;gap:8px;padding:4px 0;text-decoration:none;display:inline-flex}.task-card-sources a:hover{color:var(--color-link);text-decoration:underline}.task-mention{border-radius:var(--radius-sm);background:var(--color-accent-soft);color:inherit;padding:0 2px}
@@ -0,0 +1 @@
1
+ import{J as e,M as t,S as n,T as r,_ as i,b as a,c as o,d as s,lt as c,m as l,r as u,u as d,ut as f,w as p}from"./project-context--9YVXMif.js";import{c as m,o as h,s as g}from"./index-COCTfq_q.js";import{t as _}from"./Markdown-B3gyVKQQ.js";import{t as v}from"./project-links-XbF78Bal.js";import{t as y}from"./file-links-BV_YvnFy.js";import{n as b,t as x}from"./WorkspaceTree-1wC3k-B-.js";var S=c(),C=p();function w(e,t){let n=t.split(`/`).slice(0,-1);return n.map((t,r)=>b(e,n.slice(0,r+1).join(`/`))).concat(b(e,``))}function T({members:n,me:i,onRefresh:c}){let{api:d,projectId:p}=u(),[_,T]=(0,S.useState)(()=>{let t=new URLSearchParams(location.search),r=t.get(`owner`),i=e.safeParse(t.get(`path`));return r&&(r===`_project`||n.some(e=>e.id===r))&&i.success&&!g(i.data)?{owner:r,path:i.data}:void 0}),[D,O]=(0,S.useState)(()=>new Set(_?w(_.owner,_.path):[b(i,``)])),[k,A]=(0,S.useState)(()=>_?{..._,kind:`file`}:void 0),[j,M]=(0,S.useState)(0),[N,P]=(0,S.useState)(()=>{let e=new URLSearchParams(location.search);return!_&&(e.has(`owner`)||e.has(`path`))?`文件链接不完整或目标已不可用。`:``}),F=(0,S.useCallback)(async e=>{if(_)try{return{owner:_.owner,...await d.file(_.owner,_.path,void 0,e)}}catch(e){throw e instanceof r&&e.code===`FILE_NOT_FOUND`?Error(`找不到这个文件,可能已被删除或移动。`):e}},[_?.owner,_?.path,j]),I=a(F),L=I.data?.owner===_?.owner&&I.data?.file.path===_?.path?I.data:void 0;function R(e,t){let n=t.split(`/`).filter(Boolean);O(t=>{let r=new Set(t);for(let t=0;t<=n.length;t++)r.add(b(e,n.slice(0,t).join(`/`)));return r})}function z(e,t){P(``),R(e,t.split(`/`).slice(0,-1).join(`/`)),T({owner:e,path:t}),A({owner:e,path:t,kind:`file`}),history.replaceState(null,``,f(v(p,e,t)))}function B(e){if(!_)return!1;let t=y(_.owner===`_project`?_.path:`${_.owner}/${_.path}`,e);if(!t)return!/^[a-z][a-z0-9+.-]*:/i.test(e)&&!e.startsWith(`//`)&&(P(`该链接超出工作区,请在原仓库查看。`),!0);if(g(t.path))return P(`配置文件不在文件浏览中显示。`),!0;let[r,...i]=t.path.split(`/`),a=n.some(e=>e.id===r),o=a?r:h,s=a?i.join(`/`):t.path;return P(``),t.directory?(R(o,s),A({owner:o,path:s,kind:`directory`})):o===_.owner&&s===_.path?t.anchor&&[...document.querySelectorAll(`.markdown h1,.markdown h2,.markdown h3,.markdown h4`)].find(e=>e.textContent?.trim().toLowerCase().replace(/\s+/g,`-`)===t.anchor.toLowerCase())?.scrollIntoView({block:`center`}):z(o,s),!0}return(0,C.jsxs)(`div`,{className:`split-layout file-explorer`,"data-testid":`file-browser`,children:[(0,C.jsxs)(`aside`,{className:`file-tree-pane`,"aria-label":`文件资源管理器`,children:[(0,C.jsx)(m,{title:`文件`,icon:`files`,children:(0,C.jsx)(s,{icon:`refresh`,label:`刷新文件`,"data-testid":`files-refresh`,onClick:()=>{P(``),M(e=>e+1),c()}})}),(0,C.jsx)(x,{members:n,expanded:D,selected:k,revealed:!I.loading&&!I.error&&L?{owner:L.owner,path:L.file.path,file:L.file}:void 0,onSelect:z,onToggle:(e,t)=>{A({owner:e,path:t,kind:`directory`}),O(n=>{let r=new Set(n),i=b(e,t);return r.has(i)?r.delete(i):r.add(i),r})}},j)]}),(0,C.jsx)(`section`,{className:`file-detail`,"data-testid":`file-preview`,"data-owner-id":_?.owner,"data-file-path":_?.path,"aria-label":t.filePreview,children:_?(0,C.jsxs)(C.Fragment,{children:[(0,C.jsx)(l,{error:!0,children:N||!I.loading&&I.error}),I.error&&(0,C.jsx)(`button`,{className:`file-entry-retry`,"data-testid":`files-retry`,onClick:()=>void I.reload(),children:t.retry}),I.loading?(0,C.jsx)(`p`,{className:`preview-body`,role:`status`,children:t.loading}):!I.error&&L&&(0,C.jsx)(E,{owner:L.owner,file:L.file,onLink:B},`${L.owner}:${L.file.path}:${L.file.sha256}`)]}):N?(0,C.jsx)(l,{error:!0,children:N}):(0,C.jsx)(o,{mascot:!0,title:`从一份资料开始`,description:t.pickFile})})]})}function E({owner:e,file:r,onLink:o}){let{readTextBlob:c,blobUrl:f,projectId:p}=u(),[m,h]=(0,S.useState)(!1),g=r.size>524288,v=/\.(md|markdown)$/i.test(r.path),y=(0,S.useCallback)(e=>g?Promise.resolve(void 0):c(r,e),[r.sha256,r.size,g]),b=a(y,!1,n(p,`file-body`,r.sha256));return(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`header`,{className:`file-detail-header`,children:[(0,C.jsxs)(`span`,{className:`file-detail-path`,title:`${e===`_project`?`workspaces`:e}/${r.path}`,children:[(0,C.jsxs)(`span`,{children:[e===`_project`?`workspaces`:e,` / `]}),r.path]}),(0,C.jsxs)(i,{label:`文件操作`,children:[v&&(0,C.jsx)(s,{icon:m?`preview`:`code`,label:m?`切换为预览`:`切换为原文`,"data-testid":`file-toggle-raw`,"aria-pressed":m,onClick:()=>h(!m)}),(0,C.jsx)(`a`,{className:`icon-button`,"data-testid":`file-download`,"aria-label":t.download,title:t.download,href:f(r.sha256),download:r.path.split(`/`).at(-1),children:(0,C.jsx)(d,{name:`download`})})]})]}),g?(0,C.jsx)(`div`,{className:`empty`,children:(0,C.jsx)(`p`,{children:t.previewLarge})}):b.error?(0,C.jsxs)(`div`,{className:`preview-body`,children:[(0,C.jsx)(l,{error:!0,children:b.error}),(0,C.jsx)(`button`,{"data-testid":`file-preview-retry`,onClick:()=>void b.reload(),children:t.retry})]}):b.loading?(0,C.jsx)(`p`,{className:`preview-body`,role:`status`,children:t.loading}):(0,C.jsx)(`div`,{className:`preview-body`,children:v&&!m?(0,C.jsx)(`article`,{className:`markdown`,"data-testid":`file-content`,"data-format":`markdown`,children:(0,C.jsx)(_,{onLink:o,children:b.data??``})}):(0,C.jsx)(`pre`,{className:`raw`,"data-testid":`file-content`,"data-format":`text`,children:b.data})})]})}export{T as Files};