@teammy/server 0.0.4 → 0.0.5
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 +3 -3
- package/dist/main.js +28 -1
- package/dist/web/assets/Dashboard--f1vaGx8.js +1 -0
- package/dist/web/assets/Files-Bc00wOfT.js +1 -0
- package/dist/web/assets/{Markdown-CcCzS4DP.js → Markdown-BrmcIotb.js} +4 -4
- package/dist/web/assets/Sessions-Bmlb8Xay.js +1 -0
- package/dist/web/assets/index-CgsCXCcm.js +64 -0
- package/dist/web/assets/index-uXzN7fKr.css +1 -0
- package/dist/web/index.html +3 -3
- package/package.json +1 -1
- package/dist/web/assets/Dashboard-CfMDmukc.js +0 -1
- package/dist/web/assets/Files-DvK_N3lO.js +0 -1
- package/dist/web/assets/Sessions-DTLr1cTK.js +0 -1
- package/dist/web/assets/file-links-BAMah1K6.js +0 -1
- package/dist/web/assets/index-CeGPJ_an.css +0 -1
- package/dist/web/assets/index-l1r3Ekhs.js +0 -64
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.5
|
|
7
7
|
teammy-server --help
|
|
8
8
|
teammy-server --version
|
|
9
9
|
teammy-server --host 127.0.0.1 --port 4317
|
|
@@ -27,8 +27,8 @@ 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 服务使用同一数据目录。浏览器“设置 → 模型接入”维护私有配置,成员从“工作看板 → 资料分析”发起任务。执行并发为 1,读取固定资料快照,仅有清单、读取和搜索工具;不提供 Shell 或任意代码执行。
|
|
31
31
|
|
|
32
|
-
0.0.
|
|
32
|
+
0.0.5 安装包随附全部服务端运行依赖,包含 Pi 1.4.0 及修复后的 undici 8.12.2,普通 npm 安装即可使用,无需配置 overrides。服务端包包含 Web 静态资源;客户端不安装 Pi 或 Web 依赖。
|
|
33
33
|
|
|
34
34
|
备份必须包含 SQLite、blobs 和 `ai-secret.key`。API 与 Worker 停止后备份整个目录。
|
package/dist/main.js
CHANGED
|
@@ -2216,7 +2216,15 @@ function registerWeb(server, root) {
|
|
|
2216
2216
|
dotfiles: "deny"
|
|
2217
2217
|
});
|
|
2218
2218
|
server.get("/ai", (_request, reply) => reply.redirect("/"));
|
|
2219
|
-
for (const path of [
|
|
2219
|
+
for (const path of [
|
|
2220
|
+
"/",
|
|
2221
|
+
"/files",
|
|
2222
|
+
"/members",
|
|
2223
|
+
"/settings",
|
|
2224
|
+
"/dashboard",
|
|
2225
|
+
"/sessions",
|
|
2226
|
+
"/account"
|
|
2227
|
+
])
|
|
2220
2228
|
server.get(
|
|
2221
2229
|
path,
|
|
2222
2230
|
(_request, reply) => reply.header("cache-control", "no-cache").header(
|
|
@@ -2497,6 +2505,12 @@ var SessionListSchema = z11.object({
|
|
|
2497
2505
|
sessions: z11.array(SessionInfoSchema),
|
|
2498
2506
|
nextCursor: RevisionSchema.nullable()
|
|
2499
2507
|
});
|
|
2508
|
+
var SessionStatsSchema = z11.object({
|
|
2509
|
+
totalSessions: z11.number().int().nonnegative(),
|
|
2510
|
+
totalMessages: z11.number().int().nonnegative(),
|
|
2511
|
+
memberCount: z11.number().int().nonnegative(),
|
|
2512
|
+
updatedAt: z11.string().nullable()
|
|
2513
|
+
});
|
|
2500
2514
|
var SessionSearchSchema = z11.object({
|
|
2501
2515
|
matches: z11.array(
|
|
2502
2516
|
z11.object({
|
|
@@ -2657,6 +2671,15 @@ var SessionRepository = class {
|
|
|
2657
2671
|
);
|
|
2658
2672
|
return SessionInfoSchema.parse(JSON.parse(String(row.info_json)));
|
|
2659
2673
|
}
|
|
2674
|
+
stats(identity) {
|
|
2675
|
+
return SessionStatsSchema.parse(
|
|
2676
|
+
this.hub.db.prepare(
|
|
2677
|
+
`SELECT count(*) AS totalSessions, coalesce(sum(message_count), 0) AS totalMessages,
|
|
2678
|
+
count(DISTINCT owner) AS memberCount, max(updated_at) AS updatedAt
|
|
2679
|
+
FROM sessions WHERE workspace_id=?`
|
|
2680
|
+
).get(identity.workspaceId)
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2660
2683
|
list(identity, after, limit) {
|
|
2661
2684
|
const rows = this.hub.db.prepare(
|
|
2662
2685
|
`SELECT s.id, r.info_json FROM sessions s JOIN session_revisions r USING(workspace_id, owner, session_key, revision)
|
|
@@ -2724,6 +2747,10 @@ function registerSessionRoutes(server, store) {
|
|
|
2724
2747
|
const q = pagination.parse(request.query);
|
|
2725
2748
|
return sessions.list(authorizeRead(store, request), q.after, q.limit);
|
|
2726
2749
|
});
|
|
2750
|
+
server.get(
|
|
2751
|
+
"/api/v1/sessions/stats",
|
|
2752
|
+
async (request) => sessions.stats(authorizeRead(store, request))
|
|
2753
|
+
);
|
|
2727
2754
|
server.get("/api/v1/sessions/search", async (request) => {
|
|
2728
2755
|
const q = pagination.extend({
|
|
2729
2756
|
q: z12.string().min(1).max(500).refine((s) => !s.includes("\0"))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{A as e,M as t,O as n,P as r,_ as i,a,b as o,d as s,f as c,g as l,i as u,j as d,l as f,m as p,n as m,o as h,p as g,r as _,t as v,u as y,y as b}from"./index-CgsCXCcm.js";var x=r(),S=c();function C({members:e,me:t,seed:n,initialProfile:r,initialQuestion:o,onCreated:s}){let c=p(v.profiles),d=g(),[m,_]=(0,x.useState)(n??{owner:t,kind:`directory`,path:``}),[y,C]=(0,x.useState)(r??``),[w,T]=(0,x.useState)(o??``),[E,D]=(0,x.useState)(!1),[O,k]=(0,x.useState)(``),A=(0,x.useRef)({fingerprint:``,id:i()}),j=c.data?.profiles.filter(e=>e.enabled)??[],M=y||j[0]?.id||``;async function N(){if(!E){D(!0),k(``);try{let e={profileId:M,workflow:m.kind===`file`?`file-summary`:`directory-analysis`,selection:m,question:w},t=JSON.stringify(e);A.current.fingerprint!==t&&(A.current={fingerprint:t,id:i()});let n=await b(`ai/jobs`,a,{method:`POST`,body:u.parse({...e,operationId:A.current.id}),signal:d});A.current={fingerprint:``,id:i()},s(n.job)}catch(e){d.aborted||k(l(e))}finally{D(!1)}}}return(0,S.jsxs)(`form`,{className:`panel stack`,"data-testid":`ai-job-form`,onSubmit:e=>{e.preventDefault(),N()},children:[(0,S.jsx)(`h2`,{children:`新建分析`}),(0,S.jsxs)(`fieldset`,{className:`stack`,disabled:E,children:[(0,S.jsxs)(`div`,{className:`form-grid`,children:[(0,S.jsx)(h,{id:`ai-job-profile`,label:`使用模型`,children:(0,S.jsxs)(`select`,{required:!0,value:M,onChange:e=>C(e.target.value),children:[(0,S.jsx)(`option`,{value:``,disabled:!0,children:`请选择模型`}),j.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]})}),(0,S.jsx)(h,{id:`ai-job-owner`,label:`资料所属成员`,children:(0,S.jsx)(`select`,{value:m.owner,onChange:e=>_({owner:e.target.value,kind:m.kind,path:``}),children:e.map(e=>(0,S.jsxs)(`option`,{value:e.id,children:[e.displayName,` · `,e.id]},e.id))})}),(0,S.jsx)(h,{id:`ai-job-kind`,label:`分析范围`,children:(0,S.jsxs)(`select`,{value:m.kind,onChange:e=>_({...m,kind:e.target.value,path:``}),children:[(0,S.jsx)(`option`,{value:`directory`,children:`目录分析`}),(0,S.jsx)(`option`,{value:`file`,children:`单文件摘要`})]})}),(0,S.jsx)(h,{id:`ai-job-path`,label:`成员目录内的路径`,hint:m.kind===`directory`?`留空表示该成员的整个目录。`:`例如 review/notes.md`,children:(0,S.jsx)(`input`,{required:m.kind===`file`,maxLength:1024,value:m.path,onChange:e=>_({...m,path:e.target.value})})})]}),(0,S.jsx)(h,{id:`ai-job-question`,label:`关注的问题(可选)`,children:(0,S.jsx)(`textarea`,{rows:2,maxLength:2e3,value:w,onChange:e=>T(e.target.value)})}),m.revision!==void 0&&(0,S.jsxs)(f,{children:[`已选择目录版本 v`,m.revision,`。`,` `,(0,S.jsx)(`button`,{type:`button`,"data-testid":`ai-use-latest`,onClick:()=>{let{revision:e,...t}=m;_(t)},children:`改用最新版本`})]})]}),(0,S.jsx)(`p`,{className:`muted`,children:`使用提交时的资料版本,结果向团队共享。`}),!c.loading&&!j.length&&(0,S.jsx)(f,{children:`请管理员先在“设置 → 模型接入”中添加并启用模型。`}),(0,S.jsx)(f,{error:!0,children:c.error||O}),(0,S.jsx)(`button`,{className:`primary`,"data-testid":`ai-job-submit`,disabled:E||!M,children:E?`正在提交…`:`开始分析`})]})}var w=d({path:t(),startLine:e(),endLine:e(),totalLines:e(),content:t(),hasMore:n()});function T({id:e,session:t,onChanged:n,onUpdated:r,onLatest:o}){let c=(0,x.useRef)(0),u=g(),[d,h]=(0,x.useState)([]),y=(0,x.useCallback)(async t=>{let[n,r]=await Promise.all([v.detail(e,t),v.events(e,c.current,t)]);return t.aborted||(c.current=r.nextCursor,h(e=>[...e,...r.events].slice(-200))),n},[e]),[C,w]=(0,x.useState)(3e3),T=p(y,C),D=T.data?.job;(0,x.useEffect)(()=>{D&&r(D)},[D,r]),(0,x.useEffect)(()=>{D&&w([`queued`,`running`].includes(D.status)?3e3:3e4)},[D?.status]);let[O,k]=(0,x.useState)(!1),[A,j]=(0,x.useState)(``),[M,N]=(0,x.useState)(),P=(0,x.useRef)(i()),F=D&&(t.role===`admin`||D.requester===t.memberId);async function I(t){if(!O){k(!0),j(``);try{await b(`ai/jobs/${e}/${t}`,a,{method:`POST`,...t===`retry`?{body:{operationId:P.current}}:{},signal:u}),P.current=i(),await T.reload(),n()}catch(e){u.aborted||j(l(e))}finally{k(!1)}}}return(0,S.jsxs)(`section`,{className:`panel stack`,"data-testid":`ai-job-detail`,"data-job-id":e,"data-status":D?.status,children:[(0,S.jsxs)(`div`,{className:`section-heading`,children:[(0,S.jsx)(`h2`,{children:`分析详情`}),(0,S.jsx)(`button`,{"data-testid":`ai-detail-refresh`,disabled:T.loading,onClick:()=>void T.reload(),children:`刷新进度`})]}),(0,S.jsx)(f,{error:!0,children:T.error||A}),D&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`span`,{className:`badge`,children:m[D.status]}),` `,(0,S.jsx)(`strong`,{children:_[D.workflow]})]}),(0,S.jsxs)(`p`,{className:`ai-break`,children:[D.input.owner,`/`,D.input.path||`(全部文件)`,` · v`,D.input.revision,` ·`,` `,D.input.fileCount,` 个文件`,(0,S.jsx)(`br`,{}),D.model,` · 由 `,D.requester,` 发起 · `,(0,S.jsx)(s,{value:D.createdAt})]}),D.question&&(0,S.jsx)(`p`,{children:D.question}),D.status===`queued`&&(0,S.jsx)(`small`,{children:`等待独立 AI Worker 领取任务;若长时间未开始,请管理员检查 Worker 是否运行。`}),D.error&&(0,S.jsxs)(f,{error:!0,children:[`本次分析未完成:`,D.error]}),D.sourceChanged&&(0,S.jsx)(f,{children:`来源已有新版本,本结果仍对应提交时的资料。`}),(0,S.jsxs)(`div`,{className:`row wrap`,children:[F&&[`queued`,`running`].includes(D.status)&&(0,S.jsx)(`button`,{"data-testid":`ai-job-cancel`,disabled:O,onClick:()=>void I(`cancel`),children:`取消任务`}),F&&[`failed`,`cancelled`].includes(D.status)&&(0,S.jsx)(`button`,{"data-testid":`ai-job-retry`,disabled:O,onClick:()=>void I(`retry`),children:`重试原版本`}),(0,S.jsx)(`button`,{"data-testid":`ai-job-latest`,disabled:O,onClick:()=>o(D),children:`分析最新版本…`})]}),D.result&&(0,S.jsxs)(`article`,{className:`stack ai-result`,"data-testid":`ai-result`,children:[(0,S.jsx)(`h3`,{children:`摘要`}),(0,S.jsx)(`p`,{children:D.result.summary}),D.result.findings.length>0&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(`h3`,{children:`主要发现`}),(0,S.jsx)(`ul`,{children:D.result.findings.map((e,t)=>(0,S.jsx)(`li`,{children:e},t))})]}),D.result.uncertainties.length>0&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(`h3`,{children:`待确认事项`}),(0,S.jsx)(`ul`,{children:D.result.uncertainties.map((e,t)=>(0,S.jsx)(`li`,{children:e},t))})]}),(0,S.jsxs)(f,{children:[`已读 `,D.result.coverage.readFiles.length,` / `,D.result.coverage.totalFiles,` `,`个文件 · `,D.result.coverage.partial?`未完整覆盖全部内容`:`已完整读取所选内容`]}),D.result.coverage.limitations.map((e,t)=>(0,S.jsx)(`p`,{children:e},t)),(0,S.jsx)(`h3`,{children:`引用来源`}),(0,S.jsx)(`div`,{className:`stack`,children:D.result.citations.map((e,t)=>(0,S.jsxs)(`button`,{className:`ai-citation`,"data-testid":`ai-citation`,onClick:()=>N(e),children:[e.path,`:`,e.startLine,`–`,e.endLine]},t))}),M&&(0,S.jsx)(E,{id:e,citation:M},`${M.path}:${M.startLine}`)]}),(0,S.jsxs)(`details`,{open:D.status===`running`,children:[(0,S.jsx)(`summary`,{children:`执行进度(最近 200 条)`}),(0,S.jsx)(`ol`,{className:`ai-events`,"data-testid":`ai-events`,children:d.map(e=>(0,S.jsxs)(`li`,{children:[`第 `,e.attempt,` 次 · `,e.message]},e.seq))})]}),(0,S.jsxs)(`details`,{children:[(0,S.jsxs)(`summary`,{children:[`执行记录 · `,T.data.attempts.length,` 次`]}),T.data.attempts.map(e=>(0,S.jsxs)(`p`,{children:[`第 `,e.number,` 次 · `,e.status,` · `,(0,S.jsx)(s,{value:e.startedAt}),e.error&&` · ${e.error}`,e.usage&&` · 输入 ${e.usage.inputTokens} / 输出 ${e.usage.outputTokens} token`]},e.number))]})]})]})}function E({id:e,citation:t}){let n=(0,x.useCallback)(n=>b(`ai/jobs/${e}/source?${new URLSearchParams({path:t.path,startLine:String(t.startLine)})}`,w,{signal:n}),[e,t.path,t.startLine]),r=p(n);return(0,S.jsxs)(`div`,{"data-testid":`ai-source`,children:[(0,S.jsx)(`small`,{children:`提交时的原文 · 最多 100 行`}),(0,S.jsx)(f,{error:!0,children:r.error}),r.data&&(0,S.jsx)(`pre`,{className:`raw`,children:r.data.content})]})}function D({session:e,members:t,seed:n}){let[r,i]=(0,x.useState)(!!n),[a,o]=(0,x.useState)(``),s=(0,x.useCallback)(e=>v.jobs(a,e),[a]),c=p(s,!0),[l,u]=(0,x.useState)(``),[d,h]=(0,x.useState)(),[g,y]=(0,x.useState)(),[b,w]=(0,x.useState)(),[E,D]=(0,x.useState)(n),[O,k]=(0,x.useState)(0),A=()=>{o(``),c.reload()};return(0,S.jsxs)(`section`,{className:`board-analysis`,"data-testid":`board-analysis`,children:[(0,S.jsxs)(`div`,{className:`section-heading`,children:[(0,S.jsx)(`h2`,{children:`手动分析`}),(0,S.jsx)(`button`,{"data-testid":`board-analyze`,onClick:()=>i(e=>!e),children:r?`收起`:`新建分析`})]}),(0,S.jsxs)(`div`,{className:`ai-grid ${!l&&!r?`ai-grid-idle`:``}`,children:[(0,S.jsxs)(`div`,{className:`stack`,children:[r&&(0,S.jsx)(C,{members:t,me:e.memberId,seed:E,initialProfile:g,initialQuestion:b,onCreated:e=>{u(e.id),A()}},O),(0,S.jsxs)(`section`,{className:`panel stack`,children:[(0,S.jsxs)(`div`,{className:`section-heading`,children:[(0,S.jsx)(`h3`,{children:`分析记录`}),(0,S.jsx)(`button`,{"data-testid":`ai-list-refresh`,onClick:A,children:`刷新`})]}),(0,S.jsx)(f,{error:!0,children:c.error}),c.data?.jobs.length===0&&(0,S.jsx)(`p`,{children:`暂无分析记录`}),c.data?.jobs.map(e=>{let t=d?.id===e.id?d:e;return(0,S.jsxs)(`button`,{className:`ai-job-row ${l===t.id?`selected`:``}`,"data-testid":`ai-job-row`,"data-job-id":t.id,"data-status":t.status,"aria-pressed":l===t.id,onClick:()=>u(t.id),children:[(0,S.jsxs)(`strong`,{children:[t.input.owner,`/`,t.input.path||`全部文件`]}),(0,S.jsxs)(`small`,{children:[_[t.workflow],` · `,m[t.status],` · v`,t.input.revision]})]},t.id)}),c.data?.nextCursor&&(0,S.jsx)(`button`,{"data-testid":`ai-jobs-next`,onClick:()=>o(c.data.nextCursor),children:`下一页`})]})]}),l?(0,S.jsx)(T,{id:l,session:e,onChanged:A,onUpdated:h,onLatest:e=>{i(!0),y(e.profileId),w(e.question),D({owner:e.input.owner,kind:e.input.kind,path:e.input.path}),k(e=>e+1),document.querySelector(`[data-testid="ai-job-form"]`)?.scrollIntoView({behavior:`smooth`})}},l):r?(0,S.jsx)(`section`,{className:`panel empty`,children:(0,S.jsx)(`p`,{children:`分析完成后,在这里查看结果与来源。`})}):null]})]})}function O({session:e,members:t,seed:n}){let[r,i]=(0,x.useState)(!!n);return(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(y,{title:o.dashboard,children:(0,S.jsx)(`span`,{className:`muted`,children:`进度分析待接入`})}),(0,S.jsx)(`section`,{className:`progress-lanes`,"aria-label":`任务进度`,"data-testid":`progress-board`,children:[`计划`,`进行中`,`Review`].map(e=>(0,S.jsxs)(`section`,{className:`progress-lane`,"aria-label":e,children:[(0,S.jsxs)(`h2`,{children:[(0,S.jsx)(`span`,{className:`lane-dot`}),e]}),(0,S.jsx)(`div`,{className:`lane-empty`,children:`等待进度分析`})]},e))}),(0,S.jsxs)(`details`,{className:`analysis-tools`,"data-testid":`analysis-tools`,open:r,onToggle:e=>i(e.currentTarget.open),children:[(0,S.jsx)(`summary`,{children:`资料分析`}),r&&(0,S.jsx)(D,{session:e,members:t,seed:n})]})]})}export{O as Dashboard};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{P as e,S as t,b as n,c as r,f as i,g as a,h as o,l as s,m as c,s as l,v as u}from"./index-CgsCXCcm.js";import{t as d}from"./Markdown-BrmcIotb.js";var f=e(),p=i(),m=(e,t)=>`${e}/${t}`,h=e=>({"--tree-level":e});function g({members:e,...t}){let[n,r]=(0,f.useState)(`folder:${m(e[0]?.id??``,``)}`);function i(e){let t=e.target.closest(`[role="treeitem"]`);if(!t)return;let n=[...e.currentTarget.querySelectorAll(`[role="treeitem"]`)],r=n.indexOf(t),i=Number(t.getAttribute(`aria-level`)),a;switch(e.key){case`ArrowDown`:a=n[r+1];break;case`ArrowUp`:a=n[r-1];break;case`Home`:a=n[0];break;case`End`:a=n.at(-1);break;case`ArrowRight`:t.getAttribute(`aria-expanded`)===`false`?t.click():Number(n[r+1]?.getAttribute(`aria-level`))>i&&(a=n[r+1]);break;case`ArrowLeft`:t.getAttribute(`aria-expanded`)===`true`?t.click():a=n.slice(0,r).reverse().find(e=>Number(e.getAttribute(`aria-level`))<i);break;default:return}e.preventDefault(),a?.focus()}return(0,p.jsx)(`div`,{role:`tree`,"aria-label":`工作区文件`,"data-testid":`workspace-tree`,onKeyDown:i,children:e.map(e=>(0,p.jsx)(_,{owner:e.id,path:``,level:1,context:{...t,focused:n,onFocus:r}},e.id))})}function _({owner:e,path:t,level:n,context:r}){let i=r.expanded.has(m(e,t)),a=`folder:${m(e,t)}`;return(0,p.jsxs)(`div`,{role:`none`,children:[(0,p.jsxs)(`button`,{type:`button`,role:`treeitem`,className:`tree-item tree-folder`,style:h(n),"aria-label":t||e,"aria-level":n,"aria-expanded":i,tabIndex:r.focused===a?0:-1,"data-testid":`directory-row`,"data-owner-id":e,"data-directory-path":t,onFocus:()=>r.onFocus(a),onClick:()=>r.onToggle(e,t),children:[(0,p.jsx)(`span`,{className:`tree-chevron ${i?`expanded`:``}`,children:(0,p.jsx)(l,{name:`chevron`})}),(0,p.jsx)(l,{name:`files`}),(0,p.jsx)(`span`,{className:`tree-name`,children:t.split(`/`).at(-1)||e})]}),i&&(0,p.jsx)(v,{owner:e,path:t,level:n+1,context:r})]})}function v({owner:e,path:t,level:n,context:r}){let i=(0,f.useCallback)(n=>o.tree(e,t,void 0,``,n),[e,t]),u=c(i),[d,g]=(0,f.useState)(),[v,y]=(0,f.useState)(!1),[b,x]=(0,f.useState)(``),S=(0,f.useRef)(null);(0,f.useEffect)(()=>()=>S.current?.abort(),[]);let C=d?d.nextCursor:u.data?.nextCursor;async function w(){if(!C||!u.data||v)return;y(!0),x(``);let n=new AbortController;S.current=n;try{let r=await o.tree(e,t,u.data.revision,C,n.signal);n.signal.aborted||g({...r,entries:[...d?.entries??[],...r.entries]})}catch(e){n.signal.aborted||x(a(e))}finally{n.signal.aborted||y(!1)}}let T=[...u.data?.entries??[],...d?.entries??[]],E=r.revealed;if(E?.owner===e&&(!t||E.path.startsWith(t+`/`))){let e=t?E.path.slice(t.length+1):E.path,n=e.indexOf(`/`),r=n<0?E.path:[t,e.slice(0,n)].filter(Boolean).join(`/`);T.some(e=>e.path===r)||T.push(n<0?{...E.file,kind:`file`}:{kind:`directory`,path:r})}return T.sort((e,t)=>e.kind===t.kind?e.path.localeCompare(t.path):e.kind===`directory`?-1:1),(0,p.jsxs)(`div`,{role:`group`,"aria-label":m(e,t),"aria-busy":u.loading||v,children:[u.loading&&(0,p.jsx)(`p`,{className:`tree-status`,role:`status`,children:`正在加载…`}),(0,p.jsx)(s,{error:!0,children:u.error||b}),u.error&&(0,p.jsx)(`button`,{className:`tree-action`,onClick:()=>void u.reload(),children:`重试`}),T.map(t=>t.kind===`directory`?(0,p.jsx)(_,{owner:e,path:t.path,level:n,context:r},t.path):(0,p.jsxs)(`button`,{type:`button`,role:`treeitem`,className:`tree-item`,style:h(n),"aria-label":t.path,"aria-level":n,"aria-selected":r.selected?.owner===e&&r.selected.path===t.path,tabIndex:r.focused===`file:${m(e,t.path)}`?0:-1,"data-testid":`file-row`,"data-owner-id":e,"data-file-path":t.path,onFocus:()=>r.onFocus(`file:${m(e,t.path)}`),onClick:()=>r.onSelect(e,t.path),children:[(0,p.jsx)(`span`,{className:`tree-chevron`}),(0,p.jsx)(l,{name:`file`}),(0,p.jsx)(`span`,{className:`tree-name`,children:t.path.split(`/`).at(-1)})]},t.path)),!u.loading&&u.data&&T.length===0&&(0,p.jsx)(`p`,{className:`tree-status`,children:`空文件夹`}),C&&(0,p.jsx)(`button`,{className:`tree-action`,"data-testid":`files-load-more`,disabled:v,onClick:()=>void w(),children:v?`正在加载…`:`加载更多`})]})}function y(e,n){if(!n||/^(?:[a-z][a-z0-9+.-]*:|\/)/i.test(n))return null;try{let r=n.split(/[?#]/,1)[0],i=r?e.split(`/`).slice(0,-1):e.split(`/`);for(let e of r.split(`/`)){let t=decodeURIComponent(e);if(t&&t!==`.`){if(t===`..`){if(!i.length)return null;i.pop()}else{if(t.includes(`/`)||t.includes(`\\`))return null;i.push(t)}}}return{path:i.length?t.parse(i.join(`/`)):``,directory:r.endsWith(`/`),anchor:n.includes(`#`)?decodeURIComponent(n.slice(n.indexOf(`#`)+1)):``}}catch{return null}}function b({members:e,me:t,onAnalyze:i,onRefresh:a}){let[u,d]=(0,f.useState)(()=>new Set([m(t,``)])),[h,_]=(0,f.useState)(),[v,b]=(0,f.useState)(0),[S,C]=(0,f.useState)(``),w=(0,f.useCallback)(async e=>h?{owner:h.owner,...await o.file(h.owner,h.path,void 0,e)}:void 0,[h?.owner,h?.path,v]),T=c(w),E=T.data?.owner===h?.owner&&T.data?.file.path===h?.path?T.data:void 0;function D(e,t){let n=t.split(`/`).filter(Boolean);d(t=>{let r=new Set(t);for(let t=0;t<=n.length;t++)r.add(m(e,n.slice(0,t).join(`/`)));return r})}function O(e,t){C(``),D(e,t.split(`/`).slice(0,-1).join(`/`)),_({owner:e,path:t})}function k(e){if(!h)return!1;let t=y(h.path,e);return t?(C(``),t.directory?D(h.owner,t.path):t.path===h.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`}):O(h.owner,t.path),!0):!/^[a-z][a-z0-9+.-]*:/i.test(e)&&!e.startsWith(`//`)&&(C(`该链接超出成员工作区,请在原仓库查看。`),!0)}return(0,p.jsxs)(`div`,{className:`file-explorer`,"data-testid":`file-browser`,children:[(0,p.jsxs)(`aside`,{className:`file-tree-pane`,"aria-label":`文件资源管理器`,children:[(0,p.jsxs)(`div`,{className:`explorer-heading`,children:[(0,p.jsx)(`h1`,{children:`workspaces`}),(0,p.jsx)(r,{icon:`refresh`,label:`刷新文件`,"data-testid":`files-refresh`,onClick:()=>{C(``),b(e=>e+1),a()}})]}),(0,p.jsx)(g,{members:e,expanded:u,selected:h,revealed:!T.loading&&!T.error&&E?{owner:E.owner,path:E.file.path,file:E.file}:void 0,onSelect:O,onToggle:(e,t)=>d(n=>{let r=new Set(n),i=m(e,t);return r.has(i)?r.delete(i):r.add(i),r})},v)]}),(0,p.jsx)(`section`,{className:`file-detail`,"data-testid":`file-preview`,"data-owner-id":h?.owner,"data-file-path":h?.path,"aria-label":n.filePreview,children:h?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(s,{error:!0,children:S||!T.loading&&T.error}),T.error&&(0,p.jsx)(`button`,{className:`file-entry-retry`,"data-testid":`files-retry`,onClick:()=>void T.reload(),children:n.retry}),T.loading?(0,p.jsx)(`p`,{className:`preview-body`,role:`status`,children:n.loading}):!T.error&&E&&(0,p.jsx)(x,{owner:E.owner,file:E.file,onLink:k,onAnalyze:()=>i({owner:E.owner,kind:`file`,path:E.file.path,revision:E.revision})},`${E.owner}:${E.file.path}:${E.file.sha256}`)]}):(0,p.jsxs)(`div`,{className:`empty preview-empty`,children:[(0,p.jsx)(l,{name:`file`}),(0,p.jsx)(`p`,{children:n.pickFile})]})})]})}function x({owner:e,file:t,onLink:i,onAnalyze:a}){let[o,m]=(0,f.useState)(!1),h=t.size>524288,g=/\.(md|markdown)$/i.test(t.path),_=(0,f.useCallback)(e=>h?Promise.resolve(void 0):u(t,e),[t.sha256,t.size,h]),v=c(_);return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`header`,{className:`file-detail-header`,children:[(0,p.jsxs)(`span`,{className:`file-detail-path`,title:`${e}/${t.path}`,children:[(0,p.jsxs)(`span`,{children:[e,` / `]}),t.path]}),(0,p.jsxs)(`div`,{className:`row`,children:[(0,p.jsx)(r,{icon:`sparkles`,label:`分析文件`,"data-testid":`ai-analyze-file`,onClick:a}),g&&(0,p.jsx)(r,{icon:o?`preview`:`code`,label:o?`切换为预览`:`切换为原文`,"data-testid":`file-toggle-raw`,"aria-pressed":o,onClick:()=>m(!o)}),(0,p.jsx)(`a`,{className:`icon-button`,"data-testid":`file-download`,"aria-label":n.download,title:n.download,href:`/api/v1/blobs/${t.sha256}`,download:t.path.split(`/`).at(-1),children:(0,p.jsx)(l,{name:`download`})})]})]}),h?(0,p.jsx)(`div`,{className:`empty`,children:(0,p.jsx)(`p`,{children:n.previewLarge})}):v.error?(0,p.jsxs)(`div`,{className:`preview-body`,children:[(0,p.jsx)(s,{error:!0,children:v.error}),(0,p.jsx)(`button`,{"data-testid":`file-preview-retry`,onClick:()=>void v.reload(),children:n.retry})]}):v.loading?(0,p.jsx)(`p`,{className:`preview-body`,role:`status`,children:n.loading}):(0,p.jsx)(`div`,{className:`preview-body`,children:g&&!o?(0,p.jsx)(`article`,{className:`markdown`,"data-testid":`file-content`,"data-format":`markdown`,children:(0,p.jsx)(d,{onLink:i,children:v.data??``})}):(0,p.jsx)(`pre`,{className:`raw`,"data-testid":`file-content`,"data-format":`text`,children:v.data})})]})}export{b as Files};
|