Skydata001 commited on
Commit
b5a9ada
·
1 Parent(s): e59817b

Delete static

Browse files
Files changed (1) hide show
  1. static/index.html +0 -342
static/index.html DELETED
@@ -1,342 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
6
- <meta name="theme-color" content="#0a0d12" />
7
- <title>AgentSpace Code</title>
8
- <style>
9
- :root{
10
- --bg:#080b10;--panel:#0c1118;--panel2:#0f151e;--panel3:#131a24;
11
- --line:#202936;--line2:#2a3543;--text:#edf2f8;--muted:#8490a2;--dim:#5f6a7a;
12
- --accent:#8b7cff;--accent2:#a79cff;--blue:#57a6ff;--green:#4fd39a;--yellow:#e7c66c;--red:#f17f8b;
13
- --shadow:0 16px 45px rgba(0,0,0,.32);--radius:10px;
14
- }
15
- *{box-sizing:border-box}
16
- html,body{margin:0;width:100%;height:100%;overflow:hidden;background:var(--bg);color:var(--text);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
17
- button,input,textarea,select{font:inherit;color:inherit}button{cursor:pointer}
18
- button:disabled{opacity:.45;cursor:not-allowed}
19
- ::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-thumb{background:#27313e;border-radius:99px}::-webkit-scrollbar-track{background:transparent}
20
-
21
- .app{height:100%;display:grid;grid-template-rows:54px 1fr;background:var(--bg)}
22
- .topbar{display:flex;align-items:center;gap:10px;padding:0 12px;border-bottom:1px solid var(--line);background:rgba(9,13,19,.98);z-index:10}
23
- .brand{display:flex;align-items:center;gap:9px;font-size:14px;font-weight:780;letter-spacing:.1px;white-space:nowrap}
24
- .brand-mark{width:26px;height:26px;border-radius:8px;display:grid;place-items:center;background:linear-gradient(135deg,#8b7cff,#4f88ff);box-shadow:0 0 22px rgba(120,105,255,.25)}
25
- .brand-mark svg{width:15px;height:15px;fill:none;stroke:white;stroke-width:1.9}
26
- .workspace-chip{display:flex;align-items:center;gap:7px;min-width:0;color:var(--muted);font-size:12px;padding-left:8px;border-left:1px solid var(--line)}
27
- .workspace-chip .truncate{max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
28
- .top-spacer{flex:1}
29
- .model-chip,.live-chip{display:flex;align-items:center;gap:7px;border:1px solid var(--line);background:#0e141c;color:#c7d0dc;border-radius:999px;padding:6px 9px;font-size:11px;white-space:nowrap}
30
- .live-dot{width:7px;height:7px;border-radius:50%;background:var(--green);box-shadow:0 0 9px rgba(79,211,154,.65)}
31
- .live-chip.busy .live-dot{background:var(--yellow);box-shadow:0 0 10px rgba(231,198,108,.65);animation:pulse 1.2s infinite}
32
- .live-chip.off .live-dot{background:var(--red);box-shadow:none}
33
- .icon-btn,.top-btn,.ghost-btn,.primary-btn{border:1px solid var(--line);background:#0f151d;border-radius:8px;min-height:32px;padding:0 10px;display:inline-flex;align-items:center;justify-content:center;gap:7px;font-size:12px}
34
- .icon-btn{width:32px;padding:0;color:#adb7c5}.icon-btn:hover,.top-btn:hover,.ghost-btn:hover{background:#151c26;border-color:var(--line2)}
35
- .primary-btn{background:linear-gradient(180deg,#7668ed,#6254d6);border-color:#8579fa;color:white;font-weight:760}.primary-btn:hover{filter:brightness(1.07)}
36
- .stop-btn{background:#171118;border-color:#4a2932;color:#ffabb1}
37
-
38
- .layout{min-height:0;display:grid;grid-template-columns:250px minmax(440px,1fr) 330px}
39
- .leftbar,.rightbar{min-width:0;background:var(--panel);display:flex;flex-direction:column}
40
- .leftbar{border-right:1px solid var(--line)}.rightbar{border-left:1px solid var(--line)}
41
- .panel-head{height:43px;flex:0 0 43px;display:flex;align-items:center;justify-content:space-between;padding:0 10px;border-bottom:1px solid var(--line);font-size:11px;font-weight:800;letter-spacing:.8px;text-transform:uppercase;color:#9ba6b5}
42
- .panel-head .actions{display:flex;gap:4px}
43
- .panel-body{min-height:0;overflow:auto}
44
-
45
- .search-box{padding:9px;border-bottom:1px solid var(--line)}
46
- .search-box input{width:100%;height:31px;background:#0a0f15;border:1px solid var(--line);border-radius:7px;padding:0 10px;outline:none;font-size:12px}
47
- .search-box input:focus{border-color:#4d5f84}
48
- .tree{padding:6px}
49
- .tree-row{height:30px;display:flex;align-items:center;gap:7px;padding:0 7px;border-radius:6px;font-size:12px;color:#bfc8d4;user-select:none;white-space:nowrap}
50
- .tree-row:hover{background:#131a23}.tree-row.active{background:#1b2230;color:#fff}.tree-row.dir{color:#bac4d3}.tree-arrow{width:12px;color:#667386;text-align:center}.tree-icon{width:16px;color:#8e9bb0;display:grid;place-items:center}.tree-name{min-width:0;overflow:hidden;text-overflow:ellipsis;flex:1}.file-size{font-size:10px;color:#556173}
51
- .tree-children{display:block}
52
- .upload-row{padding:10px;border-top:1px solid var(--line);margin-top:auto}
53
-
54
- .center{min-width:0;min-height:0;background:#090d13;display:grid;grid-template-rows:minmax(320px,.68fr) minmax(300px,.95fr)}
55
- .agent-workspace{min-height:0;display:grid;grid-template-rows:50px 46px minmax(0,1fr) 88px;border-bottom:1px solid var(--line)}
56
- .agent-head{display:flex;align-items:center;gap:9px;padding:0 14px;border-bottom:1px solid var(--line);background:#0b1017}
57
- .agent-title{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:760}.agent-orb{width:27px;height:27px;border:1px solid #4a4189;border-radius:8px;background:#15112d;display:grid;place-items:center;color:#aaa0ff}.agent-orb svg{width:15px;height:15px}
58
- .agent-head .head-actions{margin-left:auto;display:flex;gap:6px}
59
- .activity{display:flex;align-items:center;gap:6px;padding:7px 12px;overflow:auto;border-bottom:1px solid var(--line);background:#0b1016}
60
- .stage{display:flex;align-items:center;gap:7px;white-space:nowrap;padding:7px 9px;border:1px solid var(--line);border-radius:8px;color:#727d8d;font-size:10px;background:#0e141b}.stage .stage-dot{width:7px;height:7px;border-radius:50%;background:#465160}.stage.active{color:#ece8ff;border-color:#463f72;background:#15122a}.stage.active .stage-dot{background:var(--accent);box-shadow:0 0 11px rgba(139,124,255,.75);animation:pulse 1s infinite}.stage.done{color:#8fe4bf}.stage.done .stage-dot{background:var(--green)}.stage.error{color:#ffacb3}.stage.error .stage-dot{background:var(--red)}
61
- .activity-line{height:1px;min-width:10px;flex:1;background:#242d3a}
62
- .chat-scroll{min-height:0;overflow:auto;padding:16px 15px 8px}
63
- .welcome{max-width:690px;margin:8px auto 15px;border:1px solid var(--line);background:linear-gradient(180deg,#0f151e,#0c1118);border-radius:13px;padding:16px;box-shadow:var(--shadow)}
64
- .welcome h2{margin:0 0 7px;font-size:16px}.welcome p{margin:0;color:#8d99aa;font-size:12px;line-height:1.6}.quick-actions{display:flex;flex-wrap:wrap;gap:7px;margin-top:12px}.quick-actions button{border:1px solid var(--line);background:#111821;border-radius:8px;padding:7px 9px;color:#bfc8d5;font-size:11px}
65
- .message{max-width:820px;margin:0 auto 14px;display:flex;gap:10px;align-items:flex-start}.message.user{justify-content:flex-end}.avatar{width:27px;height:27px;flex:0 0 27px;border-radius:8px;display:grid;place-items:center;font-size:10px;font-weight:800}.message.agent .avatar{background:#17142e;color:#b8afff;border:1px solid #423c76}.message.user .avatar{background:#19202b;color:#dce4ef;border:1px solid #2b3543;order:2}
66
- .message .content{max-width:calc(100% - 40px)}.message.user .content{order:1}.msg-meta{font-size:10px;color:#657184;margin:0 0 5px 2px}.bubble{border:1px solid var(--line);background:#0f151c;border-radius:10px;padding:10px 11px;color:#d8dee7;font-size:12px;line-height:1.6;white-space:pre-wrap;overflow-wrap:anywhere}.message.user .bubble{background:#151b24;border-color:#293442}.bubble pre{margin:8px 0;padding:10px;background:#090d12;border:1px solid #202833;border-radius:7px;overflow:auto;font-size:11px}.bubble code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:#d9cfff}
67
- .plan-card,.review-card,.changes-card,.tool-card{max-width:820px;margin:0 auto 12px;border:1px solid var(--line);background:#0e141b;border-radius:10px;overflow:hidden}.card-cap{display:flex;align-items:center;gap:7px;padding:9px 11px;border-bottom:1px solid var(--line);font-size:10px;font-weight:800;letter-spacing:.7px;text-transform:uppercase;color:#99a4b3}.card-body{padding:10px 11px;font-size:11px;color:#c3ccd8;line-height:1.55}.steps{display:grid;gap:7px}.step-row{display:flex;align-items:flex-start;gap:8px}.step-num{width:19px;height:19px;border-radius:6px;border:1px solid #363f4c;display:grid;place-items:center;color:#aeb9c8;font-size:10px;flex:0 0 19px}.issue{padding:6px 8px;border-radius:6px;background:#17141a;color:#d5bdc0;margin-top:6px}.change-list{display:grid;gap:5px}.change-item{padding:6px 8px;border-radius:6px;background:#0b1016;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px}.change-item.changed{color:#d6ca93}.change-item.created{color:#8fe2bd}.change-item.deleted{color:#f59ba4}
68
- .tool-live{max-width:820px;margin:0 auto 10px;border:1px solid #25303c;background:#0b1118;border-radius:9px;padding:8px 10px;display:flex;align-items:center;gap:9px;font-size:10px;color:#8590a1}.tool-spinner{width:15px;height:15px;border:2px solid #2b3440;border-top-color:var(--accent);border-radius:50%;animation:spin .9s linear infinite}.tool-name{color:#ccd4df;font-weight:700}.tool-args{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#687587}
69
- .composer{border-top:1px solid var(--line);background:#0a0f15;padding:10px 12px;display:grid;gap:7px}.composer-box{position:relative}.composer-box textarea{display:block;width:100%;min-height:52px;max-height:130px;resize:none;background:#0d131b;border:1px solid #2a3441;border-radius:9px;padding:10px 42px 10px 10px;outline:none;font-size:12px;line-height:1.45}.composer-box textarea:focus{border-color:#5a4fd2;box-shadow:0 0 0 2px rgba(104,88,230,.1)}
70
- .composer-tools{display:flex;align-items:center;gap:6px;min-width:0}.control{height:29px;border:1px solid var(--line);background:#0f151c;border-radius:7px;padding:0 8px;font-size:10px;color:#aeb8c5}.control.grow{flex:1;min-width:0}.send-main{height:29px;min-width:76px}.composer-hint{font-size:9px;color:#596576;margin-left:auto;white-space:nowrap}
71
- .approval{display:none;align-items:center;gap:9px;padding:8px 11px;border:1px solid #4f4620;background:#17150d;border-radius:8px;color:#d9cd9a;font-size:10px}.approval.show{display:flex}.approval button{margin-left:auto}
72
-
73
- .workbench{min-height:0;display:grid;grid-template-rows:43px minmax(0,1fr)}
74
- .workbench-head{display:flex;align-items:center;border-bottom:1px solid var(--line);background:#0d1219}.wb-tabs{display:flex;overflow:auto;min-width:0}.wb-tab{height:42px;padding:0 12px;border:0;border-right:1px solid var(--line);background:transparent;color:#6e7a8a;font-size:11px}.wb-tab.active{background:#111720;color:#e9eef5;border-bottom:2px solid var(--accent)}.wb-tools{margin-left:auto;display:flex;gap:5px;padding:0 8px}.workbench-body{min-height:0;display:grid;grid-template-rows:minmax(160px,1fr) 42%;position:relative}
75
- .editor-pane{min-height:0;position:relative;background:#080c11}.editor-tabs{height:36px;display:flex;align-items:stretch;overflow:auto;border-bottom:1px solid var(--line);background:#0c1117}.editor-tab{height:35px;display:flex;align-items:center;gap:7px;border:0;border-right:1px solid var(--line);padding:0 10px;background:#0e131a;color:#707b8b;font-size:10px;white-space:nowrap}.editor-tab.active{color:#edf2f7;background:#111821}.editor-tab .close{color:#566274}.editor-host{position:absolute;left:0;right:0;top:36px;bottom:0}.empty-editor{position:absolute;inset:36px 0 0;display:grid;place-items:center;color:#667184;font-size:12px;pointer-events:none;z-index:2}
76
- .terminal-pane{min-height:0;border-top:1px solid var(--line);display:grid;grid-template-rows:36px minmax(0,1fr) 40px;background:#080c11}.term-head{display:flex;align-items:center;gap:8px;padding:0 10px;border-bottom:1px solid var(--line);font-size:10px;color:#a1adbc}.term-indicator{width:7px;height:7px;border-radius:50%;background:var(--green)}.term-clear{margin-left:auto}.term-log{overflow:auto;padding:8px 10px;font:10px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace}.term-row{white-space:pre-wrap;overflow-wrap:anywhere;margin-bottom:3px}.term-row.cmd{color:#aab4c2}.term-row.out{color:#cdd6e1}.term-row.err{color:#ef8e97}.term-row.agent{color:#a69bff}.term-input{display:grid;grid-template-columns:1fr auto;gap:6px;padding:6px;border-top:1px solid var(--line)}.term-input input{min-width:0;background:#0d131a;border:1px solid var(--line);border-radius:7px;padding:0 9px;font:10px ui-monospace,SFMono-Regular,Menlo,monospace;outline:none}.term-input button{height:28px}
77
-
78
- .right-tabs{display:grid;grid-template-columns:repeat(3,1fr);border-bottom:1px solid var(--line)}.right-tab{height:43px;border:0;border-right:1px solid var(--line);background:#0d1219;color:#717d8e;font-size:10px}.right-tab:last-child{border-right:0}.right-tab.active{background:#111820;color:#f0f3f7;border-bottom:2px solid var(--accent)}
79
- .right-content{min-height:0;overflow:auto;padding:10px}.section-card{border:1px solid var(--line);background:#0d131a;border-radius:9px;margin-bottom:9px;overflow:hidden}.section-card-head{display:flex;align-items:center;justify-content:space-between;padding:9px 10px;border-bottom:1px solid var(--line);font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;color:#8e99a9}.section-card-body{padding:10px}.kv{display:grid;grid-template-columns:100px minmax(0,1fr);gap:7px;font-size:10px}.kv .k{color:#617082}.kv .v{color:#cbd4de;word-break:break-word}.agent-grid{display:grid;gap:7px}.agent-card{border:1px solid var(--line);background:#0a0f15;border-radius:8px;padding:8px}.agent-line{display:flex;align-items:center;gap:8px}.agent-badge{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;background:#17142d;color:#aaa0ff;font-size:9px;font-weight:800}.agent-name{font-size:10px;font-weight:760}.agent-role{font-size:9px;color:#657184;margin-top:1px}.agent-state{margin-left:auto;font-size:9px;color:#687486}.agent-card.active{border-color:#493f7b;background:#110f1f}.activity-list{display:grid;gap:5px}.activity-row{display:grid;grid-template-columns:8px 1fr auto;align-items:start;gap:7px;font-size:9px}.activity-row .dotx{width:6px;height:6px;border-radius:50%;background:#42505f;margin-top:4px}.activity-row .dotx.green{background:var(--green)}.activity-row .dotx.yellow{background:var(--yellow)}.activity-row .dotx.red{background:var(--red)}.activity-row .time{color:#4e5969}.pill{display:inline-flex;align-items:center;border:1px solid var(--line);border-radius:999px;padding:4px 7px;font-size:9px;color:#9ba7b7}.pill.ok{color:#8fe2bd}.pill.warn{color:#e0cb87}.pill.danger{color:#f19ca4}
80
- .settings-grid{display:grid;gap:8px}.field{display:grid;gap:5px}.field label{font-size:9px;color:#738093}.field input,.field textarea,.field select{width:100%;background:#0a0f15;border:1px solid var(--line);border-radius:7px;padding:8px;outline:none;font-size:10px}.field textarea{min-height:110px;resize:vertical;line-height:1.5}.field .readonly{color:#778394}.setting-note{font-size:9px;color:#586576;line-height:1.5}.mini-row{display:flex;gap:6px;flex-wrap:wrap}.save-settings{width:100%;height:31px;margin-top:4px}
81
-
82
- .mobile-bar{display:none}
83
- .overlay{position:fixed;inset:0;background:rgba(4,6,9,.72);backdrop-filter:blur(5px);display:none;z-index:40}.sheet{position:absolute;left:0;right:0;bottom:0;max-height:86vh;background:#0d131a;border-top:1px solid var(--line2);border-radius:16px 16px 0 0;box-shadow:var(--shadow);display:grid;grid-template-rows:48px minmax(0,1fr)}.sheet-head{display:flex;align-items:center;padding:0 14px;border-bottom:1px solid var(--line)}.sheet-head strong{font-size:12px}.sheet-head button{margin-left:auto}.sheet-body{min-height:0;overflow:auto;padding:10px}
84
- .login{position:fixed;inset:0;z-index:100;background:#070a0f;display:none;place-items:center;padding:18px}.login-card{width:min(430px,100%);border:1px solid var(--line2);background:#0e141c;border-radius:14px;padding:22px;box-shadow:var(--shadow)}.login-card .mark{width:42px;height:42px;border-radius:12px;display:grid;place-items:center;background:linear-gradient(135deg,#8b7cff,#4f88ff);margin-bottom:14px}.login-card h2{font-size:18px;margin:0 0 7px}.login-card p{font-size:12px;color:#788496;line-height:1.5;margin:0 0 15px}.login-card input{width:100%;height:40px;background:#0a0f15;border:1px solid var(--line);border-radius:8px;padding:0 11px;outline:none}.login-card button{width:100%;height:40px;margin-top:8px}.login-error{font-size:10px;color:#f18e97;margin-top:8px;min-height:14px}
85
-
86
- .toast{position:fixed;right:15px;bottom:15px;z-index:90;max-width:min(420px,calc(100vw - 30px));background:#111821;border:1px solid #303b49;color:#d8e0ea;border-radius:9px;padding:9px 11px;font-size:11px;box-shadow:var(--shadow);display:none}
87
- .hidden{display:none!important}
88
- @keyframes pulse{0%,100%{opacity:.6;transform:scale(.92)}50%{opacity:1;transform:scale(1)}}
89
- @keyframes spin{to{transform:rotate(360deg)}}
90
-
91
- @media(max-width:1180px){.layout{grid-template-columns:220px minmax(420px,1fr) 292px}.workspace-chip .truncate{max-width:130px}.model-chip{display:none}}
92
- @media(max-width:900px){
93
- .app{grid-template-rows:50px 1fr}.topbar{padding:0 9px}.topbar .workspace-chip{display:none}.layout{display:block;position:relative}.leftbar,.rightbar{display:none}.center{height:calc(100vh - 50px);padding-bottom:55px;grid-template-rows:minmax(360px,1fr) 45%}
94
- .agent-workspace{grid-template-rows:48px 43px minmax(0,1fr) 96px}.composer{padding-bottom:8px}.activity{padding-left:8px;padding-right:8px}.chat-scroll{padding-left:9px;padding-right:9px}.workbench{min-height:300px}.mobile-bar{display:grid;position:fixed;left:0;right:0;bottom:0;height:55px;z-index:20;grid-template-columns:repeat(5,1fr);background:#0b1017;border-top:1px solid var(--line);padding-bottom:env(safe-area-inset-bottom)}.mobile-item{border:0;background:transparent;color:#697587;font-size:9px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px}.mobile-item.active{color:#e9e4ff}.mobile-item svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.7}.right-tabs{grid-template-columns:repeat(3,1fr)}
95
- }
96
- @media(max-width:560px){.brand{font-size:13px}.topbar .top-btn{display:none}.live-chip{margin-left:auto}.agent-head{padding:0 9px}.agent-head .head-actions .icon-btn:nth-child(2){display:none}.agent-title{font-size:12px}.activity-line{display:none}.stage{padding:6px 7px}.composer-tools .composer-hint{display:none}.control{max-width:122px}.workbench{grid-template-rows:40px minmax(0,1fr)}.wb-tools .icon-btn{width:30px}.editor-tabs{height:34px}.editor-tab{height:33px}.editor-host{top:34px}.empty-editor{top:34px}.message .content{max-width:85%}.welcome{margin-top:2px}.quick-actions{display:grid;grid-template-columns:1fr 1fr}.tool-args{max-width:40%}}
97
- </style>
98
- </head>
99
- <body>
100
-
101
- <div class="login" id="loginOverlay">
102
- <div class="login-card">
103
- <div class="mark">⌁</div>
104
- <h2>AgentSpace Code</h2>
105
- <p>This Space is protected by an access token. Enter the token configured in Hugging Face Space secrets.</p>
106
- <input id="loginToken" type="password" autocomplete="current-password" placeholder="Access token" />
107
- <button class="primary-btn" onclick="login()">Unlock workspace</button>
108
- <div id="loginError" class="login-error"></div>
109
- </div>
110
- </div>
111
-
112
- <div class="app">
113
- <header class="topbar">
114
- <div class="brand">
115
- <div class="brand-mark"><svg viewBox="0 0 24 24"><path d="M7 8.5 3.8 12 7 15.5M17 8.5l3.2 3.5-3.2 3.5M14 6.5l-4 11"/></svg></div>
116
- <span>AgentSpace Code</span>
117
- </div>
118
- <div class="workspace-chip"><span>workspace</span><span class="truncate" id="workspaceName">—</span></div>
119
- <div class="top-spacer"></div>
120
- <div class="model-chip" id="modelChip">Kimi-K2.7-Code</div>
121
- <div class="live-chip" id="liveChip"><span class="live-dot"></span><span id="liveText">Connecting</span></div>
122
- <button class="top-btn" onclick="newSession()">New session</button>
123
- <button class="icon-btn" title="Refresh project" onclick="refreshAll()">↻</button>
124
- </header>
125
-
126
- <div class="layout">
127
- <aside class="leftbar">
128
- <div class="panel-head"><span>Project</span><div class="actions"><button class="icon-btn" title="Refresh" onclick="loadTree()">↻</button><button class="icon-btn" title="Upload files" onclick="document.getElementById('uploadInput').click()">↑</button></div></div>
129
- <div class="search-box"><input id="fileSearch" placeholder="Search files…" oninput="renderTree()" /></div>
130
- <div class="panel-body"><div class="tree" id="tree"></div></div>
131
- <div class="upload-row"><button class="ghost-btn" style="width:100%" onclick="document.getElementById('uploadInput').click()">+ Add files</button><input id="uploadInput" type="file" multiple hidden onchange="uploadFiles(this.files)" /></div>
132
- </aside>
133
-
134
- <main class="center">
135
- <section class="agent-workspace">
136
- <div class="agent-head">
137
- <div class="agent-orb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M8 9h8M7 13h10M8.7 17h6.6"/><rect x="4" y="4" width="16" height="16" rx="4"/></svg></div>
138
- <div><div class="agent-title">Coding Agent</div><div style="font-size:9px;color:#637084" id="agentSub">Planner · Coder · Reviewer</div></div>
139
- <div class="head-actions"><button class="icon-btn" title="Agent activity" onclick="openMobilePanel('activity')">✦</button><button class="icon-btn" title="Settings" onclick="openMobilePanel('settings')">⚙</button></div>
140
- </div>
141
- <div class="activity" id="activityBar">
142
- <div class="stage" data-stage="planner"><span class="stage-dot"></span>Planner</div><div class="activity-line"></div>
143
- <div class="stage" data-stage="coder"><span class="stage-dot"></span>Coder</div><div class="activity-line"></div>
144
- <div class="stage" data-stage="reviewer"><span class="stage-dot"></span>Reviewer</div><div class="activity-line"></div>
145
- <div class="stage" data-stage="tester"><span class="stage-dot"></span>Tester</div>
146
- </div>
147
- <div class="chat-scroll" id="chatScroll"></div>
148
- <div class="composer">
149
- <div class="approval" id="approvalBar"><span>Planner produced a plan. Review it before file edits continue.</span><button class="primary-btn" onclick="approveAgent()">Approve</button></div>
150
- <div class="composer-box"><textarea id="prompt" rows="2" placeholder="Describe what you want the agent to build, change, debug, or inspect…"></textarea></div>
151
- <div class="composer-tools">
152
- <select id="mode" class="control" title="Agent mode"><option value="autonomous">Autonomous</option><option value="review-first">Review first</option><option value="plan">Plan only</option></select>
153
- <button class="control" onclick="attachPromptFile()">Attach</button>
154
- <button class="control" onclick="clearChat()">Clear</button>
155
- <span class="composer-hint">Enter to send · Shift+Enter for newline</span>
156
- <button class="primary-btn send-main" id="sendBtn" onclick="sendAgent()">Run agent</button>
157
- <button class="icon-btn stop-btn" id="stopBtn" style="display:none" title="Stop agent" onclick="stopAgent()">■</button>
158
- </div>
159
- </div>
160
- </section>
161
-
162
- <section class="workbench">
163
- <div class="workbench-head">
164
- <div class="wb-tabs"><button class="wb-tab active">Code</button><button class="wb-tab" onclick="focusTerminal()">Terminal</button></div>
165
- <div class="wb-tools"><button class="icon-btn" title="Save current file" onclick="saveCurrent()">✓</button><button class="icon-btn" title="Download current file" onclick="downloadCurrent()">↓</button><button class="icon-btn" title="Close editor tab" onclick="closeCurrentTab()">×</button></div>
166
- </div>
167
- <div class="workbench-body">
168
- <div class="editor-pane">
169
- <div class="editor-tabs" id="editorTabs"></div>
170
- <div class="editor-host" id="editor"></div>
171
- <div class="empty-editor" id="emptyEditor">Open a project file to inspect or edit it.</div>
172
- </div>
173
- <div class="terminal-pane" id="terminalPane">
174
- <div class="term-head"><span class="term-indicator"></span><strong>Terminal</strong><span style="color:#5f6b7b">workspace shell</span><span class="pill ok" id="terminalStatus">enabled</span><button class="icon-btn term-clear" onclick="clearTerminal()">Clear</button></div>
175
- <div class="term-log" id="terminalLog"></div>
176
- <div class="term-input"><input id="terminalInput" placeholder="run a command in the workspace…" autocomplete="off" /><button class="primary-btn" onclick="runTerminal()">Run</button></div>
177
- </div>
178
- </div>
179
- </section>
180
- </main>
181
-
182
- <aside class="rightbar">
183
- <div class="right-tabs">
184
- <button class="right-tab active" id="tabActivity" onclick="showRight('activity')">Activity</button>
185
- <button class="right-tab" id="tabChanges" onclick="showRight('changes')">Changes</button>
186
- <button class="right-tab" id="tabSettings" onclick="showRight('settings')">Settings</button>
187
- </div>
188
- <div class="right-content" id="rightContent"></div>
189
- </aside>
190
- </div>
191
- </div>
192
-
193
- <div class="mobile-bar">
194
- <button class="mobile-item active" data-mobile="agent" onclick="mobileView('agent')"><svg viewBox="0 0 24 24"><path d="M5 5h14v10H9l-4 4z"/></svg><span>Agent</span></button>
195
- <button class="mobile-item" data-mobile="files" onclick="openMobilePanel('files')"><svg viewBox="0 0 24 24"><path d="M4 6.5A2.5 2.5 0 0 1 6.5 4H10l2 2h5.5A2.5 2.5 0 0 1 20 8.5v8A2.5 2.5 0 0 1 17.5 19h-11A2.5 2.5 0 0 1 4 16.5z"/></svg><span>Files</span></button>
196
- <button class="mobile-item" data-mobile="editor" onclick="mobileView('editor')"><svg viewBox="0 0 24 24"><path d="M5 4h14v16H5zM8 8h8M8 12h8M8 16h5"/></svg><span>Editor</span></button>
197
- <button class="mobile-item" data-mobile="terminal" onclick="focusTerminal()"><svg viewBox="0 0 24 24"><path d="m5 7 4 5-4 5M12 17h7"/></svg><span>Terminal</span></button>
198
- <button class="mobile-item" data-mobile="settings" onclick="openMobilePanel('settings')"><svg viewBox="0 0 24 24"><path d="M12 8.3a3.7 3.7 0 1 0 0 7.4 3.7 3.7 0 0 0 0-7.4zM19.1 13.5l1.1.8-1.5 2.6-1.3-.6a7.8 7.8 0 0 1-1.9 1.1l-.2 1.4h-3l-.2-1.4a7.7 7.7 0 0 1-1.9-1.1l-1.3.6-1.5-2.6 1.1-.8a7.6 7.6 0 0 1 0-2.9l-1.1-.8 1.5-2.6 1.3.6a7.8 7.8 0 0 1 1.9-1.1l.2-1.4h3l.2 1.4a7.7 7.7 0 0 1 1.9 1.1l1.3-.6 1.5 2.6-1.1.8a7.6 7.6 0 0 1 0 2.9z"/></svg><span>Settings</span></button>
199
- </div>
200
-
201
- <div class="overlay" id="mobileOverlay" onclick="closeMobilePanel(event)"><div class="sheet" onclick="event.stopPropagation()"><div class="sheet-head"><strong id="sheetTitle">Panel</strong><button class="icon-btn" onclick="closeMobilePanel()">×</button></div><div class="sheet-body" id="sheetBody"></div></div></div>
202
- <div class="toast" id="toast"></div>
203
-
204
- <script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js"></script>
205
- <script>
206
- const state={
207
- sessionId:null,ws:null,editor:null,models:new Map(),opened:null,openTabs:[],files:[],events:[],status:{},running:false,
208
- right:'activity',mobile:'agent',settings:{instructions:'',mode:'autonomous'},expanded:new Set(),activeStage:'ready',changes:{changed:[],created:[],deleted:[]},activity:[]
209
- };
210
-
211
- const $=id=>document.getElementById(id);
212
- const esc=s=>String(s??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
213
- function toast(msg){const t=$('toast');t.textContent=msg;t.style.display='block';clearTimeout(toast._t);toast._t=setTimeout(()=>t.style.display='none',2600)}
214
- function api(path,opts={}){const isForm=opts.body instanceof FormData;const headers={...(opts.headers||{})};if(!isForm&&opts.body!==undefined)headers['Content-Type']='application/json';return fetch(path,{credentials:'include',...opts,headers}).then(async r=>{if(r.status===401){showLogin();throw new Error('Authentication required')}if(!r.ok){let msg=await r.text();try{const j=JSON.parse(msg);msg=j.detail||msg}catch{}throw new Error(msg||('HTTP '+r.status))}const ct=r.headers.get('content-type')||'';return ct.includes('application/json')?r.json():r;})}
215
- function showLogin(){ $('loginOverlay').style.display='grid'; setTimeout(()=>$('loginToken').focus(),80) }
216
- async function checkAuth(){const r=await fetch('/api/auth/status',{credentials:'include'});const j=await r.json();if(j.required){try{await api('/api/status');return true}catch{return false}}return true}
217
- async function login(){const token=$('loginToken').value.trim();if(!token)return; $('loginError').textContent='Checking token…';try{await fetch('/api/auth/login',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify({token})}).then(async r=>{if(!r.ok)throw new Error('Invalid access token')});$('loginOverlay').style.display='none';$('loginError').textContent='';await boot(true)}catch(e){$('loginError').textContent=e.message}}
218
-
219
- async function boot(afterLogin=false){
220
- try{
221
- if(!afterLogin && !(await checkAuth())){showLogin();return}
222
- const [status]=await Promise.all([api('/api/status'),loadTree()]);
223
- state.status=status;$('workspaceName').textContent=status.workspace||'workspace';$('modelChip').textContent=status.model||'Kimi-K2.7-Code';
224
- $('terminalStatus').textContent=status.terminal_enabled?'enabled':'disabled';$('terminalStatus').className='pill '+(status.terminal_enabled?'ok':'danger');
225
- await createSession();renderRight();renderWelcome();setLive('Ready','ready');
226
- addTerm('out','AgentSpace Code connected. Workspace: '+(status.workspace||'workspace'));
227
- }catch(e){setLive('Offline','off');if(e.message!=='Authentication required')toast(e.message)}
228
- }
229
- async function refreshAll(){try{await loadTree();if(state.opened)await openFile(state.opened,true);toast('Workspace refreshed')}catch(e){toast(e.message)}}
230
- async function createSession(){if(state.ws)try{state.ws.close()}catch{};const r=await api('/api/session',{method:'POST'});state.sessionId=r.session_id;state.events=[];state.activity=[];state.changes={changed:[],created:[],deleted:[]};await loadSessionIfAny();connectWS()}
231
- async function newSession(){await createSession();$('prompt').value='';renderChat();renderRight();setLive('Ready','ready');toast('New agent session created')}
232
- async function loadSessionIfAny(){if(!state.sessionId)return;try{const r=await api('/api/session/'+encodeURIComponent(state.sessionId));state.events=r.events||[];state.running=!!r.running}catch{}}
233
- function connectWS(){state.ws=new WebSocket((location.protocol==='https:'?'wss://':'ws://')+location.host+'/ws/'+state.sessionId);state.ws.onopen=()=>setLive('Ready','ready');state.ws.onclose=()=>{if(state.running)setLive('Disconnected','off');else setLive('Offline','off')};state.ws.onerror=()=>setLive('Connection error','off');state.ws.onmessage=e=>{try{handleEvent(JSON.parse(e.data))}catch(err){console.error(err)}}}
234
- function setLive(text,type='ready'){const chip=$('liveChip');$('liveText').textContent=text;chip.classList.toggle('busy',type==='busy');chip.classList.toggle('off',type==='off')}
235
-
236
- async function loadTree(){const r=await api('/api/tree');state.files=r.items||[];renderTree()}
237
- function fileIcon(path,isDir){const n=String(path).split('/').pop().toLowerCase();if(isDir)return '▱';if(n.endsWith('.js')||n.endsWith('.ts'))return 'JS';if(n.endsWith('.py'))return 'PY';if(n.endsWith('.html'))return 'HT';if(n.endsWith('.css'))return '#';if(n.endsWith('.json'))return '{}';if(n.endsWith('.md'))return 'MD';return '·'}
238
- function renderTree(){
239
- const host=$('tree'),query=($('fileSearch').value||'').trim().toLowerCase();host.innerHTML='';
240
- const items=state.files.filter(x=>!query||x.path.toLowerCase().includes(query));
241
- const dirs=new Set();items.forEach(x=>{let parts=x.path.split('/');for(let i=1;i<parts.length;i++)dirs.add(parts.slice(0,i).join('/'))});
242
- const all=items.concat([...dirs].filter(d=>!items.some(x=>x.path===d)).map(path=>({path,type:'dir',size:null}))).sort((a,b)=>a.path.localeCompare(b.path));
243
- const hasChild=p=>all.some(x=>x.path.startsWith(p+'/'));
244
- const renderNode=(parent,depth)=>{
245
- all.filter(x=>{const ps=x.path.split('/');return (ps.length===depth+1&&!ps.slice(0,-1).length&&depth===0)||ps.length===depth+1&&ps.slice(0,-1).join('/')===parent}).forEach(x=>{
246
- const row=document.createElement('div');row.className='tree-row '+(x.type==='dir'?'dir':'')+(x.path===state.opened?' active':'');row.style.paddingLeft=(7+depth*15)+'px';
247
- const child=hasChild(x.path);const isExp=state.expanded.has(x.path)||query;
248
- row.innerHTML='<span class="tree-arrow">'+(x.type==='dir'?(isExp?'▾':'▸'):'')+'</span><span class="tree-icon">'+esc(fileIcon(x.path,x.type==='dir'))+'</span><span class="tree-name">'+esc(x.path.split('/').pop())+'</span>'+(x.type==='file'?'<span class="file-size">'+fmtSize(x.size)+'</span>':'');
249
- row.onclick=()=>{if(x.type==='dir'){isExp?state.expanded.delete(x.path):state.expanded.add(x.path);renderTree()}else openFile(x.path)};host.appendChild(row);
250
- if(x.type==='dir'&&isExp)renderNode(x.path,depth+1);
251
- });
252
- };
253
- renderNode('',0);
254
- if(!host.children.length)host.innerHTML='<div style="padding:18px;color:#657184;font-size:11px">No files match the search.</div>';
255
- }
256
- function fmtSize(n){if(n==null)return '';if(n<1024)return n+' B';if(n<1048576)return (n/1024).toFixed(1)+' KB';return (n/1048576).toFixed(1)+' MB'}
257
- function fileLang(path){const n=path.toLowerCase();if(n.endsWith('.js'))return 'javascript';if(n.endsWith('.ts'))return 'typescript';if(n.endsWith('.tsx'))return 'typescript';if(n.endsWith('.jsx'))return 'javascript';if(n.endsWith('.py'))return 'python';if(n.endsWith('.html')||n.endsWith('.htm'))return 'html';if(n.endsWith('.css'))return 'css';if(n.endsWith('.json'))return 'json';if(n.endsWith('.md'))return 'markdown';if(n.endsWith('.sh'))return 'shell';if(n.endsWith('.yml')||n.endsWith('.yaml'))return 'yaml';if(n.endsWith('.xml'))return 'xml';return 'plaintext'}
258
- async function openFile(path,refresh=false){
259
- if(!path||state.files.find(x=>x.path===path&&x.type==='dir'))return;
260
- try{
261
- if(!state.models.has(path)||refresh){const r=await api('/api/file?path='+encodeURIComponent(path));const model=state.models.get(path);if(model)model.setValue(r.content||'');else state.models.set(path,monaco.editor.createModel(r.content||'',fileLang(path),monaco.Uri.parse('inmemory://agent-space/'+encodeURIComponent(path))));state.files=state.files.map(x=>x.path===path?{...x,content:r.content,_dirty:false}:x)}
262
- state.opened=path;if(!state.openTabs.includes(path))state.openTabs.push(path);state.editor.setModel(state.models.get(path));$('emptyEditor').style.display='none';renderEditorTabs();renderTree();
263
- }catch(e){toast(e.message)}
264
- }
265
- function renderEditorTabs(){const host=$('editorTabs');host.innerHTML=state.openTabs.map(p=>{const item=state.files.find(x=>x.path===p);const dirty=item?._dirty;return '<button class="editor-tab '+(p===state.opened?'active':'')+'" onclick="openFile(\''+escJs(p)+'\')"><span>'+esc(p.split('/').pop())+(dirty?' •':'')+'</span><span class="close" onclick="event.stopPropagation();closeTab(\''+escJs(p)+'\')">×</span></button>'}).join('')}
266
- function escJs(s){return String(s).replace(/\\/g,'\\\\').replace(/'/g,"\\'")}
267
- function closeTab(path){const i=state.openTabs.indexOf(path);if(i<0)return;state.openTabs.splice(i,1);if(state.opened===path){state.opened=state.openTabs[i-1]||state.openTabs[i]||null;if(state.opened)openFile(state.opened);else{state.editor.setModel(null);$('emptyEditor').style.display='grid'}}renderEditorTabs();renderTree()}
268
- function closeCurrentTab(){if(state.opened)closeTab(state.opened)}
269
- async function saveCurrent(){if(!state.opened||!state.editor)return;try{const content=state.editor.getValue();await api('/api/file',{method:'PUT',body:JSON.stringify({path:state.opened,content})});state.models.get(state.opened).setValue(content);state.files=state.files.map(x=>x.path===state.opened?{...x,_dirty:false,size:new Blob([content]).size}:x);renderEditorTabs();renderTree();addTerm('out','Saved '+state.opened);toast('Saved '+state.opened)}catch(e){toast(e.message)}}
270
- async function downloadCurrent(){if(!state.opened){toast('Open a file first');return}location.href='/api/download?path='+encodeURIComponent(state.opened)}
271
- function attachPromptFile(){document.getElementById('uploadInput').click();toast('Select files to add to the workspace')}
272
- async function uploadFiles(files){if(!files||!files.length)return;for(const file of files){try{const fd=new FormData();fd.append('file',file);const r=await api('/api/upload',{method:'POST',body:fd});addTerm('out','Uploaded '+r.path);toast('Uploaded '+r.path)}catch(e){toast(e.message)}}document.getElementById('uploadInput').value='';await loadTree()}
273
-
274
- function renderWelcome(){if(state.events.length){renderChat();return}$('chatScroll').innerHTML='<div class="welcome"><h2>Build with an agent, not a text box.</h2><p>Give the coding agent a goal. It can inspect the workspace, search files, edit code, run commands, review its changes, and report what it actually changed.</p><div class="quick-actions"><button onclick="quickPrompt(\'Inspect this project and explain its architecture. Do not modify files.\')">Inspect project</button><button onclick="quickPrompt(\'Find the biggest bug in this project, explain it, then fix it and verify the fix.\')">Debug & fix</button><button onclick="quickPrompt(\'Review the project for obvious issues and propose concrete improvements.\')">Code review</button><button onclick="quickPrompt(\'Add a small professional improvement to the UI that fits the existing architecture.\')">Improve UI</button></div></div>'}
275
- function quickPrompt(p){$('prompt').value=p;$('prompt').focus()}
276
- function clearChat(){state.events=[];renderWelcome()}
277
- function renderChat(){
278
- const host=$('chatScroll');if(!state.events.length){renderWelcome();return}host.innerHTML='';
279
- let renderedTool=false;
280
- state.events.forEach(e=>{
281
- if(e.type==='agent_message'&&e.text){host.appendChild(messageEl('agent',e.stage||'AGENT',e.text));}
282
- else if(e.type==='plan')host.appendChild(planEl(e.plan));
283
- else if(e.type==='review')host.appendChild(reviewEl(e.review));
284
- else if(e.type==='changes')host.appendChild(changesEl(e));
285
- else if(e.type==='tool_start'){const el=document.createElement('div');el.className='tool-live';el.innerHTML='<span class="tool-spinner"></span><span class="tool-name">'+esc(e.tool||'tool')+'</span><span class="tool-args">'+esc(JSON.stringify(e.args||{}))+'</span>';host.appendChild(el);renderedTool=true}
286
- else if(e.type==='error')host.appendChild(messageEl('agent','ERROR',e.text||'Unknown error','error'));
287
- });
288
- if(!host.children.length)renderWelcome();host.scrollTop=host.scrollHeight;
289
- }
290
- function messageEl(role,label,text,kind=''){const wrap=document.createElement('div');wrap.className='message '+role;wrap.innerHTML='<div class="avatar">'+(role==='user'?'YOU':'AI')+'</div><div class="content"><div class="msg-meta">'+esc(label)+'</div><div class="bubble '+kind+'">'+formatText(text)+'</div></div>';return wrap}
291
- function formatText(text){const s=String(text||'');let out=esc(s);out=out.replace(/```([\s\S]*?)```/g,(m,c)=>'<pre><code>'+c.trim()+'</code></pre>');out=out.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');return out}
292
- function planEl(plan){const div=document.createElement('div');const steps=(plan&&plan.steps)||[];div.className='plan-card';div.innerHTML='<div class="card-cap">Plan <span class="pill">'+steps.length+' steps</span></div><div class="card-body"><div style="margin-bottom:9px;color:#d5dce6">'+esc((plan&&plan.summary)||'Plan created')+'</div><div class="steps">'+steps.map((s,i)=>'<div class="step-row"><span class="step-num">'+(i+1)+'</span><span>'+esc(s)+'</span></div>').join('')+'</div></div>';return div}
293
- function reviewEl(review){const div=document.createElement('div');const issues=(review&&review.issues)||[];div.className='review-card';div.innerHTML='<div class="card-cap">Reviewer <span class="pill '+(issues.length?'warn':'ok')+'">'+(issues.length?issues.length+' issue(s)':'clean')+'</span></div><div class="card-body"><div>'+esc((review&&review.summary)||'Review complete')+'</div>'+(issues.length?'<div style="margin-top:7px">'+issues.map(x=>'<div class="issue">'+esc(x)+'</div>').join('')+'</div>':'');div.innerHTML+='</div>';return div}
294
- function changesEl(e){const div=document.createElement('div');div.className='changes-card';const a=e.changed||[],b=e.created||[],c=e.deleted||[];div.innerHTML='<div class="card-cap">Workspace changes</div><div class="card-body"><div class="change-list">'+a.map(x=>'<div class="change-item changed">~ '+esc(x)+'</div>').join('')+b.map(x=>'<div class="change-item created">+ '+esc(x)+'</div>').join('')+c.map(x=>'<div class="change-item deleted">− '+esc(x)+'</div>').join('')+(a.length+b.length+c.length?'':'<div style="color:#667385">No file changes.</div>')+'</div></div>';return div}
295
-
296
- async function sendAgent(){const p=$('prompt').value.trim();if(!p||state.running)return;$('prompt').value='';state.events.push({type:'agent_message',text:p,stage:'YOU'});renderChat();setRunning(true);try{await api('/api/agent',{method:'POST',body:JSON.stringify({session_id:state.sessionId,prompt:p,mode:$('mode').value,instructions:state.settings.instructions})});}catch(e){setRunning(false);state.events.push({type:'error',text:e.message});renderChat()}}
297
- async function stopAgent(){if(!state.sessionId)return;try{await api('/api/agent/stop',{method:'POST',body:JSON.stringify({session_id:state.sessionId})});toast('Stop requested')}catch(e){toast(e.message)}}
298
- async function approveAgent(){if(!state.sessionId)return;try{await api('/api/agent/approve',{method:'POST',body:JSON.stringify({session_id:state.sessionId})});$('approvalBar').classList.remove('show');toast('Plan approved')}catch(e){toast(e.message)}}
299
- function setRunning(v){state.running=v;$('sendBtn').style.display=v?'none':'inline-flex';$('stopBtn').style.display=v?'inline-flex':'none';if(v)setLive('Agent working','busy');else setLive('Ready','ready');}
300
- function handleEvent(e){
301
- state.events.push(e);state.events=state.events.slice(-300);
302
- if(e.type==='agent_status'){state.activeStage=String(e.stage||'').toLowerCase();$('agentSub').textContent=e.text||'Coding agent';setStage(state.activeStage);if(['ready','stopped','error'].includes(state.activeStage)){setRunning(false);if(state.activeStage==='error')setLive('Error','off')}}
303
- if(e.type==='plan'){state.activity.push({name:'Planner',detail:'Plan produced',tone:'green'});setStage('planner-done');}
304
- if(e.type==='approval_required'){$('approvalBar').classList.add('show');setLive('Approval required','busy')}
305
- if(e.type==='tool_start'){state.activity.unshift({name:e.tool||'tool',detail:JSON.stringify(e.args||{}),tone:'yellow',time:new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'})});setStage(toolStage(e.tool));addTerm('agent','[agent] '+(e.tool||'tool')+' '+JSON.stringify(e.args||{}))}
306
- if(e.type==='tool_result'){const r=e.result||{};if(r.stdout)addTerm('out',r.stdout);if(r.stderr)addTerm('err',r.stderr);state.activity.unshift({name:e.tool||'tool',detail:e.ok?'completed':'failed',tone:e.ok?'green':'red',time:new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'})});}
307
- if(e.type==='changes'){state.changes={changed:e.changed||[],created:e.created||[],deleted:e.deleted||[]};loadTree()}
308
- if(e.type==='review'){setStage('reviewer-done')}
309
- if(e.type==='complete'){setRunning(false);state.changes={changed:e.changed||[],created:e.created||[],deleted:e.deleted||[]};setStage('done');loadTree();addTerm('agent','[agent] completed');}
310
- if(e.type==='error'){setRunning(false);setStage('error')}
311
- renderChat();renderRight();
312
- }
313
- function toolStage(tool){const t=String(tool||'').toLowerCase();if(t.includes('list')||t.includes('search')||t.includes('read'))return 'planner';if(t.includes('write')||t.includes('patch')||t.includes('mkdir')||t.includes('delete'))return 'coder';if(t.includes('run'))return 'tester';return 'coder'}
314
- function setStage(stage){const s=String(stage||'').toLowerCase();document.querySelectorAll('.stage').forEach(x=>{x.classList.remove('active','done','error');const n=x.dataset.stage;if(s==='error'){if(n==='coder')x.classList.add('error')}else if(s==='done'||s==='ready'){x.classList.add('done')}else if(s.startsWith(n)){x.classList.add('active')}})}
315
-
316
- function showRight(which){state.right=which;['activity','changes','settings'].forEach(x=>$('tab'+cap(x)).classList.toggle('active',x===which));renderRight()}
317
- function cap(s){return s.charAt(0).toUpperCase()+s.slice(1)}
318
- function renderRight(){const host=$('rightContent');if(state.right==='activity'){host.innerHTML=activityPanel();return}if(state.right==='changes'){host.innerHTML=changesPanel();return}host.innerHTML=settingsPanel()}
319
- function activityPanel(){const acts=state.activity.slice(0,30);return '<div class="section-card"><div class="section-card-head"><span>Agents</span><span class="pill '+(state.running?'warn':'ok')+'">'+(state.running?'working':'ready')+'</span></div><div class="section-card-body"><div class="agent-grid"><div class="agent-card '+(state.activeStage.includes('planner')?'active':'')+'"><div class="agent-line"><div class="agent-badge">P</div><div><div class="agent-name">Planner</div><div class="agent-role">breaks the request into actions</div></div><div class="agent-state">'+(state.activeStage.includes('planner')?'active':'idle')+'</div></div></div><div class="agent-card '+(state.activeStage.includes('coder')?'active':'')+'"><div class="agent-line"><div class="agent-badge">C</div><div><div class="agent-name">Coder</div><div class="agent-role">reads, edits, creates files</div></div><div class="agent-state">'+(state.activeStage.includes('coder')?'active':'idle')+'</div></div></div><div class="agent-card '+(state.activeStage.includes('reviewer')?'active':'')+'"><div class="agent-line"><div class="agent-badge">R</div><div><div class="agent-name">Reviewer</div><div class="agent-role">checks the result and proposes fixes</div></div><div class="agent-state">'+(state.activeStage.includes('reviewer')?'active':'idle')+'</div></div><div class="agent-card '+(state.activeStage.includes('tester')?'active':'')+'"><div class="agent-line"><div class="agent-badge">T</div><div><div class="agent-name">Tester</div><div class="agent-role">runs commands and validation</div></div><div class="agent-state">'+(state.activeStage.includes('tester')?'active':'idle')+'</div></div></div></div></div><div class="section-card"><div class="section-card-head">Live tool activity</div><div class="section-card-body">'+(acts.length?'<div class="activity-list">'+acts.map(a=>'<div class="activity-row"><span class="dotx '+esc(a.tone||'')+'"></span><div><div style="color:#c3cdd9">'+esc(a.name||'tool')+'</div><div style="color:#5f6d7f;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(a.detail||'')+'</div></div><span class="time">'+esc(a.time||'')+'</span></div>').join('')+'</div>':'<div style="font-size:10px;color:#606c7d">Agent tool calls will appear here in real time.</div>')+'</div></div><div class="section-card"><div class="section-card-head">Runtime</div><div class="section-card-body"><div class="kv"><span class="k">Model</span><span class="v">'+esc(state.status.model||'—')+'</span><span class="k">Terminal</span><span class="v">'+(state.status.terminal_enabled?'<span class="pill ok">enabled</span>':'<span class="pill danger">disabled</span>')+'</span><span class="k">Workspace</span><span class="v">'+esc(state.status.workspace||'—')+'</span><span class="k">API key</span><span class="v"><span class="pill ok">server-side</span></span></div></div></div>'}
320
- function changesPanel(){const a=state.changes.changed||[],b=state.changes.created||[],c=state.changes.deleted||[];return '<div class="section-card"><div class="section-card-head"><span>Session changes</span><span class="pill">'+(a.length+b.length+c.length)+' file(s)</span></div><div class="section-card-body"><div class="change-list">'+a.map(x=>'<div class="change-item changed">~ '+esc(x)+'</div>').join('')+b.map(x=>'<div class="change-item created">+ '+esc(x)+'</div>').join('')+c.map(x=>'<div class="change-item deleted">− '+esc(x)+'</div>').join('')+(a.length+b.length+c.length?'':'<div style="font-size:10px;color:#5f6d7f">No changes reported in this session.</div>')+'</div></div><div class="section-card"><div class="section-card-head">What the agent can do</div><div class="section-card-body"><div class="mini-row"><span class="pill">read files</span><span class="pill">search</span><span class="pill">edit files</span><span class="pill">create folders</span><span class="pill">run commands</span><span class="pill">review</span></div></div></div>'}
321
- function settingsPanel(){return '<div class="section-card"><div class="section-card-head">Agent controls</div><div class="section-card-body"><div class="settings-grid"><div class="field"><label>Execution mode</label><select id="settingsMode"><option value="autonomous">Autonomous</option><option value="review-first">Review first</option><option value="plan">Plan only</option></select></div><div class="field"><label>Project instructions</label><textarea id="instructions" placeholder="Rules the agent should follow in this workspace…">'+esc(state.settings.instructions)+'</textarea></div><div class="setting-note">These instructions are sent with each agent request. API credentials remain on the server and are not inserted into the prompt.</div><button class="primary-btn save-settings" onclick="saveSettings()">Save agent settings</button></div></div><div class="section-card"><div class="section-card-head">Model & runtime</div><div class="section-card-body"><div class="kv"><span class="k">Provider</span><span class="v">Top Tools AI</span><span class="k">Model</span><span class="v">'+esc(state.status.model||'Kimi-K2.7-Code')+'</span><span class="k">Terminal</span><span class="v">'+(state.status.terminal_enabled?'enabled':'disabled')+'</span><span class="k">Workspace</span><span class="v">'+esc(state.status.workspace||'—')+'</span></div></div></div><div class="section-card"><div class="section-card-head">Safety controls</div><div class="section-card-body"><div class="mini-row"><span class="pill">approval mode</span><span class="pill">tool logs</span><span class="pill">stop agent</span><span class="pill">server-side key</span></div></div></div>'}
322
- function saveSettings(){const m=$('settingsMode');if(m)state.settings.mode=m.value;const i=$('instructions');if(i)state.settings.instructions=i.value;const top=$('mode');if(top)top.value=state.settings.mode;toast('Agent settings saved for this session')}
323
-
324
- function addTerm(type,text){const host=$('terminalLog');if(!host)return;const el=document.createElement('div');el.className='term-row '+type;el.textContent=String(text||'');host.appendChild(el);host.scrollTop=host.scrollHeight}
325
- function clearTerminal(){$('terminalLog').innerHTML=''}
326
- async function runTerminal(){const input=$('terminalInput');const cmd=input.value.trim();if(!cmd)return;input.value='';addTerm('cmd','$ '+cmd);try{const r=await api('/api/terminal',{method:'POST',body:JSON.stringify({command:cmd,timeout:30})});if(r.stdout)addTerm('out',r.stdout);if(r.stderr)addTerm('err',r.stderr);addTerm(r.exit_code===0?'out':'err','[exit '+r.exit_code+']')}catch(e){addTerm('err',e.message)}}
327
- function focusTerminal(){const el=$('terminalPane');el.scrollIntoView({behavior:'smooth',block:'end'});document.querySelectorAll('.mobile-item').forEach(b=>b.classList.toggle('active',b.dataset.mobile==='terminal'));setTimeout(()=>$('terminalInput').focus(),250)}
328
-
329
- function mobileView(which){state.mobile=which;document.querySelectorAll('.mobile-item').forEach(b=>b.classList.toggle('active',b.dataset.mobile===which));if(which==='agent')$('prompt').focus();if(which==='editor'){document.querySelector('.editor-pane').scrollIntoView({behavior:'smooth',block:'start'});}if(which==='terminal'){focusTerminal()}}
330
- function openMobilePanel(which){let title='Panel',body='';if(which==='files'){title='Project files';body='<div class="search-box" style="border:0;padding:0 0 9px"><input id="mobileFileSearch" placeholder="Search files…" oninput="syncMobileSearch()" /></div><div class="tree" id="mobileTree"></div>'}else if(which==='settings'){title='Agent settings';body=settingsPanel()}else{title='Agent activity';body=activityPanel()}$('sheetTitle').textContent=title;$('sheetBody').innerHTML=body;$('mobileOverlay').style.display='block';if(which==='files'){renderMobileTree()}if(which==='settings'){const m=$('settingsMode');if(m)m.value=state.settings.mode}}
331
- function renderMobileTree(){const host=$('mobileTree');if(!host)return;const q=($('mobileFileSearch')?.value||'').toLowerCase();host.innerHTML='';const prev=$('fileSearch').value;$('fileSearch').value=q;const tmp=$('tree');const html=tmp.innerHTML;$('fileSearch').value=prev;host.innerHTML=html;host.querySelectorAll('.tree-row').forEach(row=>row.onclick=()=>{const idx=[...document.querySelectorAll('#tree .tree-row')].findIndex(x=>x.textContent===row.textContent);const real=document.querySelectorAll('#tree .tree-row')[idx];if(real)real.click();closeMobilePanel()})}
332
- function syncMobileSearch(){const q=$('mobileFileSearch').value;const main=$('fileSearch');const prev=main.value;main.value=q;renderTree();renderMobileTree();main.value=prev}
333
- function closeMobilePanel(e){if(e&&e.target!==$('mobileOverlay'))return;$('mobileOverlay').style.display='none'}
334
-
335
- document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='s'){e.preventDefault();saveCurrent()}if(e.key==='Escape'&&$('mobileOverlay').style.display==='block')closeMobilePanel()});
336
- $('prompt').addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendAgent()}});$('terminalInput').addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();runTerminal()}});
337
-
338
- function initMonaco(){return new Promise(resolve=>{require.config({paths:{vs:'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs'}});require(['vs/editor/editor.main'],()=>{state.editor=monaco.editor.create($('editor'),{value:'',language:'plaintext',theme:'vs-dark',automaticLayout:true,fontSize:13,minimap:{enabled:false},padding:{top:10,bottom:10},scrollBeyondLastLine:false,wordWrap:'on',tabSize:2,renderLineHighlight:'line'});state.editor.onDidChangeModelContent(()=>{if(state.opened){state.files=state.files.map(x=>x.path===state.opened?{...x,_dirty:true}:x);renderEditorTabs();renderTree()}});resolve()})})}
339
- (async()=>{await initMonaco();await boot()})();
340
- </script>
341
- </body>
342
- </html>