linkshell-cli 0.5.33 → 0.5.34

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.
@@ -1918,6 +1918,9 @@ function previewFromTimelineItem(item: AgentTimelineItem): string | undefined {
1918
1918
 
1919
1919
  export class AgentWorkspaceProxy {
1920
1920
  private clients = new Map<AgentProvider, AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient>();
1921
+ // In-flight client starts, so concurrent ensureProviderClient calls (eager
1922
+ // initialize() racing conversation.open) share one subprocess spawn.
1923
+ private clientStartPromises = new Map<AgentProvider, Promise<AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient | undefined>>();
1921
1924
  private agentProtocols = new Map<AgentProvider, AgentProtocol>();
1922
1925
  private providerCapabilities = new Map<AgentProvider, ProviderRuntimeCapabilities>();
1923
1926
  private providerErrors = new Map<AgentProvider, string>();
@@ -2247,6 +2250,23 @@ export class AgentWorkspaceProxy {
2247
2250
  const existing = this.clients.get(provider);
2248
2251
  if (existing) return existing;
2249
2252
 
2253
+ // Dedupe concurrent starts: eager initialize() and conversation.open can
2254
+ // race here before this.clients is populated (it's only set AFTER the
2255
+ // client finishes initializing), which would spawn the provider subprocess
2256
+ // twice. Memoize the in-flight promise so both callers share one spawn;
2257
+ // drop it once settled so a failed start can be retried.
2258
+ const inflight = this.clientStartPromises.get(provider);
2259
+ if (inflight) return inflight;
2260
+ const start = this.startProviderClient(provider);
2261
+ this.clientStartPromises.set(provider, start);
2262
+ try {
2263
+ return await start;
2264
+ } finally {
2265
+ this.clientStartPromises.delete(provider);
2266
+ }
2267
+ }
2268
+
2269
+ private async startProviderClient(provider: AgentProvider): Promise<AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient | undefined> {
2250
2270
  const resolved = resolveAgentCommand({
2251
2271
  provider,
2252
2272
  command: this.input.command,
@@ -2299,7 +2319,7 @@ export class AgentWorkspaceProxy {
2299
2319
  const client = await tryCreateClient(resolved);
2300
2320
  this.clients.set(provider, client);
2301
2321
  this.providerErrors.delete(provider);
2302
- await this.refreshProviderCapabilities(provider, client, resolved.protocol);
2322
+ this.refreshProviderCapabilitiesInBackground(provider, client, resolved.protocol);
2303
2323
  this.status = "idle";
2304
2324
  this.error = undefined;
2305
2325
  this.sendCapabilities();
@@ -2319,7 +2339,7 @@ export class AgentWorkspaceProxy {
2319
2339
  const client = await tryCreateClient(fallback);
2320
2340
  this.clients.set(provider, client);
2321
2341
  this.providerErrors.delete(provider);
2322
- await this.refreshProviderCapabilities(provider, client, fallback.protocol);
2342
+ this.refreshProviderCapabilitiesInBackground(provider, client, fallback.protocol);
2323
2343
  this.status = "idle";
2324
2344
  this.error = undefined;
2325
2345
  this.sendCapabilities();
@@ -2339,6 +2359,27 @@ export class AgentWorkspaceProxy {
2339
2359
  }
2340
2360
  }
2341
2361
 
2362
+ /** Kick off runtime capability discovery WITHOUT blocking the caller.
2363
+ * refreshProviderCapabilities spawns a warm provider subprocess (measured
2364
+ * 858ms warm, multi-second cold for Claude), and nothing on the open/prompt
2365
+ * path needs it — sendCapabilities and prompt-time model resolution both
2366
+ * degrade gracefully to static fallbacks until providerCapabilities is
2367
+ * populated. When discovery lands we push a fresh agent.v2.capabilities so
2368
+ * clients pick up the real model/command list. */
2369
+ private refreshProviderCapabilitiesInBackground(
2370
+ provider: AgentProvider,
2371
+ client: AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient,
2372
+ protocol: AgentProtocol,
2373
+ ): void {
2374
+ void this.refreshProviderCapabilities(provider, client, protocol)
2375
+ .then(() => this.sendCapabilities())
2376
+ .catch((error) => {
2377
+ if (this.input.verbose) {
2378
+ process.stderr.write(`[agent:v2] capability refresh failed for ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
2379
+ }
2380
+ });
2381
+ }
2382
+
2342
2383
  private async refreshProviderCapabilities(
2343
2384
  provider: AgentProvider,
2344
2385
  client: AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient,
@@ -241,6 +241,11 @@ export function parseClaudeJsonlSession(input: {
241
241
  // one we see rather than accumulating. Decoupled from any live LinkShell turn
242
242
  // — this is read straight from the on-disk transcript.
243
243
  let usage: Record<string, unknown> | undefined;
244
+ // Last-seen raw permissionMode ("default" | "acceptEdits" | ...). Claude
245
+ // records it on user records; the LAST one is the session's current mode.
246
+ // Captured here so readSessionConfig can be a cache field read instead of a
247
+ // second full parse of the transcript.
248
+ let permissionMode: string | undefined;
244
249
  let index = 0;
245
250
 
246
251
  for (const line of input.text.split(/\r?\n/)) {
@@ -256,6 +261,7 @@ export function parseClaudeJsonlSession(input: {
256
261
  continue;
257
262
  }
258
263
 
264
+ permissionMode = stringField(raw, ["permissionMode"]) ?? permissionMode;
259
265
  const timestamp = parseTimestamp(raw.timestamp ?? raw.createdAt ?? raw.created_at);
260
266
  cwd = stringField(raw, ["cwd", "workingDirectory", "workspacePath"]) ?? cwd;
261
267
  if (timestamp !== undefined) {
@@ -386,6 +392,7 @@ export function parseClaudeJsonlSession(input: {
386
392
  updatedAt,
387
393
  usage,
388
394
  turns,
395
+ ...(permissionMode ? { permissionMode } : {}),
389
396
  },
390
397
  };
391
398
  }
@@ -564,6 +571,44 @@ export class ClaudeSdkClient {
564
571
  // every one of them (the "scan storm"). Only the file being actively appended
565
572
  // to changes, so the steady-state cost drops to stat()+status per file.
566
573
  private sessionMetaCache = new Map<string, { mtimeMs: number; size: number; base: Record<string, unknown>; midTurn: boolean }>();
574
+ // Full parsed-transcript cache shared by readSession / readSessionConfig /
575
+ // listTurns, keyed by file path and validated by (mtime,size) like
576
+ // sessionMetaCache above. Opening a conversation hits all three of those in
577
+ // quick succession, and every history page re-slices the same parse — on a
578
+ // 39MB transcript that was ~257ms per open plus ~93ms per scroll page of
579
+ // redundant readFileSync + per-line JSON.parse. Unlike sessionMetaCache this
580
+ // holds the FULL turn list (large for big transcripts), so it is bounded to a
581
+ // small LRU (Map iteration order + delete/re-set on hit = LRU).
582
+ private parsedTranscriptCache = new Map<string, { mtimeMs: number; size: number; parsed: Record<string, unknown> }>();
583
+ private static readonly PARSED_TRANSCRIPT_CACHE_MAX = 8;
584
+
585
+ /** Read + parse a session transcript through the LRU cache. Returns the
586
+ * parseClaudeJsonlSession result (stable for a given (mtime,size)); an
587
+ * appended-to or rewritten file re-parses on next access. */
588
+ private parsedTranscript(filePath: string, sessionId: string): Record<string, unknown> {
589
+ const stat = statSync(filePath);
590
+ const cached = this.parsedTranscriptCache.get(filePath);
591
+ if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
592
+ // Refresh recency so the hottest transcripts survive the LRU bound.
593
+ this.parsedTranscriptCache.delete(filePath);
594
+ this.parsedTranscriptCache.set(filePath, cached);
595
+ return cached.parsed;
596
+ }
597
+ const parsed = parseClaudeJsonlSession({
598
+ text: readFileSync(filePath, "utf8"),
599
+ cwd: this.input.cwd,
600
+ sessionId,
601
+ fallbackUpdatedAt: stat.mtimeMs,
602
+ });
603
+ this.parsedTranscriptCache.delete(filePath);
604
+ this.parsedTranscriptCache.set(filePath, { mtimeMs: stat.mtimeMs, size: stat.size, parsed });
605
+ while (this.parsedTranscriptCache.size > ClaudeSdkClient.PARSED_TRANSCRIPT_CACHE_MAX) {
606
+ const oldest = this.parsedTranscriptCache.keys().next().value;
607
+ if (oldest === undefined) break;
608
+ this.parsedTranscriptCache.delete(oldest);
609
+ }
610
+ return parsed;
611
+ }
567
612
 
568
613
  constructor(
569
614
  private readonly input: {
@@ -622,13 +667,7 @@ export class ClaudeSdkClient {
622
667
  },
623
668
  };
624
669
  }
625
- const stat = statSync(filePath);
626
- return parseClaudeJsonlSession({
627
- text: readFileSync(filePath, "utf8"),
628
- cwd: this.input.cwd,
629
- sessionId: input.sessionId,
630
- fallbackUpdatedAt: stat.mtimeMs,
631
- });
670
+ return this.parsedTranscript(filePath, input.sessionId);
632
671
  }
633
672
 
634
673
  /** Resolve the on-disk JSONL transcript for a session, preferring the cwd's
@@ -706,14 +745,10 @@ export class ClaudeSdkClient {
706
745
  if (!filePath) return {};
707
746
  let permissionModeRaw: string | undefined;
708
747
  try {
709
- const text = readFileSync(filePath, "utf8");
710
- for (const line of text.split(/\r?\n/)) {
711
- if (!line.trim()) continue;
712
- let record: Record<string, unknown> | undefined;
713
- try { record = asRecord(JSON.parse(line)); } catch { continue; }
714
- const pm = stringField(record, ["permissionMode"]);
715
- if (pm) permissionModeRaw = pm; // keep the last one — the current mode
716
- }
748
+ // The shared parse already tracked the last-seen permissionMode, so this
749
+ // is a cache field read on the open path (readSession runs right before).
750
+ const thread = asRecord(this.parsedTranscript(filePath, input.sessionId).thread);
751
+ permissionModeRaw = stringField(thread, ["permissionMode"]);
717
752
  } catch {
718
753
  return {};
719
754
  }
@@ -753,13 +788,7 @@ export class ClaudeSdkClient {
753
788
  const filePath = this.resolveSessionFile(input.sessionId);
754
789
  if (!filePath) return empty;
755
790
 
756
- const stat = statSync(filePath);
757
- const parsed = parseClaudeJsonlSession({
758
- text: readFileSync(filePath, "utf8"),
759
- cwd: this.input.cwd,
760
- sessionId: input.sessionId,
761
- fallbackUpdatedAt: stat.mtimeMs,
762
- });
791
+ const parsed = this.parsedTranscript(filePath, input.sessionId);
763
792
  const thread = asRecord(parsed.thread);
764
793
  const allTurns = Array.isArray(thread?.turns) ? thread.turns : [];
765
794
  const total = allTurns.length;
@@ -1,2 +1,2 @@
1
- import{r as n,j as e}from"./vendor-react-0zU5oKmd.js";import{k as b,a as w,l as k,h as C,m as z}from"./index-ByEwVA14.js";import"./vendor-highlight-Cus7zBI4.js";function I({store:x,initialPath:d,onClose:j}){const[c,u]=n.useState(d||"."),[y,N]=n.useState([]),[o,l]=n.useState(!1),[f,r]=n.useState(null),[i,p]=n.useState(null),a=n.useCallback(async t=>{l(!0),r(null),p(null);try{const s=await x.browse(t,!0);s.error?r(s.error):(u(s.path),N([...s.entries].sort((m,h)=>m.isDirectory!==h.isDirectory?m.isDirectory?-1:1:m.name.localeCompare(h.name))))}catch(s){r(s.message||"浏览失败")}finally{l(!1)}},[x]);n.useEffect(()=>{a(d||".")},[a,d]);const g=async t=>{l(!0),r(null);try{const s=await x.readFile(t.path);s.error?r(s.error):p({path:s.path,content:s.content,truncated:s.truncated})}catch(s){r(s.message||"读取失败")}finally{l(!1)}},v=c.replace(/\/+$/,"").split("/").slice(0,-1).join("/")||"/";return e.jsxs("div",{className:"flex h-full flex-col bg-surface",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-2xs text-content-secondary",children:[e.jsx(b,{size:13,className:"text-content-faint"})," 文件"]}),e.jsx("button",{onClick:j,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭文件面板",children:e.jsx(w,{size:15})})]}),e.jsxs("div",{className:"flex items-center gap-1 border-b border-border px-2 py-1.5",children:[e.jsx("button",{onClick:()=>a(v),disabled:o,className:"cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"上级目录","aria-label":"上级目录",children:e.jsx(k,{size:14})}),e.jsx("input",{value:c,onChange:t=>u(t.target.value),onKeyDown:t=>t.key==="Enter"&&a(c),className:"flex-1 bg-transparent font-mono text-2xs text-content-primary outline-none"}),e.jsx("button",{onClick:()=>a(c),disabled:o,className:"cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"刷新","aria-label":"刷新",children:e.jsx(C,{size:14})})]}),f&&e.jsx("p",{className:"px-3 py-1.5 text-2xs text-danger",children:f}),e.jsxs("div",{className:"min-h-0 flex-1 overflow-auto",children:[o&&e.jsx("p",{className:"px-3 py-2 text-2xs text-content-muted",children:"加载中…"}),!o&&i?e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-1.5",children:[e.jsx("span",{className:"truncate font-mono text-2xs text-content-secondary",children:i.path}),e.jsx("button",{onClick:()=>p(null),className:"codex-btn-ghost text-2xs",children:"返回"})]}),e.jsxs("pre",{className:"whitespace-pre-wrap px-3 py-2 font-mono text-2xs leading-relaxed text-content-secondary",children:[i.content,i.truncated&&e.jsx("span",{className:"text-warning",children:`
1
+ import{r as n,j as e}from"./vendor-react-0zU5oKmd.js";import{k as b,a as w,l as k,h as C,m as z}from"./index-pqslvKcS.js";import"./vendor-highlight-Cus7zBI4.js";function I({store:x,initialPath:d,onClose:j}){const[c,u]=n.useState(d||"."),[y,N]=n.useState([]),[o,l]=n.useState(!1),[f,r]=n.useState(null),[i,p]=n.useState(null),a=n.useCallback(async t=>{l(!0),r(null),p(null);try{const s=await x.browse(t,!0);s.error?r(s.error):(u(s.path),N([...s.entries].sort((m,h)=>m.isDirectory!==h.isDirectory?m.isDirectory?-1:1:m.name.localeCompare(h.name))))}catch(s){r(s.message||"浏览失败")}finally{l(!1)}},[x]);n.useEffect(()=>{a(d||".")},[a,d]);const g=async t=>{l(!0),r(null);try{const s=await x.readFile(t.path);s.error?r(s.error):p({path:s.path,content:s.content,truncated:s.truncated})}catch(s){r(s.message||"读取失败")}finally{l(!1)}},v=c.replace(/\/+$/,"").split("/").slice(0,-1).join("/")||"/";return e.jsxs("div",{className:"flex h-full flex-col bg-surface",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-2xs text-content-secondary",children:[e.jsx(b,{size:13,className:"text-content-faint"})," 文件"]}),e.jsx("button",{onClick:j,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭文件面板",children:e.jsx(w,{size:15})})]}),e.jsxs("div",{className:"flex items-center gap-1 border-b border-border px-2 py-1.5",children:[e.jsx("button",{onClick:()=>a(v),disabled:o,className:"cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"上级目录","aria-label":"上级目录",children:e.jsx(k,{size:14})}),e.jsx("input",{value:c,onChange:t=>u(t.target.value),onKeyDown:t=>t.key==="Enter"&&a(c),className:"flex-1 bg-transparent font-mono text-2xs text-content-primary outline-none"}),e.jsx("button",{onClick:()=>a(c),disabled:o,className:"cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"刷新","aria-label":"刷新",children:e.jsx(C,{size:14})})]}),f&&e.jsx("p",{className:"px-3 py-1.5 text-2xs text-danger",children:f}),e.jsxs("div",{className:"min-h-0 flex-1 overflow-auto",children:[o&&e.jsx("p",{className:"px-3 py-2 text-2xs text-content-muted",children:"加载中…"}),!o&&i?e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-1.5",children:[e.jsx("span",{className:"truncate font-mono text-2xs text-content-secondary",children:i.path}),e.jsx("button",{onClick:()=>p(null),className:"codex-btn-ghost text-2xs",children:"返回"})]}),e.jsxs("pre",{className:"whitespace-pre-wrap px-3 py-2 font-mono text-2xs leading-relaxed text-content-secondary",children:[i.content,i.truncated&&e.jsx("span",{className:"text-warning",children:`
2
2
  …(已截断)`})]})]}):!o&&y.map(t=>e.jsxs("button",{onClick:()=>t.isDirectory?a(t.path):g(t),className:"flex w-full cursor-pointer items-center gap-2 px-3 py-1 text-left transition-colors hover:bg-surface-overlay",children:[t.isDirectory?e.jsx(b,{size:13,className:"shrink-0 text-content-faint"}):e.jsx(z,{size:13,className:"shrink-0 text-content-faint"}),e.jsx("span",{className:"truncate font-mono text-2xs text-content-primary",children:t.name}),!t.isDirectory&&t.size!=null&&e.jsxs("span",{className:"ml-auto font-mono text-2xs text-content-faint",children:[t.size,"B"]})]},t.path))]})]})}export{I as FileBrowser};
@@ -1,2 +1,2 @@
1
- import{r,j as e}from"./vendor-react-0zU5oKmd.js";import{e as me,g as he,a as be,c as ge,d as ve,f as je,h as ye,i as Ne,j as ke}from"./index-ByEwVA14.js";import"./vendor-highlight-Cus7zBI4.js";const Z=[{cssProp:"color",label:"文本颜色"},{cssProp:"background-color",label:"背景"},{cssProp:"font-size",label:"字号"},{cssProp:"font-weight",label:"字重"},{cssProp:"padding",label:"内边距"},{cssProp:"border-radius",label:"圆角"}];function we(d){var i;const c=(i=d.ownerDocument.defaultView)==null?void 0:i.getComputedStyle(d),o={};for(const{cssProp:f}of Z)o[f]=(c==null?void 0:c.getPropertyValue(f).trim())??"";return o}function $e(d){const c=/^\s*(\d{1,5})(.*)$/.exec(d);if(!c)return null;const o=c[1];if(Number(o)<1||Number(o)>65535)return null;let i=c[2]||"/";return i.startsWith("/")||(i=`/${i}`),{port:o,path:i}}function Ce(d){if(d.id)return`#${CSS.escape(d.id)}`;const c=[];let o=d,i=0;for(;o&&o.nodeType===1&&i<4;){let f=o.tagName.toLowerCase();const P=(o.getAttribute("class")||"").trim().split(/\s+/).filter(Boolean).slice(0,2).map(m=>`.${CSS.escape(m)}`).join("");f+=P;const h=o.parentElement;if(h){const m=[...h.children].filter(y=>y.tagName===o.tagName);m.length>1&&(f+=`:nth-of-type(${m.indexOf(o)+1})`)}if(c.unshift(f),o.id){c[0]=`#${CSS.escape(o.id)}`;break}o=h,i+=1}return c.join(" > ")}function Me({gatewayUrl:d,sessionId:c,initialAuthToken:o,defaultPort:i,isMobile:f,onClose:P,onAnnotate:h}){const[m,y]=r.useState(i||"3000"),[u,ee]=r.useState(null),[R,te]=r.useState(o??null),[E,T]=r.useState(!1),[M,H]=r.useState("desktop"),[ne,U]=r.useState(0),[I,N]=r.useState(!1),[K,z]=r.useState(null),[v,j]=r.useState(null),[l,L]=r.useState(null),[W,k]=r.useState(""),[w,B]=r.useState(!1),[$,D]=r.useState({}),[C,S]=r.useState({}),A=r.useRef(null),V=r.useRef(null),O=r.useMemo(()=>me(),[]);r.useEffect(()=>{let t=!1;return T(!1),he().then(n=>{t||te((n==null?void 0:n.accessToken)??null)}).finally(()=>{t||T(!0)}),()=>{t=!0}},[o]);const b=r.useMemo(()=>{if(!u||o&&!E)return null;const t=`${d.replace(/\/+$/,"")}/tunnel/${encodeURIComponent(c)}/${u.port}${u.path}`,n=[];if(O&&n.push(`token=${encodeURIComponent(O)}`),R&&n.push(`auth_token=${encodeURIComponent(R)}`),n.length===0)return t;const s=u.path.includes("?")?"&":"?";return`${t}${s}${n.join("&")}`},[u,d,c,O,R,E]),_=()=>{const t=$e(m);t&&(ee(t),U(n=>n+1))},se=()=>U(t=>t+1),oe=()=>{var t;if(u)try{const n=(t=A.current)==null?void 0:t.contentWindow;if(!n)return;const s=`/tunnel/${encodeURIComponent(c)}/${u.port}`;let x=n.location.pathname;x.startsWith(s)&&(x=x.slice(s.length)||"/");const p=new URLSearchParams(n.location.search);p.delete("token"),p.delete("auth_token");const g=p.toString();y(`${u.port}${x}${g?`?${g}`:""}`)}catch{}},F=(t,n)=>{const s=A.current,x=s==null?void 0:s.contentDocument;if(!s||!x)return null;const p=s.getBoundingClientRect();return x.elementFromPoint(t-p.left,n-p.top)},X=t=>{const n=t.getBoundingClientRect();return{top:n.top,left:n.left,width:n.width,height:n.height}},re=t=>{try{const n=F(t.clientX,t.clientY);j(n?X(n):null)}catch{j(null),z("此页面跨域,无法读取元素(生产环境同源可用)"),N(!1)}},le=t=>{t.preventDefault();try{const n=F(t.clientX,t.clientY);if(!n||!("style"in n))return;const s=n;L({el:s,selector:Ce(s),text:(s.textContent||"").replace(/\s+/g," ").trim().slice(0,120),rect:X(s)}),D(we(s)),S({}),k("")}catch{z("此页面跨域,无法读取元素(生产环境同源可用)"),N(!1)}},ce=(t,n)=>{if(l){try{l.el.style.setProperty(t,n)}catch{}S(s=>({...s,[t]:n}))}},ae=()=>Object.entries(C).filter(([t,n])=>n!==($[t]??"")).map(([t,n])=>({cssProp:t,from:$[t]??"",to:n})),Y=()=>{if(!l)return;const t=ae(),n=["关于预览页面中的这个元素:",`- 选择器: \`${l.selector}\``,l.text?`- 文本: "${l.text}"`:"",`- 路径: localhost:${m}`,W.trim()?`- 批注: ${W.trim()}`:""];if(t.length>0){n.push("- 请把以下样式改动应用到源码:");for(const s of t)n.push(` - ${s.cssProp}: ${s.from||"(默认)"} → ${s.to}`)}h==null||h(n.filter(Boolean).join(`
1
+ import{r,j as e}from"./vendor-react-0zU5oKmd.js";import{e as me,g as he,a as be,c as ge,d as ve,f as je,h as ye,i as Ne,j as ke}from"./index-pqslvKcS.js";import"./vendor-highlight-Cus7zBI4.js";const Z=[{cssProp:"color",label:"文本颜色"},{cssProp:"background-color",label:"背景"},{cssProp:"font-size",label:"字号"},{cssProp:"font-weight",label:"字重"},{cssProp:"padding",label:"内边距"},{cssProp:"border-radius",label:"圆角"}];function we(d){var i;const c=(i=d.ownerDocument.defaultView)==null?void 0:i.getComputedStyle(d),o={};for(const{cssProp:f}of Z)o[f]=(c==null?void 0:c.getPropertyValue(f).trim())??"";return o}function $e(d){const c=/^\s*(\d{1,5})(.*)$/.exec(d);if(!c)return null;const o=c[1];if(Number(o)<1||Number(o)>65535)return null;let i=c[2]||"/";return i.startsWith("/")||(i=`/${i}`),{port:o,path:i}}function Ce(d){if(d.id)return`#${CSS.escape(d.id)}`;const c=[];let o=d,i=0;for(;o&&o.nodeType===1&&i<4;){let f=o.tagName.toLowerCase();const P=(o.getAttribute("class")||"").trim().split(/\s+/).filter(Boolean).slice(0,2).map(m=>`.${CSS.escape(m)}`).join("");f+=P;const h=o.parentElement;if(h){const m=[...h.children].filter(y=>y.tagName===o.tagName);m.length>1&&(f+=`:nth-of-type(${m.indexOf(o)+1})`)}if(c.unshift(f),o.id){c[0]=`#${CSS.escape(o.id)}`;break}o=h,i+=1}return c.join(" > ")}function Me({gatewayUrl:d,sessionId:c,initialAuthToken:o,defaultPort:i,isMobile:f,onClose:P,onAnnotate:h}){const[m,y]=r.useState(i||"3000"),[u,ee]=r.useState(null),[R,te]=r.useState(o??null),[E,T]=r.useState(!1),[M,H]=r.useState("desktop"),[ne,U]=r.useState(0),[I,N]=r.useState(!1),[K,z]=r.useState(null),[v,j]=r.useState(null),[l,L]=r.useState(null),[W,k]=r.useState(""),[w,B]=r.useState(!1),[$,D]=r.useState({}),[C,S]=r.useState({}),A=r.useRef(null),V=r.useRef(null),O=r.useMemo(()=>me(),[]);r.useEffect(()=>{let t=!1;return T(!1),he().then(n=>{t||te((n==null?void 0:n.accessToken)??null)}).finally(()=>{t||T(!0)}),()=>{t=!0}},[o]);const b=r.useMemo(()=>{if(!u||o&&!E)return null;const t=`${d.replace(/\/+$/,"")}/tunnel/${encodeURIComponent(c)}/${u.port}${u.path}`,n=[];if(O&&n.push(`token=${encodeURIComponent(O)}`),R&&n.push(`auth_token=${encodeURIComponent(R)}`),n.length===0)return t;const s=u.path.includes("?")?"&":"?";return`${t}${s}${n.join("&")}`},[u,d,c,O,R,E]),_=()=>{const t=$e(m);t&&(ee(t),U(n=>n+1))},se=()=>U(t=>t+1),oe=()=>{var t;if(u)try{const n=(t=A.current)==null?void 0:t.contentWindow;if(!n)return;const s=`/tunnel/${encodeURIComponent(c)}/${u.port}`;let x=n.location.pathname;x.startsWith(s)&&(x=x.slice(s.length)||"/");const p=new URLSearchParams(n.location.search);p.delete("token"),p.delete("auth_token");const g=p.toString();y(`${u.port}${x}${g?`?${g}`:""}`)}catch{}},F=(t,n)=>{const s=A.current,x=s==null?void 0:s.contentDocument;if(!s||!x)return null;const p=s.getBoundingClientRect();return x.elementFromPoint(t-p.left,n-p.top)},X=t=>{const n=t.getBoundingClientRect();return{top:n.top,left:n.left,width:n.width,height:n.height}},re=t=>{try{const n=F(t.clientX,t.clientY);j(n?X(n):null)}catch{j(null),z("此页面跨域,无法读取元素(生产环境同源可用)"),N(!1)}},le=t=>{t.preventDefault();try{const n=F(t.clientX,t.clientY);if(!n||!("style"in n))return;const s=n;L({el:s,selector:Ce(s),text:(s.textContent||"").replace(/\s+/g," ").trim().slice(0,120),rect:X(s)}),D(we(s)),S({}),k("")}catch{z("此页面跨域,无法读取元素(生产环境同源可用)"),N(!1)}},ce=(t,n)=>{if(l){try{l.el.style.setProperty(t,n)}catch{}S(s=>({...s,[t]:n}))}},ae=()=>Object.entries(C).filter(([t,n])=>n!==($[t]??"")).map(([t,n])=>({cssProp:t,from:$[t]??"",to:n})),Y=()=>{if(!l)return;const t=ae(),n=["关于预览页面中的这个元素:",`- 选择器: \`${l.selector}\``,l.text?`- 文本: "${l.text}"`:"",`- 路径: localhost:${m}`,W.trim()?`- 批注: ${W.trim()}`:""];if(t.length>0){n.push("- 请把以下样式改动应用到源码:");for(const s of t)n.push(` - ${s.cssProp}: ${s.from||"(默认)"} → ${s.to}`)}h==null||h(n.filter(Boolean).join(`
2
2
  `)),L(null),k(""),D({}),S({}),B(!1),j(null),N(!1)},q=()=>{L(null),k(""),D({}),S({}),B(!1)},ie=()=>{z(null),q(),j(null),N(t=>!t)};return e.jsxs("div",{className:"flex h-full flex-col bg-canvas",children:[e.jsxs("div",{className:"flex items-center gap-1.5 border-b border-border bg-surface px-2 py-1.5",children:[f&&e.jsx("button",{onClick:P,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭预览",children:e.jsx(be,{size:15})}),e.jsxs("div",{className:"flex min-w-0 flex-1 items-center gap-1 rounded-lg border border-border bg-surface-raised px-2 py-1",children:[e.jsx("span",{className:"shrink-0 font-mono text-2xs text-content-faint",children:"localhost:"}),e.jsx("input",{value:m,onChange:t=>y(t.target.value),onKeyDown:t=>t.key==="Enter"&&_(),placeholder:"3000",spellCheck:!1,className:"min-w-0 flex-1 bg-transparent font-mono text-2xs text-content-primary outline-none","aria-label":"端口与路径"}),e.jsx("button",{onClick:_,className:"shrink-0 cursor-pointer rounded bg-accent-dim px-2 py-0.5 text-2xs font-medium text-white transition-colors hover:bg-accent",children:"前往"})]}),e.jsx("button",{onClick:ie,disabled:!u,className:`shrink-0 cursor-pointer rounded-md p-1.5 transition-colors disabled:opacity-40 ${I?"bg-accent-dim text-white":"text-content-muted hover:bg-surface-overlay hover:text-accent"}`,title:"标注元素","aria-label":"标注元素",children:e.jsx(ge,{size:14})}),e.jsxs("div",{className:"flex shrink-0 items-center rounded-lg border border-border bg-surface-raised p-0.5",children:[e.jsx("button",{onClick:()=>H("desktop"),className:`cursor-pointer rounded p-1 transition-colors ${M==="desktop"?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-primary"}`,title:"桌面视口","aria-label":"桌面视口",children:e.jsx(ve,{size:13})}),e.jsx("button",{onClick:()=>H("mobile"),className:`cursor-pointer rounded p-1 transition-colors ${M==="mobile"?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-primary"}`,title:"移动视口","aria-label":"移动视口",children:e.jsx(je,{size:13})})]}),e.jsx("button",{onClick:se,disabled:!u,className:"shrink-0 cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent disabled:opacity-40",title:"刷新","aria-label":"刷新",children:e.jsx(ye,{size:14})}),e.jsx("button",{onClick:()=>b&&window.open(b,"_blank","noopener,noreferrer"),disabled:!b,className:"shrink-0 cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent disabled:opacity-40",title:"在新窗口打开","aria-label":"在新窗口打开",children:e.jsx(Ne,{size:14})})]}),K&&e.jsx("p",{className:"border-b border-border bg-warning/10 px-3 py-1.5 text-2xs text-warning",children:K}),I&&!l&&e.jsx("p",{className:"border-b border-border bg-accent/10 px-3 py-1.5 text-2xs text-accent",children:"标注模式:移动鼠标高亮元素,点击选择并添加批注。"}),e.jsx("div",{className:"min-h-0 flex-1 overflow-auto bg-surface-raised",children:b?e.jsx("div",{className:M==="mobile"?"mx-auto h-full w-[390px] max-w-full border-x border-border":"h-full w-full",children:e.jsxs("div",{className:"relative h-full w-full",children:[e.jsx("iframe",{ref:A,src:b,title:"端口预览",onLoad:oe,className:"h-full w-full bg-white"},`${b}-${ne}`),I&&e.jsx("div",{className:"absolute inset-0 cursor-crosshair",onMouseMove:re,onMouseLeave:()=>j(null),onClick:le,children:v&&!l&&e.jsx("div",{className:"pointer-events-none absolute border-2 border-accent bg-accent/10",style:{top:v.top,left:v.left,width:v.width,height:v.height}})}),l&&(()=>{var J,Q;const s=((J=V.current)==null?void 0:J.clientHeight)??600,x=l.rect.top+l.rect.height+8,p=l.rect.top-8,g=w?340:140,G=x+g>s&&p-g>0?Math.max(4,p-g):x,de=Math.max(4,Math.min(l.rect.left,(((Q=V.current)==null?void 0:Q.clientWidth)??800)-280-4));return e.jsxs("div",{className:"absolute z-20 flex flex-col gap-2 rounded-xl border border-accent-dim/40 bg-surface-raised px-3 py-2.5 shadow-2xl animate-fade-in",style:{top:G,left:de,width:280,maxHeight:s-G-8},children:[e.jsx("p",{className:"truncate font-mono text-2xs text-content-faint",title:l.selector,children:l.selector}),l.text&&e.jsxs("p",{className:"-mt-1 truncate text-2xs text-content-muted",children:['"',l.text,'"']}),e.jsx("input",{value:W,onChange:a=>k(a.target.value),onKeyDown:a=>a.key==="Enter"&&Y(),autoFocus:!0,placeholder:"添加批注…",className:"w-full rounded-lg border border-border bg-surface px-2.5 py-1.5 text-xs text-content-primary placeholder-content-muted outline-none focus:border-accent-dim"}),e.jsxs("button",{onClick:()=>B(a=>!a),className:`flex cursor-pointer items-center gap-1 self-start rounded-md px-1.5 py-0.5 text-2xs transition-colors ${w?"text-accent":"text-content-muted hover:text-content-secondary"}`,children:[e.jsx("svg",{width:10,height:10,viewBox:"0 0 10 10",className:`shrink-0 transition-transform ${w?"rotate-180":""}`,children:e.jsx("path",{d:"M2 3.5 L5 6.5 L8 3.5",fill:"none",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round"})}),"调整样式"]}),w&&e.jsx("div",{className:"grid grid-cols-2 gap-x-2 gap-y-1.5 animate-fade-in",children:Z.map(({cssProp:a,label:ue})=>{const xe=C[a]??$[a]??"",pe=a in C&&C[a]!==($[a]??"");return e.jsxs("label",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:`text-2xs ${pe?"text-accent":"text-content-faint"}`,children:ue}),e.jsx("input",{value:xe,onChange:fe=>ce(a,fe.target.value),spellCheck:!1,className:"w-full rounded-md border border-border bg-surface px-2 py-1 font-mono text-2xs text-content-primary outline-none focus:border-accent-dim"})]},a)})}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{onClick:q,className:"codex-btn-ghost flex-1 text-2xs",children:"取消"}),e.jsx("button",{onClick:Y,className:"codex-btn-primary flex-1 text-2xs",children:"发送给 Agent"})]})]})})()]})}):e.jsxs("div",{className:"flex h-full flex-col items-center justify-center gap-3 px-6 text-center",children:[e.jsx(ke,{size:34,className:"text-content-faint"}),e.jsx("p",{className:"text-sm leading-6 text-content-muted",children:u&&!E?"正在准备预览认证...":"输入主机上服务的端口号,预览远程本地服务"})]})})]})}export{Me as PortPreview};
