@tom2012/cc-web 1.5.20 → 1.5.21
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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# CC Web
|
|
2
2
|
|
|
3
|
-
A self-hosted web application (
|
|
3
|
+
A self-hosted web application (distributed as npm package) that provides a browser-based interface for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI sessions. Create projects, each with a persistent terminal running Claude Code, and interact with them through a real-time terminal UI.
|
|
4
4
|
|
|
5
|
-
**Current version**: v1.5.
|
|
5
|
+
**Current version**: v1.5.21 | [GitHub](https://github.com/zbc0315/cc-web) | MIT License
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ A self-hosted web application (and macOS Electron desktop app) that provides a b
|
|
|
16
16
|
- **File Browser**: Browse directories and preview/edit files with zoom-level memory per file
|
|
17
17
|
- **Auto-restart**: Terminals automatically recover from crashes
|
|
18
18
|
- **Usage Tracking**: Monitor Claude Code plan usage directly from the dashboard
|
|
19
|
-
- **
|
|
19
|
+
- **CLI Update**: `ccweb update` stops the server and updates to the latest npm version
|
|
20
20
|
- **Localhost Auto-auth**: Local access skips login entirely; JWT only required for remote access
|
|
21
21
|
- **Auto Port Switching**: Backend tries ports 3001–3020 and reports the actual port
|
|
22
22
|
- **Network Access Modes**: Local only (127.0.0.1), LAN (private IPs), or public — selectable at startup
|
|
@@ -57,6 +57,7 @@ ccweb stop # stop background server
|
|
|
57
57
|
ccweb status # show PID, port, data location
|
|
58
58
|
ccweb open # open browser to running server
|
|
59
59
|
ccweb setup # reconfigure username / password
|
|
60
|
+
ccweb update # stop server & update to latest version
|
|
60
61
|
ccweb enable-autostart # start automatically on login
|
|
61
62
|
ccweb disable-autostart # remove auto-start
|
|
62
63
|
ccweb logs # tail background log file
|
|
@@ -116,7 +117,7 @@ Browser (React/Vite :5173 dev | Express :3001 prod)
|
|
|
116
117
|
| `usage-terminal.ts` | Claude Code OAuth usage stats |
|
|
117
118
|
| `routes/auth.ts` | `POST /login`, `GET /local-token` (localhost only) |
|
|
118
119
|
| `routes/projects.ts` | CRUD + start/stop + `POST /open` (restore from `.ccweb/`) |
|
|
119
|
-
| `routes/update.ts` | `GET /check-running`, `POST /prepare` (save memory → wait idle → stop all) |
|
|
120
|
+
| `routes/update.ts` | `GET /check-running`, `POST /prepare` (save memory → wait idle → stop all, used by in-app update flow) |
|
|
120
121
|
| `routes/filesystem.ts` | Directory browser, file read/write |
|
|
121
122
|
| `routes/shortcuts.ts` | Global shortcut CRUD with inheritance |
|
|
122
123
|
| `routes/backup.ts` | Cloud backup API (providers, OAuth, backup trigger, schedule) |
|
|
@@ -140,7 +141,7 @@ Browser (React/Vite :5173 dev | Express :3001 prod)
|
|
|
140
141
|
| `components/GraphPreview.tsx` | SVG topology graph of `.notebook/graph.yaml` (layered DAG, zoom/pan) |
|
|
141
142
|
| `components/FileTree.tsx` | Expandable directory tree |
|
|
142
143
|
| `components/FilePreviewDialog.tsx` | File viewer with plain/rendered/edit modes, zoom memory per file |
|
|
143
|
-
| `components/UpdateButton.tsx` |
|
|
144
|
+
| `components/UpdateButton.tsx` | Version display and update check |
|
|
144
145
|
| `components/OpenProjectDialog.tsx` | Open existing project from `.ccweb/` folder |
|
|
145
146
|
| `components/NewProjectDialog.tsx` | 3-step wizard: name → folder → permissions |
|
|
146
147
|
| `lib/api.ts` | Typed REST client, dynamic base URL (relative in prod, localhost:3001 in dev) |
|
|
@@ -149,7 +150,7 @@ Browser (React/Vite :5173 dev | Express :3001 prod)
|
|
|
149
150
|
|
|
150
151
|
### Data Storage
|
|
151
152
|
|
|
152
|
-
**Application data** (
|
|
153
|
+
**Application data** (`~/.ccweb/` for npm install, `data/` for dev):
|
|
153
154
|
|
|
154
155
|
```
|
|
155
156
|
data/
|
|
@@ -226,33 +227,7 @@ Localhost WebSocket connections are pre-authenticated — no `auth` message need
|
|
|
226
227
|
| `GET/PUT` | `/api/backup/excludes` | Exclude patterns |
|
|
227
228
|
| `GET` | `/api/backup/history` | Backup history |
|
|
228
229
|
|
|
229
|
-
##
|
|
230
|
-
|
|
231
|
-
CC Web can be packaged as a standalone macOS app using Electron.
|
|
232
|
-
|
|
233
|
-
```bash
|
|
234
|
-
# Install all dependencies first
|
|
235
|
-
npm run install:all
|
|
236
|
-
npm install
|
|
237
|
-
|
|
238
|
-
# Build DMG (outputs to release/)
|
|
239
|
-
npm run dist:dmg
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
The DMG will be at `release/CCWeb-{version}-arm64.dmg`. Double-click to install.
|
|
243
|
-
|
|
244
|
-
On first launch, the app auto-generates login credentials and displays them in a dialog. Local access (localhost) skips login entirely. You'll need `claude` CLI installed and authenticated on your machine.
|
|
245
|
-
|
|
246
|
-
**Update flow**: Download zip from GitHub releases → extract with `ditto` → replace app via detached shell script (no code signing required).
|
|
247
|
-
|
|
248
|
-
### Building for other architectures
|
|
249
|
-
|
|
250
|
-
Edit the `arch` field in `package.json` under `build.mac.target`:
|
|
251
|
-
- `["arm64"]` — Apple Silicon (default)
|
|
252
|
-
- `["x64"]` — Intel Mac
|
|
253
|
-
- `["arm64", "x64"]` — Universal
|
|
254
|
-
|
|
255
|
-
## Server Deployment (without Electron)
|
|
230
|
+
## Server Deployment
|
|
256
231
|
|
|
257
232
|
```bash
|
|
258
233
|
# Build everything
|
|
@@ -279,37 +254,26 @@ Environment variables:
|
|
|
279
254
|
## Build & Release
|
|
280
255
|
|
|
281
256
|
```bash
|
|
282
|
-
# Full build (frontend + backend
|
|
283
|
-
npm run build
|
|
284
|
-
|
|
285
|
-
# Build DMG + ZIP for release
|
|
286
|
-
npm run dist
|
|
257
|
+
# Full build (frontend + backend)
|
|
258
|
+
npm run build
|
|
287
259
|
|
|
288
260
|
# Release checklist:
|
|
289
|
-
# 1. Bump version in package.json
|
|
290
|
-
# 2. Update
|
|
291
|
-
# 3. npm run
|
|
261
|
+
# 1. Bump version in package.json, UpdateButton.tsx, README.md, CLAUDE.md
|
|
262
|
+
# 2. Update docs with new features
|
|
263
|
+
# 3. npm run build
|
|
292
264
|
# 4. git add -A && git commit && git push
|
|
293
|
-
# 5.
|
|
294
|
-
# release/CCWeb-X.Y.Z-arm64.dmg \
|
|
295
|
-
# release/CCWeb-X.Y.Z-arm64-mac.zip \
|
|
296
|
-
# release/CCWeb-X.Y.Z-arm64-mac.zip.blockmap \
|
|
297
|
-
# release/latest-mac.yml
|
|
265
|
+
# 5. npm publish --registry https://registry.npmjs.org --access=public
|
|
298
266
|
```
|
|
299
267
|
|
|
300
|
-
> **Note**: `productName` in `package.json` must not contain spaces (currently `CCWeb`), otherwise GitHub mangles the filename and auto-update gets 404.
|
|
301
|
-
|
|
302
268
|
## Development Guide
|
|
303
269
|
|
|
304
270
|
### Project Structure
|
|
305
271
|
|
|
306
272
|
```
|
|
307
273
|
cc-web/
|
|
308
|
-
├── package.json ← Root scripts +
|
|
274
|
+
├── package.json ← Root scripts + npm package config
|
|
275
|
+
├── bin/ccweb.js ← CLI entry point (ccweb command)
|
|
309
276
|
├── setup.js ← Interactive credential setup
|
|
310
|
-
├── electron/
|
|
311
|
-
│ ├── main.ts ← Electron main process
|
|
312
|
-
│ └── tsconfig.json
|
|
313
277
|
├── backend/
|
|
314
278
|
│ ├── package.json
|
|
315
279
|
│ ├── tsconfig.json
|
|
@@ -331,7 +295,7 @@ cc-web/
|
|
|
331
295
|
- **Scrollback buffer**: 5 MB per terminal for client reconnect replay.
|
|
332
296
|
- **Session pruning**: Keeps latest 20 sessions per project, deletes oldest on new session start.
|
|
333
297
|
- **Zoom memory**: `FilePreviewDialog` persists zoom level per file path in `localStorage`.
|
|
334
|
-
- **Auto port switching**: Backend tries ports 3001–3020, reports actual port
|
|
298
|
+
- **Auto port switching**: Backend tries ports 3001–3020, reports actual port via IPC.
|
|
335
299
|
- **Localhost auto-auth**: Local requests skip JWT verification entirely.
|
|
336
300
|
|
|
337
301
|
### Adding a New API Endpoint
|
|
@@ -351,7 +315,6 @@ cc-web/
|
|
|
351
315
|
|
|
352
316
|
**Backend**: Node.js, Express, WebSocket (ws), node-pty, TypeScript
|
|
353
317
|
**Frontend**: React 18, Vite, Tailwind CSS, shadcn/ui, xterm.js, TypeScript
|
|
354
|
-
**Desktop**: Electron
|
|
355
318
|
**Auth**: JWT (bcryptjs for password hashing)
|
|
356
319
|
|
|
357
320
|
## License
|
package/bin/ccweb.js
CHANGED
|
@@ -405,6 +405,36 @@ function showStatus() {
|
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
+
function updatePackage() {
|
|
409
|
+
// Stop running server first
|
|
410
|
+
const status = getStatus();
|
|
411
|
+
if (status.running) {
|
|
412
|
+
console.log('Stopping CCWeb...');
|
|
413
|
+
try {
|
|
414
|
+
process.kill(status.pid, 'SIGTERM');
|
|
415
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
416
|
+
try { fs.unlinkSync(PORT_FILE); } catch {}
|
|
417
|
+
console.log(`Stopped (PID ${status.pid}).`);
|
|
418
|
+
} catch (err) {
|
|
419
|
+
console.error('Failed to stop:', err.message);
|
|
420
|
+
}
|
|
421
|
+
// Brief wait for process to fully exit
|
|
422
|
+
const deadline = Date.now() + 5000;
|
|
423
|
+
while (Date.now() < deadline && isProcessRunning(status.pid)) {
|
|
424
|
+
execSync('sleep 0.2');
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
console.log('\nUpdating @tom2012/cc-web to latest version...\n');
|
|
429
|
+
try {
|
|
430
|
+
execSync('npm install -g @tom2012/cc-web@latest', { stdio: 'inherit' });
|
|
431
|
+
console.log('\nUpdate complete! Run "ccweb" to start the new version.');
|
|
432
|
+
} catch (err) {
|
|
433
|
+
console.error('\nUpdate failed:', err.message);
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
408
438
|
function showHelp() {
|
|
409
439
|
console.log(`
|
|
410
440
|
CCWeb — Self-hosted Claude Code web interface
|
|
@@ -421,6 +451,7 @@ Usage:
|
|
|
421
451
|
ccweb status Show running status
|
|
422
452
|
ccweb open Open browser to running server
|
|
423
453
|
ccweb setup Reconfigure username / password
|
|
454
|
+
ccweb update Update to latest version
|
|
424
455
|
ccweb enable-autostart Enable auto-start on login
|
|
425
456
|
ccweb disable-autostart Disable auto-start on login
|
|
426
457
|
ccweb logs Tail log file (background mode)
|
|
@@ -480,6 +511,10 @@ const accessModeFlag = args.includes('--local') ? 'local'
|
|
|
480
511
|
await disableAutoStart();
|
|
481
512
|
break;
|
|
482
513
|
|
|
514
|
+
case 'update':
|
|
515
|
+
updatePackage();
|
|
516
|
+
break;
|
|
517
|
+
|
|
483
518
|
case 'logs': {
|
|
484
519
|
if (!fs.existsSync(LOG_FILE)) { console.log('No log file found.'); break; }
|
|
485
520
|
// Tail the log file (cross-platform)
|
|
@@ -403,7 +403,7 @@ Error generating stack: `+s.message+`
|
|
|
403
403
|
|
|
404
404
|
If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component.
|
|
405
405
|
|
|
406
|
-
For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return y.useEffect(()=>{e&&(document.getElementById(e)||console.error(n))},[n,e]),null},i3="DialogDescriptionWarning",s3=({contentRef:e,descriptionId:t})=>{const r=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${gA(i3).contentName}}.`;return y.useEffect(()=>{var s;const i=(s=e.current)==null?void 0:s.getAttribute("aria-describedby");t&&i&&(document.getElementById(t)||console.warn(r))},[r,e,t]),null},a3=nA,o3=sA,mA=aA,bA=oA,_A=cA,yA=dA,l3=pA;const ul=a3,c3=o3,vA=y.forwardRef(({className:e,...t},n)=>h.jsx(mA,{ref:n,className:ee("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e),...t}));vA.displayName=mA.displayName;const fa=y.forwardRef(({className:e,children:t,...n},r)=>h.jsxs(c3,{children:[h.jsx(vA,{}),h.jsxs(bA,{ref:r,className:ee("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",e),...n,children:[t,h.jsxs(l3,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",children:[h.jsx(rs,{className:"h-4 w-4"}),h.jsx("span",{className:"sr-only",children:"Close"})]})]})]}));fa.displayName=bA.displayName;const ga=({className:e,...t})=>h.jsx("div",{className:ee("flex flex-col space-y-1.5 text-center sm:text-left",e),...t});ga.displayName="DialogHeader";const ma=({className:e,...t})=>h.jsx("div",{className:ee("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",e),...t});ma.displayName="DialogFooter";const ba=y.forwardRef(({className:e,...t},n)=>h.jsx(_A,{ref:n,className:ee("text-lg font-semibold leading-none tracking-tight",e),...t}));ba.displayName=_A.displayName;const _a=y.forwardRef(({className:e,...t},n)=>h.jsx(yA,{ref:n,className:ee("text-sm text-muted-foreground",e),...t}));_a.displayName=yA.displayName;function SA({onSelect:e}){const[t,n]=y.useState(""),[r,i]=y.useState(null),[s,a]=y.useState([]),[o,l]=y.useState(!0),[c,d]=y.useState(null),[u,p]=y.useState(!1),[f,g]=y.useState(""),[b,v]=y.useState(null),[m,_]=y.useState(!1),S=y.useRef(null),w=async O=>{l(!0),d(null);try{const A=await DT(O);n(A.path),i(A.parent),a(A.entries)}catch(A){d(A instanceof Error?A.message:"Failed to load directory")}finally{l(!1)}};y.useEffect(()=>{w()},[]),y.useEffect(()=>{u&&setTimeout(()=>{var O;return(O=S.current)==null?void 0:O.focus()},0)},[u]);const k=()=>{g(""),v(null),p(!0)},E=()=>{p(!1),g(""),v(null)},C=async()=>{const O=f.trim();if(O){_(!0),v(null);try{const{path:A}=await AP(t,O);p(!1),g(""),await w(t),w(A)}catch(A){v(A instanceof Error?A.message:"Failed to create folder")}finally{_(!1)}}},R=O=>{O.key==="Enter"?(O.preventDefault(),C()):O.key==="Escape"&&E()},M=t.split("/").filter(Boolean),N=O=>{const A="/"+M.slice(0,O+1).join("/");w(A)};return h.jsxs("div",{className:"flex flex-col h-64 border rounded-md overflow-hidden",children:[h.jsxs("div",{className:"flex items-center gap-1 px-2 py-2 bg-muted border-b flex-shrink-0",children:[h.jsx(re,{variant:"ghost",size:"sm",className:"h-6 px-1 flex-shrink-0",onClick:()=>void w(),title:"Home",children:h.jsx(sF,{className:"h-3 w-3"})}),h.jsx("div",{className:"flex items-center gap-1 flex-1 overflow-x-auto min-w-0",children:M.map((O,A)=>h.jsxs(ft.Fragment,{children:[h.jsx(Em,{className:"h-3 w-3 text-muted-foreground flex-shrink-0"}),h.jsx("button",{className:"text-xs hover:text-foreground text-muted-foreground whitespace-nowrap",onClick:()=>N(A),children:O})]},A))}),h.jsxs(re,{variant:"ghost",size:"sm",className:"h-6 px-1.5 flex-shrink-0 gap-1 text-xs",onClick:k,disabled:u||o||!!c,title:"New folder",children:[h.jsx(iF,{className:"h-3.5 w-3.5"}),h.jsx("span",{className:"hidden sm:inline",children:"New folder"})]})]}),h.jsxs("div",{className:"flex-1 overflow-y-auto",children:[o&&h.jsx("div",{className:"flex items-center justify-center h-full text-sm text-muted-foreground",children:"Loading..."}),c&&h.jsx("div",{className:"flex items-center justify-center h-full text-sm text-destructive px-4 text-center",children:c}),!o&&!c&&h.jsxs("div",{className:"p-1",children:[r&&h.jsxs("button",{className:"flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded hover:bg-accent hover:text-accent-foreground",onClick:()=>void w(r),children:[h.jsx(du,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("span",{children:".."})]}),u&&h.jsxs("div",{className:"flex items-center gap-1 px-2 py-1.5",children:[h.jsx(uf,{className:"h-4 w-4 text-blue-500 flex-shrink-0"}),h.jsx("input",{ref:S,className:ee("flex-1 text-sm bg-background border rounded px-1.5 py-0.5 outline-none","focus:ring-1 focus:ring-ring",b&&"border-destructive"),placeholder:"Folder name",value:f,onChange:O=>{g(O.target.value),v(null)},onKeyDown:R,disabled:m}),h.jsx("button",{className:"p-1 rounded hover:bg-accent disabled:opacity-50",onClick:()=>void C(),disabled:!f.trim()||m,title:"Create",children:h.jsx(BT,{className:"h-3.5 w-3.5 text-green-600"})}),h.jsx("button",{className:"p-1 rounded hover:bg-accent",onClick:E,disabled:m,title:"Cancel",children:h.jsx(rs,{className:"h-3.5 w-3.5 text-muted-foreground"})})]}),b&&h.jsx("p",{className:"px-3 pb-1 text-xs text-destructive",children:b}),s.length===0&&!u&&h.jsx("div",{className:"text-sm text-muted-foreground px-2 py-4 text-center",children:"Empty directory"}),s.map(O=>h.jsxs("button",{className:ee("flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded text-left",O.type==="dir"?"hover:bg-accent hover:text-accent-foreground cursor-pointer":"opacity-40 cursor-not-allowed"),onClick:()=>{O.type==="dir"&&w(O.path)},disabled:O.type==="file",children:[O.type==="dir"?h.jsx(uf,{className:"h-4 w-4 text-blue-500 flex-shrink-0"}):h.jsx(rF,{className:"h-4 w-4 text-muted-foreground flex-shrink-0"}),h.jsx("span",{className:"truncate",children:O.name})]},O.path))]})]}),h.jsxs("div",{className:"border-t px-3 py-2 flex items-center justify-between gap-2 flex-shrink-0 bg-background",children:[h.jsx("span",{className:"text-xs text-muted-foreground truncate flex-1",children:t}),h.jsx(re,{size:"sm",onClick:()=>e(t),disabled:!t,children:"Select this folder"})]})]})}const u3=[{value:"claude",label:"Claude",desc:"Anthropic Claude Code CLI"},{value:"opencode",label:"OpenCode",desc:"OpenCode CLI (sst/opencode)"},{value:"codex",label:"Codex",desc:"OpenAI Codex CLI"},{value:"qwen",label:"Qwen",desc:"Qwen Code CLI (QwenLM)"}],fx={claude:{limited:"claude",unlimited:"claude --dangerously-skip-permissions"},opencode:{limited:"opencode",unlimited:"opencode --dangerously-skip-permissions"},codex:{limited:"codex",unlimited:"codex --ask-for-approval never --sandbox danger-full-access"},qwen:{limited:"qwen-code",unlimited:"qwen-code --yolo"}};function d3({open:e,onOpenChange:t,onCreated:n}){const[r,i]=y.useState("name"),[s,a]=y.useState(""),[o,l]=y.useState(""),[c,d]=y.useState("claude"),[u,p]=y.useState("limited"),[f,g]=y.useState(!1),[b,v]=y.useState(null),m=()=>{i("name"),a(""),l(""),d("claude"),p("limited"),v(null),g(!1)},_=R=>{R||m(),t(R)},S=()=>{if(!s.trim()){v("Please enter a project name");return}v(null),i("folder")},w=R=>{l(R),i("settings")},k=async()=>{g(!0),v(null);try{const R=await uP({name:s.trim(),folderPath:o,permissionMode:u,cliTool:c});n(R),_(!1)}catch(R){v(R instanceof Error?R.message:"Failed to create project")}finally{g(!1)}},E={name:"New Project — Name",folder:"New Project — Select Folder",settings:"New Project — Settings"},C=["name","folder","settings"];return h.jsx(ul,{open:e,onOpenChange:_,children:h.jsxs(fa,{className:"sm:max-w-lg",children:[h.jsxs(ga,{children:[h.jsx(ba,{children:E[r]}),h.jsxs(_a,{children:[r==="name"&&"Give your project a name.",r==="folder"&&"Choose the working directory for this project.",r==="settings"&&"Choose the AI coding tool and run mode."]})]}),h.jsx("div",{className:"flex gap-2 mb-2",children:C.map((R,M)=>h.jsx("div",{className:`h-1.5 flex-1 rounded-full ${r===R?"bg-primary":C.indexOf(r)>M?"bg-primary/40":"bg-muted"}`},R))}),r==="name"&&h.jsxs("div",{className:"space-y-4",children:[h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{htmlFor:"project-name",children:"Project Name"}),h.jsx(gr,{id:"project-name",placeholder:"My Project",value:s,onChange:R=>a(R.target.value),onKeyDown:R=>{R.key==="Enter"&&S()},autoFocus:!0})]}),b&&h.jsx("p",{className:"text-sm text-destructive",children:b})]}),r==="folder"&&h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Working Directory"}),h.jsx(SA,{onSelect:w})]}),r==="settings"&&h.jsxs("div",{className:"space-y-5",children:[h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"AI Coding Tool"}),h.jsx("div",{className:"grid grid-cols-2 gap-2",children:u3.map(R=>h.jsxs("label",{className:`flex items-center gap-2.5 cursor-pointer p-3 rounded-md border transition-colors ${c===R.value?"border-primary bg-primary/5":"hover:bg-accent"}`,children:[h.jsx("input",{type:"radio",name:"cliTool",value:R.value,checked:c===R.value,onChange:()=>d(R.value),className:"accent-primary"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:R.label}),h.jsx("div",{className:"text-xs text-muted-foreground",children:R.desc})]})]},R.value))})]}),h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Permission Mode"}),h.jsxs("div",{className:"space-y-2",children:[h.jsxs("label",{className:"flex items-start gap-3 cursor-pointer p-3 rounded-md border hover:bg-accent transition-colors",children:[h.jsx("input",{type:"radio",name:"permissionMode",value:"limited",checked:u==="limited",onChange:()=>p("limited"),className:"mt-0.5"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:"Limited"}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:["Runs ",h.jsx("code",{className:"bg-muted px-1 rounded",children:fx[c].limited})," — asks for permission before file changes."]})]})]}),h.jsxs("label",{className:"flex items-start gap-3 cursor-pointer p-3 rounded-md border hover:bg-accent transition-colors",children:[h.jsx("input",{type:"radio",name:"permissionMode",value:"unlimited",checked:u==="unlimited",onChange:()=>p("unlimited"),className:"mt-0.5"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:"Unlimited"}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:["Runs ",h.jsx("code",{className:"bg-muted px-1 rounded",children:fx[c].unlimited})," — acts autonomously."]})]})]})]})]}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:[h.jsx("span",{className:"font-medium",children:"Folder:"})," ",h.jsx("span",{className:"font-mono",children:o})]}),b&&h.jsx("p",{className:"text-sm text-destructive",children:b})]}),h.jsxs(ma,{children:[r!=="name"&&h.jsx(re,{variant:"outline",onClick:()=>i(r==="settings"?"folder":"name"),disabled:f,children:"Back"}),r==="name"&&h.jsx(re,{onClick:S,children:"Next"}),r==="folder"&&h.jsx(re,{variant:"outline",onClick:()=>t(!1),children:"Cancel"}),r==="settings"&&h.jsx(re,{onClick:()=>void k(),disabled:f,children:f?"Creating...":"Create Project"})]})]})})}function h3({open:e,onOpenChange:t,onOpened:n}){const[r,i]=y.useState(""),[s,a]=y.useState(!1),[o,l]=y.useState(null),c=()=>{i(""),l(null),a(!1)},d=p=>{p||c(),t(p)},u=async p=>{i(p),a(!0),l(null);try{const f=await dP(p);n(f),d(!1)}catch(f){l(f instanceof Error?f.message:"Failed to open project"),a(!1)}};return h.jsx(ul,{open:e,onOpenChange:d,children:h.jsxs(fa,{className:"sm:max-w-lg",children:[h.jsxs(ga,{children:[h.jsx(ba,{children:"Open Existing Project"}),h.jsxs(_a,{children:["Select a folder that contains a ",h.jsx("code",{className:"bg-muted px-1 rounded text-xs",children:".ccweb/"})," configuration. The project's history and settings will be restored."]})]}),h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Project Folder"}),h.jsx(SA,{onSelect:p=>void u(p)})]}),o&&h.jsx("p",{className:"text-sm text-destructive",children:o}),r&&!o&&s&&h.jsxs("p",{className:"text-sm text-muted-foreground",children:["Opening project from ",r,"..."]}),h.jsx(ma,{children:h.jsx(re,{variant:"outline",onClick:()=>d(!1),disabled:s,children:"Cancel"})})]})})}function p3(e){if(!e)return"";const t=new Date(e).getTime()-Date.now();if(t<=0)return"即将重置";const n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4);return n>0?`${n}h${r}m`:`${r}m`}function Yl({label:e,bucket:t}){if(!t||t.utilization===void 0)return null;const n=t.utilization,r=n<50?"text-green-400":n<80?"text-yellow-400":"text-red-400",i=p3(t.resetAt);return h.jsxs("span",{className:"text-muted-foreground whitespace-nowrap",children:[e," ",h.jsxs("span",{className:ee("font-medium",r),children:[n,"%"]}),i&&h.jsxs("span",{className:"text-muted-foreground/70 ml-0.5",children:["(",i,")"]})]})}function wA({className:e}){const[t,n]=y.useState("loading"),[r,i]=y.useState(!1);y.useEffect(()=>{const u=async()=>{try{n(await kP())}catch{n(null)}};u();const p=setInterval(()=>void u(),5*60*1e3);return()=>clearInterval(p)},[]);const s=async()=>{i(!0);try{n(await CP())}catch{n(null)}finally{i(!1)}};if(t==="loading"||!t)return null;const a=!!t.fiveHour,o=!!t.sevenDay,l=!!t.sevenDaySonnet,c=!!t.sevenDayOpus,d=a||o||l||c;return h.jsxs("div",{className:ee("flex items-center gap-1.5 text-xs",e),children:[t.planName&&h.jsx("span",{className:"font-medium text-foreground bg-muted border border-border px-2 py-0.5 rounded-full",children:t.planName}),d&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"|"}),h.jsx(Yl,{label:"5h",bucket:t.fiveHour}),o&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d",bucket:t.sevenDay})]}),l&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d Sonnet",bucket:t.sevenDaySonnet})]}),c&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d Opus",bucket:t.sevenDayOpus})]})]}),h.jsx("button",{onClick:()=>void s(),className:"text-muted-foreground hover:text-foreground transition-colors",title:"刷新用量信息",children:h.jsx(Wi,{className:ee("h-3 w-3",r&&"animate-spin")})})]})}const gx="v1.5.20",Xl=!!window.electronUpdater;function f3(e,t){const n=e.split(".").map(Number),r=t.split(".").map(Number);for(let i=0;i<Math.max(n.length,r.length);i++){const s=n[i]||0,a=r[i]||0;if(s!==a)return s-a}return 0}function g3(){const[e,t]=y.useState("idle"),[n,r]=y.useState(!1),[i,s]=y.useState(""),[a,o]=y.useState(0),[l,c]=y.useState([]),[d,u]=y.useState([]),[p,f]=y.useState(null),[g,b]=y.useState(null);y.useEffect(()=>window.electronUpdater?window.electronUpdater.onUpdateStatus(E=>{switch(E.type){case"progress":{const C=E.info;o(Math.round((C==null?void 0:C.percent)??0));break}case"downloaded":t("downloaded");break;case"error":f(E.info||"Download failed"),t("error");break}}):void 0,[]);const v=y.useCallback(async()=>{t("checking"),f(null),r(!0);try{if(Xl){const E=await window.electronUpdater.checkForUpdate();if(E.error)throw new Error(E.error);if(!E.available){t("no_update");return}s(E.version?`v${E.version}`:"new version")}else{const E=await fetch("https://api.github.com/repos/zbc0315/cc-web/releases/latest");if(E.status===404){t("no_update");return}if(!E.ok)throw new Error(`GitHub API ${E.status}`);const C=await E.json(),R=C.tag_name.replace(/^v/,""),M=gx.replace(/^v/,"");if(f3(R,M)<=0){t("no_update");return}s(C.tag_name)}const k=await RP();c(k.projects),t(k.runningCount>0?"confirm_prepare":"update_available")}catch(k){f(k instanceof Error?k.message:"Check failed"),t("error")}},[]),m=async()=>{t("preparing");try{const k=await IP();u(k.results),t("update_available")}catch(k){f(k instanceof Error?k.message:"Prepare failed"),t("error")}},_=async()=>{if(Xl){t("downloading"),o(0);const k=await window.electronUpdater.downloadUpdate();k.success?k.path&&b(k.path):(f(k.error||"Download failed"),t("error"))}else window.open("https://github.com/zbc0315/cc-web/releases/latest","_blank")},S=()=>{var k;(k=window.electronUpdater)==null||k.quitAndInstall(g||void 0)},w=()=>{r(!1),setTimeout(()=>{t("idle"),s(""),c([]),u([]),f(null),o(0)},200)};return h.jsxs(h.Fragment,{children:[h.jsxs(re,{variant:"ghost",size:"sm",onClick:()=>void v(),disabled:e==="checking"||e==="preparing"||e==="downloading",children:[e==="checking"||e==="preparing"||e==="downloading"?h.jsx(Fi,{className:"h-4 w-4 mr-2 animate-spin"}):h.jsx(UT,{className:"h-4 w-4 mr-2"}),"Update"]}),h.jsx(ul,{open:n,onOpenChange:k=>{k||w()},children:h.jsxs(fa,{className:"sm:max-w-md",children:[h.jsxs(ga,{children:[h.jsxs(ba,{children:[e==="checking"&&"Checking for updates...",e==="no_update"&&"Up to date",e==="update_available"&&`Update ${i} available`,e==="confirm_prepare"&&"Save project memory first",e==="preparing"&&"Saving project memory...",e==="downloading"&&"Downloading update...",e==="downloaded"&&"Update ready to install",e==="error"&&"Update error"]}),h.jsxs(_a,{children:[e==="checking"&&"Checking for the latest version...",e==="no_update"&&`Current version ${gx} is the latest.`,e==="update_available"&&(d.length>0?"All projects saved and stopped. Ready to update.":`A new version is available. ${Xl?"Click download to update automatically.":""}`),e==="confirm_prepare"&&`${l.length} project(s) running. Each Claude will be asked to save memory, then terminals will stop.`,e==="preparing"&&"Sending save commands and waiting for Claude to finish...",e==="downloading"&&`Downloading... ${a}%`,e==="downloaded"&&"The update has been downloaded. Click install to restart and apply.",e==="error"&&(p||"An error occurred.")]})]}),e==="downloading"&&h.jsx("div",{className:"w-full bg-muted rounded-full h-2 overflow-hidden",children:h.jsx("div",{className:"bg-primary h-full transition-all duration-300",style:{width:`${a}%`}})}),e==="confirm_prepare"&&l.length>0&&h.jsx("div",{className:"space-y-1 text-sm",children:l.map(k=>h.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 rounded bg-muted",children:[h.jsx("span",{className:"h-2 w-2 rounded-full bg-green-500 shrink-0"}),h.jsx("span",{className:"truncate",children:k.name})]},k.id))}),e==="preparing"&&h.jsx("div",{className:"flex items-center justify-center py-6",children:h.jsx(Fi,{className:"h-8 w-8 animate-spin text-muted-foreground"})}),e==="update_available"&&d.length>0&&h.jsx("div",{className:"space-y-1 text-sm",children:d.map(k=>h.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 rounded bg-muted",children:[k.status==="stopped"?h.jsx(QP,{className:"h-4 w-4 text-green-500 shrink-0"}):h.jsx(qP,{className:"h-4 w-4 text-yellow-500 shrink-0"}),h.jsx("span",{className:"truncate",children:k.name}),h.jsx("span",{className:"text-xs text-muted-foreground ml-auto shrink-0",children:k.message})]},k.id))}),h.jsxs(ma,{children:[e==="no_update"&&h.jsx(re,{onClick:w,children:"OK"}),e==="update_available"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Later"}),h.jsx(re,{onClick:()=>void _(),children:Xl?"Download & Install":`Download ${i}`})]}),e==="confirm_prepare"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Cancel"}),h.jsx(re,{onClick:()=>void m(),children:"Save Memory & Stop All"})]}),e==="downloaded"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Later"}),h.jsx(re,{onClick:S,children:"Restart & Install"})]}),e==="error"&&h.jsx(re,{onClick:w,children:"Close"})]})]})})]})}function m3(){const[e,t]=y.useState([]),[n,r]=y.useState(!1),[i,s]=y.useState(""),[a,o]=y.useState(""),[l,c]=y.useState(""),[d,u]=y.useState(null),[p,f]=y.useState(""),[g,b]=y.useState(""),[v,m]=y.useState(""),_=y.useRef(null),S=y.useRef(null);y.useEffect(()=>{LT().then(t).catch(()=>t([]))},[]),y.useEffect(()=>{n&&setTimeout(()=>{var C;return(C=_.current)==null?void 0:C.focus()},0)},[n]),y.useEffect(()=>{d&&setTimeout(()=>{var C;return(C=S.current)==null?void 0:C.focus()},0)},[d]);const w=async()=>{const C=a.trim();if(C)try{const R=await _P({label:i.trim()||C,command:C,...l?{parentId:l}:{}});t(M=>[...M,R]),s(""),o(""),c(""),r(!1)}catch(R){console.error("[GlobalShortcuts] Failed to add:",R)}},k=async C=>{try{await vP(C),t(R=>R.filter(M=>M.id!==C)),d===C&&u(null)}catch(R){console.error("[GlobalShortcuts] Failed to delete:",R)}},E=async()=>{const C=g.trim();if(!(!C||!d))try{const R=await yP(d,{label:p.trim()||C,command:C,parentId:v||null});t(M=>M.map(N=>N.id===d?R:N)),u(null)}catch(R){console.error("[GlobalShortcuts] Failed to save:",R)}};return h.jsxs("section",{className:"mt-10",children:[h.jsxs("div",{className:"flex items-center justify-between mb-4",children:[h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(hu,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("h2",{className:"text-lg font-semibold",children:"全局快捷命令"}),h.jsx("span",{className:"text-xs text-muted-foreground",children:"在所有项目的终端中可用"})]}),h.jsxs("button",{onClick:()=>{r(C=>!C),u(null)},className:ee("flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-md border transition-colors",n?"border-border text-foreground bg-muted":"border-border text-muted-foreground hover:text-foreground hover:bg-muted"),children:[h.jsx(Ho,{className:"h-3.5 w-3.5"}),"新建"]})]}),n&&h.jsxs("div",{className:"mb-4 p-4 rounded-lg border border-border bg-background space-y-3",children:[h.jsx("input",{ref:_,placeholder:"名称(可选)",value:i,onChange:C=>s(C.target.value),className:"w-full text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground placeholder:text-muted-foreground outline-none focus:border-border transition-colors"}),h.jsx("textarea",{placeholder:"命令内容…",value:a,onChange:C=>o(C.target.value),rows:3,className:"w-full text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground placeholder:text-muted-foreground font-mono outline-none focus:border-border transition-colors resize-none",onKeyDown:C=>{C.key==="Enter"&&(C.metaKey||C.ctrlKey)&&(C.preventDefault(),w()),C.key==="Escape"&&(r(!1),s(""),o(""),c(""))}}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(wc,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),h.jsxs("select",{value:l,onChange:C=>c(C.target.value),className:"flex-1 text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground outline-none focus:border-border transition-colors",children:[h.jsx("option",{value:"",children:"无继承"}),e.map(C=>h.jsx("option",{value:C.id,children:C.label},C.id))]})]}),h.jsxs("div",{className:"flex items-center justify-between",children:[h.jsx("span",{className:"text-xs text-muted-foreground",children:"⌘↩ 保存 · Esc 取消"}),h.jsxs("div",{className:"flex gap-2",children:[h.jsx("button",{onClick:()=>{r(!1),s(""),o("")},className:"text-sm px-3 py-1 rounded bg-secondary hover:bg-accent text-foreground transition-colors",children:"取消"}),h.jsx("button",{onClick:()=>void w(),disabled:!a.trim(),className:ee("text-sm px-3 py-1 rounded transition-colors text-white",a.trim()?"bg-blue-600 hover:bg-blue-500":"bg-secondary opacity-40 cursor-not-allowed"),children:"保存"})]})]})]}),e.length===0&&!n?h.jsxs("div",{className:"flex flex-col items-center gap-2 py-10 text-muted-foreground/50 border border-dashed border-border rounded-lg",children:[h.jsx(hu,{className:"h-6 w-6"}),h.jsx("p",{className:"text-sm",children:"还没有全局快捷命令"})]}):h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3",children:e.map(C=>d===C.id?h.jsxs("div",{className:"rounded-lg border border-border bg-background p-3 space-y-2",children:[h.jsx("input",{ref:S,value:p,onChange:R=>f(R.target.value),placeholder:"名称(可选)",className:"w-full text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground placeholder:text-muted-foreground outline-none focus:border-border transition-colors"}),h.jsx("textarea",{value:g,onChange:R=>b(R.target.value),rows:3,className:"w-full text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground font-mono outline-none focus:border-border transition-colors resize-none",onKeyDown:R=>{R.key==="Enter"&&(R.metaKey||R.ctrlKey)&&(R.preventDefault(),E()),R.key==="Escape"&&u(null)}}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(wc,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),h.jsxs("select",{value:v,onChange:R=>m(R.target.value),className:"flex-1 text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground outline-none focus:border-border transition-colors",children:[h.jsx("option",{value:"",children:"无继承"}),e.filter(R=>R.id!==C.id).map(R=>h.jsx("option",{value:R.id,children:R.label},R.id))]})]}),h.jsxs("div",{className:"flex gap-2 justify-end",children:[h.jsx("button",{onClick:()=>u(null),className:"text-xs px-2.5 py-1 rounded bg-secondary hover:bg-accent text-foreground transition-colors",children:"取消"}),h.jsx("button",{onClick:()=>void E(),disabled:!g.trim(),className:ee("text-xs px-2.5 py-1 rounded transition-colors text-white",g.trim()?"bg-blue-600 hover:bg-blue-500":"bg-secondary opacity-40 cursor-not-allowed"),children:"保存"})]})]},C.id):h.jsxs("div",{className:"group relative rounded-lg border border-border bg-background hover:border-muted-foreground/30 transition-colors p-3",children:[h.jsx("div",{className:"text-sm font-medium text-foreground truncate pr-14",children:C.label}),C.parentId&&(()=>{const R=e.find(M=>M.id===C.parentId);return R?h.jsxs("div",{className:"flex items-center gap-1 mt-1",children:[h.jsx(wc,{className:"h-3 w-3 text-muted-foreground"}),h.jsxs("span",{className:"text-xs text-muted-foreground truncate",children:["继承: ",R.label]})]}):null})(),!C.parentId&&C.label!==C.command&&h.jsx("div",{className:"text-xs text-muted-foreground font-mono truncate mt-1 pr-14",children:C.command}),h.jsxs("div",{className:"absolute top-2.5 right-2.5 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity",children:[h.jsx("button",{onClick:()=>{u(C.id),f(C.label),b(C.command),m(C.parentId||""),r(!1)},className:"p-1 rounded text-muted-foreground hover:text-blue-400 transition-colors",title:"编辑",children:h.jsx(Cm,{className:"h-3.5 w-3.5"})}),h.jsx("button",{onClick:()=>void k(C.id),className:"p-1 rounded text-muted-foreground hover:text-red-400 transition-colors",title:"删除",children:h.jsx(rs,{className:"h-3.5 w-3.5"})})]})]},C.id))})]})}const EA=y.createContext({theme:"system",resolved:"dark",setTheme:()=>{}}),mx="cc_web_theme";function b3(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function _3(){return y.useSyncExternalStore(e=>{const t=window.matchMedia("(prefers-color-scheme: dark)");return t.addEventListener("change",e),()=>t.removeEventListener("change",e)},()=>b3())}function y3({children:e}){const[t,n]=y.useState(()=>{try{return localStorage.getItem(mx)||"system"}catch{return"system"}}),r=_3(),i=t==="system"?r:t;y.useEffect(()=>{const a=document.documentElement;a.classList.remove("light","dark"),a.classList.add(i)},[i]);const s=a=>{n(a);try{localStorage.setItem(mx,a)}catch{}};return h.jsx(EA.Provider,{value:{theme:t,resolved:i,setTheme:s},children:e})}function Om(){return y.useContext(EA)}function xA(){const{theme:e,setTheme:t}=Om(),n=[{value:"light",icon:yF,label:"浅色"},{value:"dark",icon:uF,label:"深色"},{value:"system",icon:cF,label:"系统"}];return h.jsx("div",{className:"flex items-center rounded-md border border-border bg-muted/50 p-0.5",children:n.map(({value:r,icon:i,label:s})=>h.jsx("button",{onClick:()=>t(r),className:ee("p-1 rounded-sm transition-colors",e===r?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground"),title:s,children:h.jsx(i,{className:"h-3.5 w-3.5"})},r))})}function v3(){const e=da(),[t,n]=y.useState([]),[r,i]=y.useState(!0),[s,a]=y.useState(null),[o,l]=y.useState(!1),[c,d]=y.useState(!1),[u,p]=y.useState(new Set),[f,g]=y.useState(!!document.fullscreenElement),[b,v]=y.useState(!1),m=()=>{document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen()};y.useEffect(()=>{const A=()=>g(!!document.fullscreenElement);return document.addEventListener("fullscreenchange",A),()=>document.removeEventListener("fullscreenchange",A)},[]);const _=async()=>{try{const A=await OT();n(A)}catch(A){a(A instanceof Error?A.message:"Failed to load projects")}finally{i(!1)}};y.useEffect(()=>{_()},[]),y.useEffect(()=>{let I=null;const P=async()=>{if(!document.hidden)try{const D=await bP(),j=Date.now(),x=new Set(Object.entries(D).filter(([,$])=>typeof $=="number"&&j-$<2e3).map(([$])=>$));p(x)}catch{}},F=()=>{P(),I=setInterval(()=>void P(),2e3)},B=()=>{I&&(clearInterval(I),I=null)},U=()=>{document.hidden?B():F()};return F(),document.addEventListener("visibilitychange",U),()=>{B(),document.removeEventListener("visibilitychange",U)}},[]);const S=()=>{RT(),e("/login")},w=A=>{n(I=>[...I,A])},k=A=>{n(I=>[...I,A])},E=async A=>{try{await hP(A),n(I=>I.filter(P=>P.id!==A))}catch(I){alert(I instanceof Error?I.message:"Failed to delete project")}},C=async A=>{try{const I=await gP(A);n(P=>P.map(F=>F.id===A?I:F))}catch(I){alert(I instanceof Error?I.message:"Failed to archive project")}},R=async A=>{try{const I=await mP(A);n(P=>P.map(F=>F.id===A?I:F))}catch(I){alert(I instanceof Error?I.message:"Failed to restore project")}},M=t.filter(A=>!A.archived),N=t.filter(A=>A.archived),O={onDelete:A=>void E(A),onArchive:A=>void C(A),onUnarchive:A=>void R(A)};return h.jsxs("div",{className:"min-h-screen bg-background",children:[h.jsx("header",{className:"border-b sticky top-0 bg-background z-10",children:h.jsxs("div",{className:"max-w-6xl mx-auto px-4 h-14 flex items-center justify-between",children:[h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(JE,{className:"h-5 w-5"}),h.jsx("span",{className:"font-semibold text-lg",children:"CC Web"})]}),h.jsx(wA,{}),h.jsx(re,{variant:"ghost",size:"sm",onClick:()=>e("/settings"),title:"设置",children:h.jsx(bF,{className:"h-4 w-4"})}),h.jsx(g3,{}),h.jsx(re,{variant:"ghost",size:"sm",onClick:m,title:f?"Exit fullscreen":"Fullscreen",children:f?h.jsx(km,{className:"h-4 w-4"}):h.jsx(xm,{className:"h-4 w-4"})}),h.jsx(xA,{}),h.jsxs(re,{variant:"ghost",size:"sm",onClick:S,children:[h.jsx(oF,{className:"h-4 w-4 mr-2"}),"Logout"]})]})}),h.jsxs("main",{className:"max-w-6xl mx-auto px-4 py-8",children:[h.jsxs("div",{className:"flex items-center justify-between mb-6",children:[h.jsxs("div",{children:[h.jsx("h1",{className:"text-2xl font-bold",children:"Projects"}),h.jsx("p",{className:"text-muted-foreground text-sm mt-1",children:"Each project runs Claude CLI in a dedicated terminal session."})]}),h.jsxs("div",{className:"flex gap-2",children:[h.jsxs(re,{variant:"outline",onClick:()=>d(!0),children:[h.jsx(zo,{className:"h-4 w-4 mr-2"}),"Open Project"]}),h.jsxs(re,{onClick:()=>l(!0),children:[h.jsx(Ho,{className:"h-4 w-4 mr-2"}),"New Project"]})]})]}),r&&h.jsx("div",{className:"text-center text-muted-foreground py-12",children:"Loading projects..."}),s&&h.jsx("div",{className:"text-center text-destructive py-12",children:s}),!r&&!s&&t.length===0&&h.jsxs("div",{className:"text-center py-20",children:[h.jsx(JE,{className:"h-12 w-12 text-muted-foreground mx-auto mb-4"}),h.jsx("h2",{className:"text-lg font-semibold mb-2",children:"No projects yet"}),h.jsx("p",{className:"text-muted-foreground text-sm mb-6",children:"Create a new project or open an existing one."}),h.jsxs("div",{className:"flex gap-2 justify-center",children:[h.jsxs(re,{variant:"outline",onClick:()=>d(!0),children:[h.jsx(zo,{className:"h-4 w-4 mr-2"}),"Open Project"]}),h.jsxs(re,{onClick:()=>l(!0),children:[h.jsx(Ho,{className:"h-4 w-4 mr-2"}),"New Project"]})]})]}),!r&&!s&&M.length>0&&h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",children:M.map(A=>h.jsx(tx,{project:A,active:u.has(A.id),...O},A.id))}),!r&&!s&&N.length>0&&h.jsxs("div",{className:"mt-10",children:[h.jsxs("button",{className:"flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors mb-4 group",onClick:()=>v(A=>!A),children:[b?h.jsx(rd,{className:"h-4 w-4"}):h.jsx(Em,{className:"h-4 w-4"}),h.jsx("span",{className:"font-medium",children:"Archived"}),h.jsx("span",{className:"text-xs bg-muted rounded-full px-2 py-0.5",children:N.length})]}),b&&h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",children:N.map(A=>h.jsx(tx,{project:A,active:!1,...O},A.id))})]}),h.jsx(m3,{})]}),h.jsx(d3,{open:o,onOpenChange:l,onCreated:w}),h.jsx(h3,{open:c,onOpenChange:d,onOpened:k})]})}/**
|
|
406
|
+
For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return y.useEffect(()=>{e&&(document.getElementById(e)||console.error(n))},[n,e]),null},i3="DialogDescriptionWarning",s3=({contentRef:e,descriptionId:t})=>{const r=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${gA(i3).contentName}}.`;return y.useEffect(()=>{var s;const i=(s=e.current)==null?void 0:s.getAttribute("aria-describedby");t&&i&&(document.getElementById(t)||console.warn(r))},[r,e,t]),null},a3=nA,o3=sA,mA=aA,bA=oA,_A=cA,yA=dA,l3=pA;const ul=a3,c3=o3,vA=y.forwardRef(({className:e,...t},n)=>h.jsx(mA,{ref:n,className:ee("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e),...t}));vA.displayName=mA.displayName;const fa=y.forwardRef(({className:e,children:t,...n},r)=>h.jsxs(c3,{children:[h.jsx(vA,{}),h.jsxs(bA,{ref:r,className:ee("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",e),...n,children:[t,h.jsxs(l3,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",children:[h.jsx(rs,{className:"h-4 w-4"}),h.jsx("span",{className:"sr-only",children:"Close"})]})]})]}));fa.displayName=bA.displayName;const ga=({className:e,...t})=>h.jsx("div",{className:ee("flex flex-col space-y-1.5 text-center sm:text-left",e),...t});ga.displayName="DialogHeader";const ma=({className:e,...t})=>h.jsx("div",{className:ee("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",e),...t});ma.displayName="DialogFooter";const ba=y.forwardRef(({className:e,...t},n)=>h.jsx(_A,{ref:n,className:ee("text-lg font-semibold leading-none tracking-tight",e),...t}));ba.displayName=_A.displayName;const _a=y.forwardRef(({className:e,...t},n)=>h.jsx(yA,{ref:n,className:ee("text-sm text-muted-foreground",e),...t}));_a.displayName=yA.displayName;function SA({onSelect:e}){const[t,n]=y.useState(""),[r,i]=y.useState(null),[s,a]=y.useState([]),[o,l]=y.useState(!0),[c,d]=y.useState(null),[u,p]=y.useState(!1),[f,g]=y.useState(""),[b,v]=y.useState(null),[m,_]=y.useState(!1),S=y.useRef(null),w=async O=>{l(!0),d(null);try{const A=await DT(O);n(A.path),i(A.parent),a(A.entries)}catch(A){d(A instanceof Error?A.message:"Failed to load directory")}finally{l(!1)}};y.useEffect(()=>{w()},[]),y.useEffect(()=>{u&&setTimeout(()=>{var O;return(O=S.current)==null?void 0:O.focus()},0)},[u]);const k=()=>{g(""),v(null),p(!0)},E=()=>{p(!1),g(""),v(null)},C=async()=>{const O=f.trim();if(O){_(!0),v(null);try{const{path:A}=await AP(t,O);p(!1),g(""),await w(t),w(A)}catch(A){v(A instanceof Error?A.message:"Failed to create folder")}finally{_(!1)}}},R=O=>{O.key==="Enter"?(O.preventDefault(),C()):O.key==="Escape"&&E()},M=t.split("/").filter(Boolean),N=O=>{const A="/"+M.slice(0,O+1).join("/");w(A)};return h.jsxs("div",{className:"flex flex-col h-64 border rounded-md overflow-hidden",children:[h.jsxs("div",{className:"flex items-center gap-1 px-2 py-2 bg-muted border-b flex-shrink-0",children:[h.jsx(re,{variant:"ghost",size:"sm",className:"h-6 px-1 flex-shrink-0",onClick:()=>void w(),title:"Home",children:h.jsx(sF,{className:"h-3 w-3"})}),h.jsx("div",{className:"flex items-center gap-1 flex-1 overflow-x-auto min-w-0",children:M.map((O,A)=>h.jsxs(ft.Fragment,{children:[h.jsx(Em,{className:"h-3 w-3 text-muted-foreground flex-shrink-0"}),h.jsx("button",{className:"text-xs hover:text-foreground text-muted-foreground whitespace-nowrap",onClick:()=>N(A),children:O})]},A))}),h.jsxs(re,{variant:"ghost",size:"sm",className:"h-6 px-1.5 flex-shrink-0 gap-1 text-xs",onClick:k,disabled:u||o||!!c,title:"New folder",children:[h.jsx(iF,{className:"h-3.5 w-3.5"}),h.jsx("span",{className:"hidden sm:inline",children:"New folder"})]})]}),h.jsxs("div",{className:"flex-1 overflow-y-auto",children:[o&&h.jsx("div",{className:"flex items-center justify-center h-full text-sm text-muted-foreground",children:"Loading..."}),c&&h.jsx("div",{className:"flex items-center justify-center h-full text-sm text-destructive px-4 text-center",children:c}),!o&&!c&&h.jsxs("div",{className:"p-1",children:[r&&h.jsxs("button",{className:"flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded hover:bg-accent hover:text-accent-foreground",onClick:()=>void w(r),children:[h.jsx(du,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("span",{children:".."})]}),u&&h.jsxs("div",{className:"flex items-center gap-1 px-2 py-1.5",children:[h.jsx(uf,{className:"h-4 w-4 text-blue-500 flex-shrink-0"}),h.jsx("input",{ref:S,className:ee("flex-1 text-sm bg-background border rounded px-1.5 py-0.5 outline-none","focus:ring-1 focus:ring-ring",b&&"border-destructive"),placeholder:"Folder name",value:f,onChange:O=>{g(O.target.value),v(null)},onKeyDown:R,disabled:m}),h.jsx("button",{className:"p-1 rounded hover:bg-accent disabled:opacity-50",onClick:()=>void C(),disabled:!f.trim()||m,title:"Create",children:h.jsx(BT,{className:"h-3.5 w-3.5 text-green-600"})}),h.jsx("button",{className:"p-1 rounded hover:bg-accent",onClick:E,disabled:m,title:"Cancel",children:h.jsx(rs,{className:"h-3.5 w-3.5 text-muted-foreground"})})]}),b&&h.jsx("p",{className:"px-3 pb-1 text-xs text-destructive",children:b}),s.length===0&&!u&&h.jsx("div",{className:"text-sm text-muted-foreground px-2 py-4 text-center",children:"Empty directory"}),s.map(O=>h.jsxs("button",{className:ee("flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded text-left",O.type==="dir"?"hover:bg-accent hover:text-accent-foreground cursor-pointer":"opacity-40 cursor-not-allowed"),onClick:()=>{O.type==="dir"&&w(O.path)},disabled:O.type==="file",children:[O.type==="dir"?h.jsx(uf,{className:"h-4 w-4 text-blue-500 flex-shrink-0"}):h.jsx(rF,{className:"h-4 w-4 text-muted-foreground flex-shrink-0"}),h.jsx("span",{className:"truncate",children:O.name})]},O.path))]})]}),h.jsxs("div",{className:"border-t px-3 py-2 flex items-center justify-between gap-2 flex-shrink-0 bg-background",children:[h.jsx("span",{className:"text-xs text-muted-foreground truncate flex-1",children:t}),h.jsx(re,{size:"sm",onClick:()=>e(t),disabled:!t,children:"Select this folder"})]})]})}const u3=[{value:"claude",label:"Claude",desc:"Anthropic Claude Code CLI"},{value:"opencode",label:"OpenCode",desc:"OpenCode CLI (sst/opencode)"},{value:"codex",label:"Codex",desc:"OpenAI Codex CLI"},{value:"qwen",label:"Qwen",desc:"Qwen Code CLI (QwenLM)"}],fx={claude:{limited:"claude",unlimited:"claude --dangerously-skip-permissions"},opencode:{limited:"opencode",unlimited:"opencode --dangerously-skip-permissions"},codex:{limited:"codex",unlimited:"codex --ask-for-approval never --sandbox danger-full-access"},qwen:{limited:"qwen-code",unlimited:"qwen-code --yolo"}};function d3({open:e,onOpenChange:t,onCreated:n}){const[r,i]=y.useState("name"),[s,a]=y.useState(""),[o,l]=y.useState(""),[c,d]=y.useState("claude"),[u,p]=y.useState("limited"),[f,g]=y.useState(!1),[b,v]=y.useState(null),m=()=>{i("name"),a(""),l(""),d("claude"),p("limited"),v(null),g(!1)},_=R=>{R||m(),t(R)},S=()=>{if(!s.trim()){v("Please enter a project name");return}v(null),i("folder")},w=R=>{l(R),i("settings")},k=async()=>{g(!0),v(null);try{const R=await uP({name:s.trim(),folderPath:o,permissionMode:u,cliTool:c});n(R),_(!1)}catch(R){v(R instanceof Error?R.message:"Failed to create project")}finally{g(!1)}},E={name:"New Project — Name",folder:"New Project — Select Folder",settings:"New Project — Settings"},C=["name","folder","settings"];return h.jsx(ul,{open:e,onOpenChange:_,children:h.jsxs(fa,{className:"sm:max-w-lg",children:[h.jsxs(ga,{children:[h.jsx(ba,{children:E[r]}),h.jsxs(_a,{children:[r==="name"&&"Give your project a name.",r==="folder"&&"Choose the working directory for this project.",r==="settings"&&"Choose the AI coding tool and run mode."]})]}),h.jsx("div",{className:"flex gap-2 mb-2",children:C.map((R,M)=>h.jsx("div",{className:`h-1.5 flex-1 rounded-full ${r===R?"bg-primary":C.indexOf(r)>M?"bg-primary/40":"bg-muted"}`},R))}),r==="name"&&h.jsxs("div",{className:"space-y-4",children:[h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{htmlFor:"project-name",children:"Project Name"}),h.jsx(gr,{id:"project-name",placeholder:"My Project",value:s,onChange:R=>a(R.target.value),onKeyDown:R=>{R.key==="Enter"&&S()},autoFocus:!0})]}),b&&h.jsx("p",{className:"text-sm text-destructive",children:b})]}),r==="folder"&&h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Working Directory"}),h.jsx(SA,{onSelect:w})]}),r==="settings"&&h.jsxs("div",{className:"space-y-5",children:[h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"AI Coding Tool"}),h.jsx("div",{className:"grid grid-cols-2 gap-2",children:u3.map(R=>h.jsxs("label",{className:`flex items-center gap-2.5 cursor-pointer p-3 rounded-md border transition-colors ${c===R.value?"border-primary bg-primary/5":"hover:bg-accent"}`,children:[h.jsx("input",{type:"radio",name:"cliTool",value:R.value,checked:c===R.value,onChange:()=>d(R.value),className:"accent-primary"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:R.label}),h.jsx("div",{className:"text-xs text-muted-foreground",children:R.desc})]})]},R.value))})]}),h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Permission Mode"}),h.jsxs("div",{className:"space-y-2",children:[h.jsxs("label",{className:"flex items-start gap-3 cursor-pointer p-3 rounded-md border hover:bg-accent transition-colors",children:[h.jsx("input",{type:"radio",name:"permissionMode",value:"limited",checked:u==="limited",onChange:()=>p("limited"),className:"mt-0.5"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:"Limited"}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:["Runs ",h.jsx("code",{className:"bg-muted px-1 rounded",children:fx[c].limited})," — asks for permission before file changes."]})]})]}),h.jsxs("label",{className:"flex items-start gap-3 cursor-pointer p-3 rounded-md border hover:bg-accent transition-colors",children:[h.jsx("input",{type:"radio",name:"permissionMode",value:"unlimited",checked:u==="unlimited",onChange:()=>p("unlimited"),className:"mt-0.5"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:"Unlimited"}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:["Runs ",h.jsx("code",{className:"bg-muted px-1 rounded",children:fx[c].unlimited})," — acts autonomously."]})]})]})]})]}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:[h.jsx("span",{className:"font-medium",children:"Folder:"})," ",h.jsx("span",{className:"font-mono",children:o})]}),b&&h.jsx("p",{className:"text-sm text-destructive",children:b})]}),h.jsxs(ma,{children:[r!=="name"&&h.jsx(re,{variant:"outline",onClick:()=>i(r==="settings"?"folder":"name"),disabled:f,children:"Back"}),r==="name"&&h.jsx(re,{onClick:S,children:"Next"}),r==="folder"&&h.jsx(re,{variant:"outline",onClick:()=>t(!1),children:"Cancel"}),r==="settings"&&h.jsx(re,{onClick:()=>void k(),disabled:f,children:f?"Creating...":"Create Project"})]})]})})}function h3({open:e,onOpenChange:t,onOpened:n}){const[r,i]=y.useState(""),[s,a]=y.useState(!1),[o,l]=y.useState(null),c=()=>{i(""),l(null),a(!1)},d=p=>{p||c(),t(p)},u=async p=>{i(p),a(!0),l(null);try{const f=await dP(p);n(f),d(!1)}catch(f){l(f instanceof Error?f.message:"Failed to open project"),a(!1)}};return h.jsx(ul,{open:e,onOpenChange:d,children:h.jsxs(fa,{className:"sm:max-w-lg",children:[h.jsxs(ga,{children:[h.jsx(ba,{children:"Open Existing Project"}),h.jsxs(_a,{children:["Select a folder that contains a ",h.jsx("code",{className:"bg-muted px-1 rounded text-xs",children:".ccweb/"})," configuration. The project's history and settings will be restored."]})]}),h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Project Folder"}),h.jsx(SA,{onSelect:p=>void u(p)})]}),o&&h.jsx("p",{className:"text-sm text-destructive",children:o}),r&&!o&&s&&h.jsxs("p",{className:"text-sm text-muted-foreground",children:["Opening project from ",r,"..."]}),h.jsx(ma,{children:h.jsx(re,{variant:"outline",onClick:()=>d(!1),disabled:s,children:"Cancel"})})]})})}function p3(e){if(!e)return"";const t=new Date(e).getTime()-Date.now();if(t<=0)return"即将重置";const n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4);return n>0?`${n}h${r}m`:`${r}m`}function Yl({label:e,bucket:t}){if(!t||t.utilization===void 0)return null;const n=t.utilization,r=n<50?"text-green-400":n<80?"text-yellow-400":"text-red-400",i=p3(t.resetAt);return h.jsxs("span",{className:"text-muted-foreground whitespace-nowrap",children:[e," ",h.jsxs("span",{className:ee("font-medium",r),children:[n,"%"]}),i&&h.jsxs("span",{className:"text-muted-foreground/70 ml-0.5",children:["(",i,")"]})]})}function wA({className:e}){const[t,n]=y.useState("loading"),[r,i]=y.useState(!1);y.useEffect(()=>{const u=async()=>{try{n(await kP())}catch{n(null)}};u();const p=setInterval(()=>void u(),5*60*1e3);return()=>clearInterval(p)},[]);const s=async()=>{i(!0);try{n(await CP())}catch{n(null)}finally{i(!1)}};if(t==="loading"||!t)return null;const a=!!t.fiveHour,o=!!t.sevenDay,l=!!t.sevenDaySonnet,c=!!t.sevenDayOpus,d=a||o||l||c;return h.jsxs("div",{className:ee("flex items-center gap-1.5 text-xs",e),children:[t.planName&&h.jsx("span",{className:"font-medium text-foreground bg-muted border border-border px-2 py-0.5 rounded-full",children:t.planName}),d&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"|"}),h.jsx(Yl,{label:"5h",bucket:t.fiveHour}),o&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d",bucket:t.sevenDay})]}),l&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d Sonnet",bucket:t.sevenDaySonnet})]}),c&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d Opus",bucket:t.sevenDayOpus})]})]}),h.jsx("button",{onClick:()=>void s(),className:"text-muted-foreground hover:text-foreground transition-colors",title:"刷新用量信息",children:h.jsx(Wi,{className:ee("h-3 w-3",r&&"animate-spin")})})]})}const gx="v1.5.21",Xl=!!window.electronUpdater;function f3(e,t){const n=e.split(".").map(Number),r=t.split(".").map(Number);for(let i=0;i<Math.max(n.length,r.length);i++){const s=n[i]||0,a=r[i]||0;if(s!==a)return s-a}return 0}function g3(){const[e,t]=y.useState("idle"),[n,r]=y.useState(!1),[i,s]=y.useState(""),[a,o]=y.useState(0),[l,c]=y.useState([]),[d,u]=y.useState([]),[p,f]=y.useState(null),[g,b]=y.useState(null);y.useEffect(()=>window.electronUpdater?window.electronUpdater.onUpdateStatus(E=>{switch(E.type){case"progress":{const C=E.info;o(Math.round((C==null?void 0:C.percent)??0));break}case"downloaded":t("downloaded");break;case"error":f(E.info||"Download failed"),t("error");break}}):void 0,[]);const v=y.useCallback(async()=>{t("checking"),f(null),r(!0);try{if(Xl){const E=await window.electronUpdater.checkForUpdate();if(E.error)throw new Error(E.error);if(!E.available){t("no_update");return}s(E.version?`v${E.version}`:"new version")}else{const E=await fetch("https://api.github.com/repos/zbc0315/cc-web/releases/latest");if(E.status===404){t("no_update");return}if(!E.ok)throw new Error(`GitHub API ${E.status}`);const C=await E.json(),R=C.tag_name.replace(/^v/,""),M=gx.replace(/^v/,"");if(f3(R,M)<=0){t("no_update");return}s(C.tag_name)}const k=await RP();c(k.projects),t(k.runningCount>0?"confirm_prepare":"update_available")}catch(k){f(k instanceof Error?k.message:"Check failed"),t("error")}},[]),m=async()=>{t("preparing");try{const k=await IP();u(k.results),t("update_available")}catch(k){f(k instanceof Error?k.message:"Prepare failed"),t("error")}},_=async()=>{if(Xl){t("downloading"),o(0);const k=await window.electronUpdater.downloadUpdate();k.success?k.path&&b(k.path):(f(k.error||"Download failed"),t("error"))}else window.open("https://github.com/zbc0315/cc-web/releases/latest","_blank")},S=()=>{var k;(k=window.electronUpdater)==null||k.quitAndInstall(g||void 0)},w=()=>{r(!1),setTimeout(()=>{t("idle"),s(""),c([]),u([]),f(null),o(0)},200)};return h.jsxs(h.Fragment,{children:[h.jsxs(re,{variant:"ghost",size:"sm",onClick:()=>void v(),disabled:e==="checking"||e==="preparing"||e==="downloading",children:[e==="checking"||e==="preparing"||e==="downloading"?h.jsx(Fi,{className:"h-4 w-4 mr-2 animate-spin"}):h.jsx(UT,{className:"h-4 w-4 mr-2"}),"Update"]}),h.jsx(ul,{open:n,onOpenChange:k=>{k||w()},children:h.jsxs(fa,{className:"sm:max-w-md",children:[h.jsxs(ga,{children:[h.jsxs(ba,{children:[e==="checking"&&"Checking for updates...",e==="no_update"&&"Up to date",e==="update_available"&&`Update ${i} available`,e==="confirm_prepare"&&"Save project memory first",e==="preparing"&&"Saving project memory...",e==="downloading"&&"Downloading update...",e==="downloaded"&&"Update ready to install",e==="error"&&"Update error"]}),h.jsxs(_a,{children:[e==="checking"&&"Checking for the latest version...",e==="no_update"&&`Current version ${gx} is the latest.`,e==="update_available"&&(d.length>0?"All projects saved and stopped. Ready to update.":`A new version is available. ${Xl?"Click download to update automatically.":""}`),e==="confirm_prepare"&&`${l.length} project(s) running. Each Claude will be asked to save memory, then terminals will stop.`,e==="preparing"&&"Sending save commands and waiting for Claude to finish...",e==="downloading"&&`Downloading... ${a}%`,e==="downloaded"&&"The update has been downloaded. Click install to restart and apply.",e==="error"&&(p||"An error occurred.")]})]}),e==="downloading"&&h.jsx("div",{className:"w-full bg-muted rounded-full h-2 overflow-hidden",children:h.jsx("div",{className:"bg-primary h-full transition-all duration-300",style:{width:`${a}%`}})}),e==="confirm_prepare"&&l.length>0&&h.jsx("div",{className:"space-y-1 text-sm",children:l.map(k=>h.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 rounded bg-muted",children:[h.jsx("span",{className:"h-2 w-2 rounded-full bg-green-500 shrink-0"}),h.jsx("span",{className:"truncate",children:k.name})]},k.id))}),e==="preparing"&&h.jsx("div",{className:"flex items-center justify-center py-6",children:h.jsx(Fi,{className:"h-8 w-8 animate-spin text-muted-foreground"})}),e==="update_available"&&d.length>0&&h.jsx("div",{className:"space-y-1 text-sm",children:d.map(k=>h.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 rounded bg-muted",children:[k.status==="stopped"?h.jsx(QP,{className:"h-4 w-4 text-green-500 shrink-0"}):h.jsx(qP,{className:"h-4 w-4 text-yellow-500 shrink-0"}),h.jsx("span",{className:"truncate",children:k.name}),h.jsx("span",{className:"text-xs text-muted-foreground ml-auto shrink-0",children:k.message})]},k.id))}),h.jsxs(ma,{children:[e==="no_update"&&h.jsx(re,{onClick:w,children:"OK"}),e==="update_available"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Later"}),h.jsx(re,{onClick:()=>void _(),children:Xl?"Download & Install":`Download ${i}`})]}),e==="confirm_prepare"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Cancel"}),h.jsx(re,{onClick:()=>void m(),children:"Save Memory & Stop All"})]}),e==="downloaded"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Later"}),h.jsx(re,{onClick:S,children:"Restart & Install"})]}),e==="error"&&h.jsx(re,{onClick:w,children:"Close"})]})]})})]})}function m3(){const[e,t]=y.useState([]),[n,r]=y.useState(!1),[i,s]=y.useState(""),[a,o]=y.useState(""),[l,c]=y.useState(""),[d,u]=y.useState(null),[p,f]=y.useState(""),[g,b]=y.useState(""),[v,m]=y.useState(""),_=y.useRef(null),S=y.useRef(null);y.useEffect(()=>{LT().then(t).catch(()=>t([]))},[]),y.useEffect(()=>{n&&setTimeout(()=>{var C;return(C=_.current)==null?void 0:C.focus()},0)},[n]),y.useEffect(()=>{d&&setTimeout(()=>{var C;return(C=S.current)==null?void 0:C.focus()},0)},[d]);const w=async()=>{const C=a.trim();if(C)try{const R=await _P({label:i.trim()||C,command:C,...l?{parentId:l}:{}});t(M=>[...M,R]),s(""),o(""),c(""),r(!1)}catch(R){console.error("[GlobalShortcuts] Failed to add:",R)}},k=async C=>{try{await vP(C),t(R=>R.filter(M=>M.id!==C)),d===C&&u(null)}catch(R){console.error("[GlobalShortcuts] Failed to delete:",R)}},E=async()=>{const C=g.trim();if(!(!C||!d))try{const R=await yP(d,{label:p.trim()||C,command:C,parentId:v||null});t(M=>M.map(N=>N.id===d?R:N)),u(null)}catch(R){console.error("[GlobalShortcuts] Failed to save:",R)}};return h.jsxs("section",{className:"mt-10",children:[h.jsxs("div",{className:"flex items-center justify-between mb-4",children:[h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(hu,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("h2",{className:"text-lg font-semibold",children:"全局快捷命令"}),h.jsx("span",{className:"text-xs text-muted-foreground",children:"在所有项目的终端中可用"})]}),h.jsxs("button",{onClick:()=>{r(C=>!C),u(null)},className:ee("flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-md border transition-colors",n?"border-border text-foreground bg-muted":"border-border text-muted-foreground hover:text-foreground hover:bg-muted"),children:[h.jsx(Ho,{className:"h-3.5 w-3.5"}),"新建"]})]}),n&&h.jsxs("div",{className:"mb-4 p-4 rounded-lg border border-border bg-background space-y-3",children:[h.jsx("input",{ref:_,placeholder:"名称(可选)",value:i,onChange:C=>s(C.target.value),className:"w-full text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground placeholder:text-muted-foreground outline-none focus:border-border transition-colors"}),h.jsx("textarea",{placeholder:"命令内容…",value:a,onChange:C=>o(C.target.value),rows:3,className:"w-full text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground placeholder:text-muted-foreground font-mono outline-none focus:border-border transition-colors resize-none",onKeyDown:C=>{C.key==="Enter"&&(C.metaKey||C.ctrlKey)&&(C.preventDefault(),w()),C.key==="Escape"&&(r(!1),s(""),o(""),c(""))}}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(wc,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),h.jsxs("select",{value:l,onChange:C=>c(C.target.value),className:"flex-1 text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground outline-none focus:border-border transition-colors",children:[h.jsx("option",{value:"",children:"无继承"}),e.map(C=>h.jsx("option",{value:C.id,children:C.label},C.id))]})]}),h.jsxs("div",{className:"flex items-center justify-between",children:[h.jsx("span",{className:"text-xs text-muted-foreground",children:"⌘↩ 保存 · Esc 取消"}),h.jsxs("div",{className:"flex gap-2",children:[h.jsx("button",{onClick:()=>{r(!1),s(""),o("")},className:"text-sm px-3 py-1 rounded bg-secondary hover:bg-accent text-foreground transition-colors",children:"取消"}),h.jsx("button",{onClick:()=>void w(),disabled:!a.trim(),className:ee("text-sm px-3 py-1 rounded transition-colors text-white",a.trim()?"bg-blue-600 hover:bg-blue-500":"bg-secondary opacity-40 cursor-not-allowed"),children:"保存"})]})]})]}),e.length===0&&!n?h.jsxs("div",{className:"flex flex-col items-center gap-2 py-10 text-muted-foreground/50 border border-dashed border-border rounded-lg",children:[h.jsx(hu,{className:"h-6 w-6"}),h.jsx("p",{className:"text-sm",children:"还没有全局快捷命令"})]}):h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3",children:e.map(C=>d===C.id?h.jsxs("div",{className:"rounded-lg border border-border bg-background p-3 space-y-2",children:[h.jsx("input",{ref:S,value:p,onChange:R=>f(R.target.value),placeholder:"名称(可选)",className:"w-full text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground placeholder:text-muted-foreground outline-none focus:border-border transition-colors"}),h.jsx("textarea",{value:g,onChange:R=>b(R.target.value),rows:3,className:"w-full text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground font-mono outline-none focus:border-border transition-colors resize-none",onKeyDown:R=>{R.key==="Enter"&&(R.metaKey||R.ctrlKey)&&(R.preventDefault(),E()),R.key==="Escape"&&u(null)}}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(wc,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),h.jsxs("select",{value:v,onChange:R=>m(R.target.value),className:"flex-1 text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground outline-none focus:border-border transition-colors",children:[h.jsx("option",{value:"",children:"无继承"}),e.filter(R=>R.id!==C.id).map(R=>h.jsx("option",{value:R.id,children:R.label},R.id))]})]}),h.jsxs("div",{className:"flex gap-2 justify-end",children:[h.jsx("button",{onClick:()=>u(null),className:"text-xs px-2.5 py-1 rounded bg-secondary hover:bg-accent text-foreground transition-colors",children:"取消"}),h.jsx("button",{onClick:()=>void E(),disabled:!g.trim(),className:ee("text-xs px-2.5 py-1 rounded transition-colors text-white",g.trim()?"bg-blue-600 hover:bg-blue-500":"bg-secondary opacity-40 cursor-not-allowed"),children:"保存"})]})]},C.id):h.jsxs("div",{className:"group relative rounded-lg border border-border bg-background hover:border-muted-foreground/30 transition-colors p-3",children:[h.jsx("div",{className:"text-sm font-medium text-foreground truncate pr-14",children:C.label}),C.parentId&&(()=>{const R=e.find(M=>M.id===C.parentId);return R?h.jsxs("div",{className:"flex items-center gap-1 mt-1",children:[h.jsx(wc,{className:"h-3 w-3 text-muted-foreground"}),h.jsxs("span",{className:"text-xs text-muted-foreground truncate",children:["继承: ",R.label]})]}):null})(),!C.parentId&&C.label!==C.command&&h.jsx("div",{className:"text-xs text-muted-foreground font-mono truncate mt-1 pr-14",children:C.command}),h.jsxs("div",{className:"absolute top-2.5 right-2.5 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity",children:[h.jsx("button",{onClick:()=>{u(C.id),f(C.label),b(C.command),m(C.parentId||""),r(!1)},className:"p-1 rounded text-muted-foreground hover:text-blue-400 transition-colors",title:"编辑",children:h.jsx(Cm,{className:"h-3.5 w-3.5"})}),h.jsx("button",{onClick:()=>void k(C.id),className:"p-1 rounded text-muted-foreground hover:text-red-400 transition-colors",title:"删除",children:h.jsx(rs,{className:"h-3.5 w-3.5"})})]})]},C.id))})]})}const EA=y.createContext({theme:"system",resolved:"dark",setTheme:()=>{}}),mx="cc_web_theme";function b3(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function _3(){return y.useSyncExternalStore(e=>{const t=window.matchMedia("(prefers-color-scheme: dark)");return t.addEventListener("change",e),()=>t.removeEventListener("change",e)},()=>b3())}function y3({children:e}){const[t,n]=y.useState(()=>{try{return localStorage.getItem(mx)||"system"}catch{return"system"}}),r=_3(),i=t==="system"?r:t;y.useEffect(()=>{const a=document.documentElement;a.classList.remove("light","dark"),a.classList.add(i)},[i]);const s=a=>{n(a);try{localStorage.setItem(mx,a)}catch{}};return h.jsx(EA.Provider,{value:{theme:t,resolved:i,setTheme:s},children:e})}function Om(){return y.useContext(EA)}function xA(){const{theme:e,setTheme:t}=Om(),n=[{value:"light",icon:yF,label:"浅色"},{value:"dark",icon:uF,label:"深色"},{value:"system",icon:cF,label:"系统"}];return h.jsx("div",{className:"flex items-center rounded-md border border-border bg-muted/50 p-0.5",children:n.map(({value:r,icon:i,label:s})=>h.jsx("button",{onClick:()=>t(r),className:ee("p-1 rounded-sm transition-colors",e===r?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground"),title:s,children:h.jsx(i,{className:"h-3.5 w-3.5"})},r))})}function v3(){const e=da(),[t,n]=y.useState([]),[r,i]=y.useState(!0),[s,a]=y.useState(null),[o,l]=y.useState(!1),[c,d]=y.useState(!1),[u,p]=y.useState(new Set),[f,g]=y.useState(!!document.fullscreenElement),[b,v]=y.useState(!1),m=()=>{document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen()};y.useEffect(()=>{const A=()=>g(!!document.fullscreenElement);return document.addEventListener("fullscreenchange",A),()=>document.removeEventListener("fullscreenchange",A)},[]);const _=async()=>{try{const A=await OT();n(A)}catch(A){a(A instanceof Error?A.message:"Failed to load projects")}finally{i(!1)}};y.useEffect(()=>{_()},[]),y.useEffect(()=>{let I=null;const P=async()=>{if(!document.hidden)try{const D=await bP(),j=Date.now(),x=new Set(Object.entries(D).filter(([,$])=>typeof $=="number"&&j-$<2e3).map(([$])=>$));p(x)}catch{}},F=()=>{P(),I=setInterval(()=>void P(),2e3)},B=()=>{I&&(clearInterval(I),I=null)},U=()=>{document.hidden?B():F()};return F(),document.addEventListener("visibilitychange",U),()=>{B(),document.removeEventListener("visibilitychange",U)}},[]);const S=()=>{RT(),e("/login")},w=A=>{n(I=>[...I,A])},k=A=>{n(I=>[...I,A])},E=async A=>{try{await hP(A),n(I=>I.filter(P=>P.id!==A))}catch(I){alert(I instanceof Error?I.message:"Failed to delete project")}},C=async A=>{try{const I=await gP(A);n(P=>P.map(F=>F.id===A?I:F))}catch(I){alert(I instanceof Error?I.message:"Failed to archive project")}},R=async A=>{try{const I=await mP(A);n(P=>P.map(F=>F.id===A?I:F))}catch(I){alert(I instanceof Error?I.message:"Failed to restore project")}},M=t.filter(A=>!A.archived),N=t.filter(A=>A.archived),O={onDelete:A=>void E(A),onArchive:A=>void C(A),onUnarchive:A=>void R(A)};return h.jsxs("div",{className:"min-h-screen bg-background",children:[h.jsx("header",{className:"border-b sticky top-0 bg-background z-10",children:h.jsxs("div",{className:"max-w-6xl mx-auto px-4 h-14 flex items-center justify-between",children:[h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(JE,{className:"h-5 w-5"}),h.jsx("span",{className:"font-semibold text-lg",children:"CC Web"})]}),h.jsx(wA,{}),h.jsx(re,{variant:"ghost",size:"sm",onClick:()=>e("/settings"),title:"设置",children:h.jsx(bF,{className:"h-4 w-4"})}),h.jsx(g3,{}),h.jsx(re,{variant:"ghost",size:"sm",onClick:m,title:f?"Exit fullscreen":"Fullscreen",children:f?h.jsx(km,{className:"h-4 w-4"}):h.jsx(xm,{className:"h-4 w-4"})}),h.jsx(xA,{}),h.jsxs(re,{variant:"ghost",size:"sm",onClick:S,children:[h.jsx(oF,{className:"h-4 w-4 mr-2"}),"Logout"]})]})}),h.jsxs("main",{className:"max-w-6xl mx-auto px-4 py-8",children:[h.jsxs("div",{className:"flex items-center justify-between mb-6",children:[h.jsxs("div",{children:[h.jsx("h1",{className:"text-2xl font-bold",children:"Projects"}),h.jsx("p",{className:"text-muted-foreground text-sm mt-1",children:"Each project runs Claude CLI in a dedicated terminal session."})]}),h.jsxs("div",{className:"flex gap-2",children:[h.jsxs(re,{variant:"outline",onClick:()=>d(!0),children:[h.jsx(zo,{className:"h-4 w-4 mr-2"}),"Open Project"]}),h.jsxs(re,{onClick:()=>l(!0),children:[h.jsx(Ho,{className:"h-4 w-4 mr-2"}),"New Project"]})]})]}),r&&h.jsx("div",{className:"text-center text-muted-foreground py-12",children:"Loading projects..."}),s&&h.jsx("div",{className:"text-center text-destructive py-12",children:s}),!r&&!s&&t.length===0&&h.jsxs("div",{className:"text-center py-20",children:[h.jsx(JE,{className:"h-12 w-12 text-muted-foreground mx-auto mb-4"}),h.jsx("h2",{className:"text-lg font-semibold mb-2",children:"No projects yet"}),h.jsx("p",{className:"text-muted-foreground text-sm mb-6",children:"Create a new project or open an existing one."}),h.jsxs("div",{className:"flex gap-2 justify-center",children:[h.jsxs(re,{variant:"outline",onClick:()=>d(!0),children:[h.jsx(zo,{className:"h-4 w-4 mr-2"}),"Open Project"]}),h.jsxs(re,{onClick:()=>l(!0),children:[h.jsx(Ho,{className:"h-4 w-4 mr-2"}),"New Project"]})]})]}),!r&&!s&&M.length>0&&h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",children:M.map(A=>h.jsx(tx,{project:A,active:u.has(A.id),...O},A.id))}),!r&&!s&&N.length>0&&h.jsxs("div",{className:"mt-10",children:[h.jsxs("button",{className:"flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors mb-4 group",onClick:()=>v(A=>!A),children:[b?h.jsx(rd,{className:"h-4 w-4"}):h.jsx(Em,{className:"h-4 w-4"}),h.jsx("span",{className:"font-medium",children:"Archived"}),h.jsx("span",{className:"text-xs bg-muted rounded-full px-2 py-0.5",children:N.length})]}),b&&h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",children:N.map(A=>h.jsx(tx,{project:A,active:!1,...O},A.id))})]}),h.jsx(m3,{})]}),h.jsx(d3,{open:o,onOpenChange:l,onCreated:w}),h.jsx(h3,{open:c,onOpenChange:d,onOpened:k})]})}/**
|
|
407
407
|
* Copyright (c) 2014-2024 The xterm.js authors. All rights reserved.
|
|
408
408
|
* @license MIT
|
|
409
409
|
*
|
package/frontend/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/terminal.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>CC Web</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CEcwUYC4.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-Cx18Pfpi.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|