@@ -1 +1 @@
1
- import{r as l,j as i}from"./vendor-react-0zU5oKmd.js";import{x as w,a as C}from"./vendor-xterm-D15THcI4.js";import{I as T,a as A,b as B}from"./index-ByEwVA14.js";import"./vendor-highlight-Cus7zBI4.js";function x(e,t){const r=getComputedStyle(document.documentElement).getPropertyValue(e).trim();return r?`rgb(${r})`:t}function $(e,t,r){const u=getComputedStyle(document.documentElement).getPropertyValue(e).trim();return u?`rgb(${u} / ${t})`:r}function E(){return{background:x("--c-canvas","#0b0d0f"),foreground:x("--c-content-primary","#e6e8eb"),cursor:x("--c-accent","#2dd4bf"),selectionBackground:$("--c-accent",.22,"rgba(45,212,191,0.25)"),black:x("--c-content-faint","#4e535a"),brightBlack:x("--c-content-muted","#70767e"),white:x("--c-content-secondary","#9ea5ad"),brightWhite:x("--c-content-primary","#e9ebee")}}function F(e,t){const r=l.useRef(null),u=l.useRef(null),h=l.useRef(null);return l.useEffect(()=>{if(!r.current||!e)return;const n=new w.Terminal({fontFamily:"JetBrains Mono, ui-monospace, monospace",fontSize:13,cursorBlink:!0,theme:E(),scrollback:5e3}),f=new C.FitAddon;n.loadAddon(f),n.open(r.current),f.fit(),u.current=n,h.current=f;const p=e.terminalBuffer(t);p&&n.write(p);const v=n.onData(c=>e.sendInput(t,c)),b=n.onResize(({cols:c,rows:o})=>e.sendResize(t,c,o)),k=e.onEvent(c=>{if(c.type==="terminal.reset"&&c.terminalId===t){n.reset();return}c.type==="terminal.output"&&c.terminalId===t&&n.write(c.data)}),y=new ResizeObserver(()=>{try{f.fit(),e.sendResize(t,n.cols,n.rows)}catch{}});y.observe(r.current);const s=new MutationObserver(()=>{n.options.theme=E()});return s.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]}),e.sendResize(t,n.cols,n.rows),()=>{v.dispose(),b.dispose(),k(),y.disconnect(),s.disconnect(),n.dispose(),u.current=null,h.current=null}},[e,t]),{containerRef:r}}function I(e,t){return e==="default"?"默认":t||e.slice(0,8)}const z=e=>`linkshell:web:lastTerminal:${e}`;function L(e){if(!e)return null;try{return localStorage.getItem(z(e))}catch{return null}}function P(e,t){if(e)try{localStorage.setItem(z(e),t)}catch{}}function O(e,t){if(e.length===0)return null;if(t&&e.includes(t))return t;const r=e.filter(u=>u!=="default");return r.length>0?r[r.length-1]:e[0]}function V({bridge:e,terminalId:t}){const{containerRef:r}=F(e,t);return i.jsx("div",{ref:r,className:"xterm-host min-h-0 flex-1 bg-canvas p-2"})}function N({bridge:e,onNewTerminal:t}){const r=e==null?void 0:e.sessionId,[u,h]=l.useState([]),[n,f]=l.useState("default"),p=l.useRef(!1),v=l.useRef(!1),b=l.useCallback((s,c)=>{h(o=>o.some(m=>m.id===s)?o:[...o,{id:s,label:I(s,c)}])},[]);l.useEffect(()=>{if(!e)return;p.current=!1,v.current=!1;const s=L(r);return e.requestTerminalList(),e.onEvent(o=>{if(o.type==="terminal.output"){b(o.terminalId);return}if(o.type==="terminal.list"){const m=o.envelope,a=m.payload;if(m.type==="terminal.spawned"){const R=a==null?void 0:a.terminalId;R&&(b(R,a==null?void 0:a.projectName),f(R));return}if(m.type==="terminal.list"){const j=(Array.isArray(a==null?void 0:a.terminals)?a.terminals:[]).filter(d=>typeof d.terminalId=="string"&&d.status!=="exited").map(d=>({id:d.terminalId,label:I(d.terminalId,d.projectName)}));if(h(j),!p.current){p.current=!0;const d=j.map(S=>S.id),g=O(d,s);g?f(g):v.current||(v.current=!0,t==null||t())}}}})},[e,b,t]),l.useEffect(()=>{p.current&&n&&P(r,n)},[r,n]);const k=()=>t==null?void 0:t(),y=s=>{e==null||e.killTerminal(s),h(c=>{var m;const o=c.filter(a=>a.id!==s);return s===n&&f(((m=o[o.length-1])==null?void 0:m.id)??"default"),o})};return i.jsxs("div",{className:"flex h-full flex-col bg-canvas",children:[i.jsxs("div",{className:"flex items-center gap-0.5 border-b border-border bg-surface-raised px-1.5 py-1",children:[i.jsx(T,{size:13,className:"mx-1 shrink-0 text-content-faint"}),i.jsx("div",{className:"flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto",children:u.map(s=>i.jsxs("div",{className:`group flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-2xs transition-colors ${s.id===n?"bg-surface-overlay text-content-primary":"text-content-muted hover:bg-surface-overlay/60"}`,children:[i.jsx("button",{onClick:()=>f(s.id),className:"cursor-pointer font-mono",children:s.label}),u.length>1&&i.jsx("button",{onClick:()=>y(s.id),className:"cursor-pointer rounded text-content-faint opacity-0 transition hover:text-danger group-hover:opacity-100","aria-label":"关闭终端",children:i.jsx(A,{size:11})})]},s.id))}),i.jsx("button",{onClick:k,className:"ml-1 shrink-0 cursor-pointer rounded-md p-1 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"新建终端","aria-label":"新建终端",children:i.jsx(B,{size:14})})]}),i.jsx(V,{bridge:e,terminalId:n},n)]})}export{N as TerminalPanel};
1
+ import{r as l,j as i}from"./vendor-react-0zU5oKmd.js";import{x as w,a as C}from"./vendor-xterm-D15THcI4.js";import{I as T,a as A,b as B}from"./index-pqslvKcS.js";import"./vendor-highlight-Cus7zBI4.js";function x(e,t){const r=getComputedStyle(document.documentElement).getPropertyValue(e).trim();return r?`rgb(${r})`:t}function $(e,t,r){const u=getComputedStyle(document.documentElement).getPropertyValue(e).trim();return u?`rgb(${u} / ${t})`:r}function E(){return{background:x("--c-canvas","#0b0d0f"),foreground:x("--c-content-primary","#e6e8eb"),cursor:x("--c-accent","#2dd4bf"),selectionBackground:$("--c-accent",.22,"rgba(45,212,191,0.25)"),black:x("--c-content-faint","#4e535a"),brightBlack:x("--c-content-muted","#70767e"),white:x("--c-content-secondary","#9ea5ad"),brightWhite:x("--c-content-primary","#e9ebee")}}function F(e,t){const r=l.useRef(null),u=l.useRef(null),h=l.useRef(null);return l.useEffect(()=>{if(!r.current||!e)return;const n=new w.Terminal({fontFamily:"JetBrains Mono, ui-monospace, monospace",fontSize:13,cursorBlink:!0,theme:E(),scrollback:5e3}),f=new C.FitAddon;n.loadAddon(f),n.open(r.current),f.fit(),u.current=n,h.current=f;const p=e.terminalBuffer(t);p&&n.write(p);const v=n.onData(c=>e.sendInput(t,c)),b=n.onResize(({cols:c,rows:o})=>e.sendResize(t,c,o)),k=e.onEvent(c=>{if(c.type==="terminal.reset"&&c.terminalId===t){n.reset();return}c.type==="terminal.output"&&c.terminalId===t&&n.write(c.data)}),y=new ResizeObserver(()=>{try{f.fit(),e.sendResize(t,n.cols,n.rows)}catch{}});y.observe(r.current);const s=new MutationObserver(()=>{n.options.theme=E()});return s.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]}),e.sendResize(t,n.cols,n.rows),()=>{v.dispose(),b.dispose(),k(),y.disconnect(),s.disconnect(),n.dispose(),u.current=null,h.current=null}},[e,t]),{containerRef:r}}function I(e,t){return e==="default"?"默认":t||e.slice(0,8)}const z=e=>`linkshell:web:lastTerminal:${e}`;function L(e){if(!e)return null;try{return localStorage.getItem(z(e))}catch{return null}}function P(e,t){if(e)try{localStorage.setItem(z(e),t)}catch{}}function O(e,t){if(e.length===0)return null;if(t&&e.includes(t))return t;const r=e.filter(u=>u!=="default");return r.length>0?r[r.length-1]:e[0]}function V({bridge:e,terminalId:t}){const{containerRef:r}=F(e,t);return i.jsx("div",{ref:r,className:"xterm-host min-h-0 flex-1 bg-canvas p-2"})}function N({bridge:e,onNewTerminal:t}){const r=e==null?void 0:e.sessionId,[u,h]=l.useState([]),[n,f]=l.useState("default"),p=l.useRef(!1),v=l.useRef(!1),b=l.useCallback((s,c)=>{h(o=>o.some(m=>m.id===s)?o:[...o,{id:s,label:I(s,c)}])},[]);l.useEffect(()=>{if(!e)return;p.current=!1,v.current=!1;const s=L(r);return e.requestTerminalList(),e.onEvent(o=>{if(o.type==="terminal.output"){b(o.terminalId);return}if(o.type==="terminal.list"){const m=o.envelope,a=m.payload;if(m.type==="terminal.spawned"){const R=a==null?void 0:a.terminalId;R&&(b(R,a==null?void 0:a.projectName),f(R));return}if(m.type==="terminal.list"){const j=(Array.isArray(a==null?void 0:a.terminals)?a.terminals:[]).filter(d=>typeof d.terminalId=="string"&&d.status!=="exited").map(d=>({id:d.terminalId,label:I(d.terminalId,d.projectName)}));if(h(j),!p.current){p.current=!0;const d=j.map(S=>S.id),g=O(d,s);g?f(g):v.current||(v.current=!0,t==null||t())}}}})},[e,b,t]),l.useEffect(()=>{p.current&&n&&P(r,n)},[r,n]);const k=()=>t==null?void 0:t(),y=s=>{e==null||e.killTerminal(s),h(c=>{var m;const o=c.filter(a=>a.id!==s);return s===n&&f(((m=o[o.length-1])==null?void 0:m.id)??"default"),o})};return i.jsxs("div",{className:"flex h-full flex-col bg-canvas",children:[i.jsxs("div",{className:"flex items-center gap-0.5 border-b border-border bg-surface-raised px-1.5 py-1",children:[i.jsx(T,{size:13,className:"mx-1 shrink-0 text-content-faint"}),i.jsx("div",{className:"flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto",children:u.map(s=>i.jsxs("div",{className:`group flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-2xs transition-colors ${s.id===n?"bg-surface-overlay text-content-primary":"text-content-muted hover:bg-surface-overlay/60"}`,children:[i.jsx("button",{onClick:()=>f(s.id),className:"cursor-pointer font-mono",children:s.label}),u.length>1&&i.jsx("button",{onClick:()=>y(s.id),className:"cursor-pointer rounded text-content-faint opacity-0 transition hover:text-danger group-hover:opacity-100","aria-label":"关闭终端",children:i.jsx(A,{size:11})})]},s.id))}),i.jsx("button",{onClick:k,className:"ml-1 shrink-0 cursor-pointer rounded-md p-1 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"新建终端","aria-label":"新建终端",children:i.jsx(B,{size:14})})]}),i.jsx(V,{bridge:e,terminalId:n},n)]})}export{N as TerminalPanel};
@@ -1 +1 @@
1
- import{j as e,r as T}from"./vendor-react-0zU5oKmd.js";import{a as $}from"./index-ByEwVA14.js";import"./vendor-highlight-Cus7zBI4.js";function h(t){return t>=1e8?(t/1e8).toFixed(1)+"亿":t>=1e4?(t/1e4).toFixed(1)+"万":String(Math.round(t))}function S(t){const a=Math.round(t/6e4),n=Math.floor(a/60),l=a%60;return n>0?`${n}小时${l}分`:`${l}分`}const C=53,j=864e5;function k(t){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}function E(t,a){const n=new Map;for(const o of t)n.set(o.key,o.totalTokens);const l=new Date;l.setHours(12,0,0,0);const s=new Date(l.getTime()-(C*7-1)*j);s.setDate(s.getDate()-s.getDay());const r=new Map;if(a==="weekly")for(const[o,d]of n){const i=new Date(o+"T12:00:00");i.setDate(i.getDate()-i.getDay());const x=k(i);r.set(x,(r.get(x)??0)+d)}const c=[],u=[];let N=0,w=0,D=-1;for(let o=new Date(s);o<=l;w++){const d=[];for(let x=0;x<7;x++){const p=new Date(o.getTime()+x*j),M=k(p),y=n.get(M)??0;a==="cumulative"&&(N+=y);let g=y;if(a==="weekly"){const f=new Date(p);f.setDate(f.getDate()-f.getDay()),g=r.get(k(f))??0}else a==="cumulative"&&(g=N);d.push({date:p,key:M,value:g,daily:y})}const i=d[0].date.getMonth();i!==D&&(u.push({col:w,label:`${i+1}月`}),D=i),c.push(d),o=new Date(o.getTime()+7*j)}let b=0;for(const o of c)for(const d of o)b=Math.max(b,d.value);return{weeks:c,max:b,monthLabels:u}}function F(t,a){if(t<=0||a<=0)return{className:"bg-surface-raised"};const n=t/a;return{className:"",style:{backgroundColor:`rgba(96,165,250,${n>.66?1:n>.33?.72:n>.1?.48:.28})`}}}function m({value:t,label:a}){return e.jsxs("div",{className:"flex flex-col items-center justify-center gap-1 px-4 py-3",children:[e.jsx("span",{className:"whitespace-nowrap font-mono text-xl font-semibold text-content-primary tabular-nums",children:t}),e.jsx("span",{className:"text-2xs text-content-muted",children:a})]})}function v({title:t,buckets:a}){const n=a.slice(0,6),l=n.reduce((s,r)=>Math.max(s,r.totalTokens),0);return n.length===0?null:e.jsxs("div",{className:"rounded-xl border border-border bg-surface p-4",children:[e.jsx("p",{className:"mb-3 text-2xs font-semibold uppercase tracking-wider text-content-faint",children:t}),e.jsx("div",{className:"space-y-2",children:n.map(s=>e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"w-28 shrink-0 truncate text-xs text-content-secondary",title:s.key,children:s.key}),e.jsx("div",{className:"relative h-2 flex-1 overflow-hidden rounded-full bg-surface-raised",children:e.jsx("div",{className:"absolute inset-y-0 left-0 rounded-full",style:{width:`${l>0?s.totalTokens/l*100:0}%`,backgroundColor:"rgba(96,165,250,0.7)"}})}),e.jsx("span",{className:"w-14 shrink-0 text-right font-mono text-2xs tabular-nums text-content-muted",children:h(s.totalTokens)})]},s.key))})]})}function z({report:t}){const[a,n]=T.useState("daily"),l=T.useMemo(()=>t?E(t.byDay,a):null,[t,a]);return t?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{className:"grid grid-cols-2 divide-x divide-border rounded-2xl border border-border bg-surface sm:grid-cols-5",children:[e.jsx(m,{value:h(t.totals.totalTokens),label:"累计 Token 数"}),e.jsx(m,{value:h(t.peakDayTokens),label:"峰值 Token 数"}),e.jsx(m,{value:S(t.longestTaskMs),label:"最长任务时长"}),e.jsx(m,{value:`${t.currentStreakDays} 天`,label:"当前连续天数"}),e.jsx(m,{value:`${t.longestStreakDays} 天`,label:"最长连续天数"})]}),e.jsxs("div",{className:"rounded-2xl border border-border bg-surface p-5",children:[e.jsxs("div",{className:"mb-4 flex items-center justify-between",children:[e.jsx("h3",{className:"text-sm font-semibold text-content-primary",children:"Token 活动"}),e.jsx("div",{className:"flex items-center gap-1 text-2xs",children:[["daily","每日"],["weekly","每周"],["cumulative","累计"]].map(([s,r])=>e.jsx("button",{onClick:()=>n(s),className:`rounded-md px-2 py-1 transition-colors ${a===s?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-secondary"}`,children:r},s))})]}),l&&e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("div",{className:"inline-flex flex-col gap-1",children:[e.jsx("div",{className:"flex gap-1",children:l.weeks.map((s,r)=>e.jsx("div",{className:"flex flex-col gap-1",children:s.map(c=>{const u=F(c.value,l.max);return e.jsx("div",{className:`h-3 w-3 rounded-[3px] ${u.className}`,style:u.style,title:`${c.key} · ${h(c.daily)} tokens`},c.key)})},r))}),e.jsx("div",{className:"relative mt-1.5 h-4 text-2xs text-content-faint",children:l.monthLabels.map(s=>e.jsx("span",{className:"absolute",style:{left:`${s.col*16}px`},children:s.label},s.col))})]})})]}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 sm:grid-cols-3",children:[e.jsx(v,{title:"工具分布",buckets:t.byTool}),e.jsx(v,{title:"模型分布",buckets:t.byModel}),e.jsx(v,{title:"项目分布",buckets:t.byProject})]})]}):e.jsxs("div",{className:"flex flex-col items-center justify-center gap-3 py-12 text-content-muted",children:[e.jsx("span",{className:"h-6 w-6 animate-spin rounded-full border-2 border-content-faint border-t-accent"}),e.jsx("p",{className:"text-sm",children:"正在扫描本机会话记录…"})]})}function L({report:t,onClose:a}){return e.jsxs("div",{className:"fixed inset-0 z-50 flex flex-col bg-canvas/95 backdrop-blur-sm",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-5 py-3",children:[e.jsx("h2",{className:"text-base font-semibold text-content-primary",children:"用量统计"}),e.jsx("button",{onClick:a,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭",children:e.jsx($,{size:16})})]}),e.jsx("div",{className:"mx-auto w-full max-w-5xl flex-1 overflow-y-auto px-5 py-6",children:e.jsx(z,{report:t})})]})}export{L as UsageDashboard,z as UsageDashboardContent};
1
+ import{j as e,r as T}from"./vendor-react-0zU5oKmd.js";import{a as $}from"./index-pqslvKcS.js";import"./vendor-highlight-Cus7zBI4.js";function h(t){return t>=1e8?(t/1e8).toFixed(1)+"亿":t>=1e4?(t/1e4).toFixed(1)+"万":String(Math.round(t))}function S(t){const a=Math.round(t/6e4),n=Math.floor(a/60),l=a%60;return n>0?`${n}小时${l}分`:`${l}分`}const C=53,j=864e5;function k(t){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}function E(t,a){const n=new Map;for(const o of t)n.set(o.key,o.totalTokens);const l=new Date;l.setHours(12,0,0,0);const s=new Date(l.getTime()-(C*7-1)*j);s.setDate(s.getDate()-s.getDay());const r=new Map;if(a==="weekly")for(const[o,d]of n){const i=new Date(o+"T12:00:00");i.setDate(i.getDate()-i.getDay());const x=k(i);r.set(x,(r.get(x)??0)+d)}const c=[],u=[];let N=0,w=0,D=-1;for(let o=new Date(s);o<=l;w++){const d=[];for(let x=0;x<7;x++){const p=new Date(o.getTime()+x*j),M=k(p),y=n.get(M)??0;a==="cumulative"&&(N+=y);let g=y;if(a==="weekly"){const f=new Date(p);f.setDate(f.getDate()-f.getDay()),g=r.get(k(f))??0}else a==="cumulative"&&(g=N);d.push({date:p,key:M,value:g,daily:y})}const i=d[0].date.getMonth();i!==D&&(u.push({col:w,label:`${i+1}月`}),D=i),c.push(d),o=new Date(o.getTime()+7*j)}let b=0;for(const o of c)for(const d of o)b=Math.max(b,d.value);return{weeks:c,max:b,monthLabels:u}}function F(t,a){if(t<=0||a<=0)return{className:"bg-surface-raised"};const n=t/a;return{className:"",style:{backgroundColor:`rgba(96,165,250,${n>.66?1:n>.33?.72:n>.1?.48:.28})`}}}function m({value:t,label:a}){return e.jsxs("div",{className:"flex flex-col items-center justify-center gap-1 px-4 py-3",children:[e.jsx("span",{className:"whitespace-nowrap font-mono text-xl font-semibold text-content-primary tabular-nums",children:t}),e.jsx("span",{className:"text-2xs text-content-muted",children:a})]})}function v({title:t,buckets:a}){const n=a.slice(0,6),l=n.reduce((s,r)=>Math.max(s,r.totalTokens),0);return n.length===0?null:e.jsxs("div",{className:"rounded-xl border border-border bg-surface p-4",children:[e.jsx("p",{className:"mb-3 text-2xs font-semibold uppercase tracking-wider text-content-faint",children:t}),e.jsx("div",{className:"space-y-2",children:n.map(s=>e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"w-28 shrink-0 truncate text-xs text-content-secondary",title:s.key,children:s.key}),e.jsx("div",{className:"relative h-2 flex-1 overflow-hidden rounded-full bg-surface-raised",children:e.jsx("div",{className:"absolute inset-y-0 left-0 rounded-full",style:{width:`${l>0?s.totalTokens/l*100:0}%`,backgroundColor:"rgba(96,165,250,0.7)"}})}),e.jsx("span",{className:"w-14 shrink-0 text-right font-mono text-2xs tabular-nums text-content-muted",children:h(s.totalTokens)})]},s.key))})]})}function z({report:t}){const[a,n]=T.useState("daily"),l=T.useMemo(()=>t?E(t.byDay,a):null,[t,a]);return t?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{className:"grid grid-cols-2 divide-x divide-border rounded-2xl border border-border bg-surface sm:grid-cols-5",children:[e.jsx(m,{value:h(t.totals.totalTokens),label:"累计 Token 数"}),e.jsx(m,{value:h(t.peakDayTokens),label:"峰值 Token 数"}),e.jsx(m,{value:S(t.longestTaskMs),label:"最长任务时长"}),e.jsx(m,{value:`${t.currentStreakDays} 天`,label:"当前连续天数"}),e.jsx(m,{value:`${t.longestStreakDays} 天`,label:"最长连续天数"})]}),e.jsxs("div",{className:"rounded-2xl border border-border bg-surface p-5",children:[e.jsxs("div",{className:"mb-4 flex items-center justify-between",children:[e.jsx("h3",{className:"text-sm font-semibold text-content-primary",children:"Token 活动"}),e.jsx("div",{className:"flex items-center gap-1 text-2xs",children:[["daily","每日"],["weekly","每周"],["cumulative","累计"]].map(([s,r])=>e.jsx("button",{onClick:()=>n(s),className:`rounded-md px-2 py-1 transition-colors ${a===s?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-secondary"}`,children:r},s))})]}),l&&e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("div",{className:"inline-flex flex-col gap-1",children:[e.jsx("div",{className:"flex gap-1",children:l.weeks.map((s,r)=>e.jsx("div",{className:"flex flex-col gap-1",children:s.map(c=>{const u=F(c.value,l.max);return e.jsx("div",{className:`h-3 w-3 rounded-[3px] ${u.className}`,style:u.style,title:`${c.key} · ${h(c.daily)} tokens`},c.key)})},r))}),e.jsx("div",{className:"relative mt-1.5 h-4 text-2xs text-content-faint",children:l.monthLabels.map(s=>e.jsx("span",{className:"absolute",style:{left:`${s.col*16}px`},children:s.label},s.col))})]})})]}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 sm:grid-cols-3",children:[e.jsx(v,{title:"工具分布",buckets:t.byTool}),e.jsx(v,{title:"模型分布",buckets:t.byModel}),e.jsx(v,{title:"项目分布",buckets:t.byProject})]})]}):e.jsxs("div",{className:"flex flex-col items-center justify-center gap-3 py-12 text-content-muted",children:[e.jsx("span",{className:"h-6 w-6 animate-spin rounded-full border-2 border-content-faint border-t-accent"}),e.jsx("p",{className:"text-sm",children:"正在扫描本机会话记录…"})]})}function L({report:t,onClose:a}){return e.jsxs("div",{className:"fixed inset-0 z-50 flex flex-col bg-canvas/95 backdrop-blur-sm",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-5 py-3",children:[e.jsx("h2",{className:"text-base font-semibold text-content-primary",children:"用量统计"}),e.jsx("button",{onClick:a,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭",children:e.jsx($,{size:16})})]}),e.jsx("div",{className:"mx-auto w-full max-w-5xl flex-1 overflow-y-auto px-5 py-6",children:e.jsx(z,{report:t})})]})}export{L as UsageDashboard,z as UsageDashboardContent};