pi-web-ui 0.45.0 → 0.47.0
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/dist/server/agent-service.js +137 -27
- package/package.json +1 -1
- package/web/dist/assets/{TerminalPanel-C2R8nfig.js → TerminalPanel-ClyNBJJt.js} +1 -1
- package/web/dist/assets/index-BvfQTKan.js +19 -0
- package/web/dist/assets/index-xAU0zXkW.css +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-CODfCAWK.css +0 -10
- package/web/dist/assets/index-CeePtC8A.js +0 -19
|
@@ -1030,6 +1030,7 @@ export class ClientSession {
|
|
|
1030
1030
|
// (new chat + first message, completed turns, compaction, etc.).
|
|
1031
1031
|
case "agent_end": {
|
|
1032
1032
|
this.scheduleSessionsRefresh();
|
|
1033
|
+
this.refreshConversationTitle(conv);
|
|
1033
1034
|
// Manual interrupt (Stop button / abort): the last assistant message
|
|
1034
1035
|
// carries stopReason "aborted". A half-finished run should NOT be
|
|
1035
1036
|
// reviewed (it would fail and inject a revision, only to be stopped
|
|
@@ -1059,6 +1060,7 @@ export class ClientSession {
|
|
|
1059
1060
|
}
|
|
1060
1061
|
case "entry_appended":
|
|
1061
1062
|
this.scheduleSessionsRefresh();
|
|
1063
|
+
this.refreshConversationTitle(conv);
|
|
1062
1064
|
break;
|
|
1063
1065
|
case "message_update": {
|
|
1064
1066
|
// Live assistant-message increment, deliberately OUTSIDE the snapshot
|
|
@@ -1125,6 +1127,21 @@ export class ClientSession {
|
|
|
1125
1127
|
}, 800);
|
|
1126
1128
|
// pushSessions no-ops unless the client opted in via list_sessions.
|
|
1127
1129
|
}
|
|
1130
|
+
/** Refresh a conversation's title from its persisted first user message
|
|
1131
|
+
* while it is still unnamed. Runs off the event stream (entry_appended /
|
|
1132
|
+
* agent_end) rather than the prompt() call site, so ANY entry path that
|
|
1133
|
+
* lands a message names the chat the moment it is persisted — a rename
|
|
1134
|
+
* skipped by the prompt-start fast path (e.g. a concurrent switch) is
|
|
1135
|
+
* recovered here instead of leaving a permanent “新对话”. */
|
|
1136
|
+
refreshConversationTitle(conv) {
|
|
1137
|
+
if (conv.title !== DEFAULT_CONV_TITLE)
|
|
1138
|
+
return;
|
|
1139
|
+
const title = conversationTitle(conv.session);
|
|
1140
|
+
if (title === DEFAULT_CONV_TITLE)
|
|
1141
|
+
return;
|
|
1142
|
+
conv.title = title;
|
|
1143
|
+
this.emitConversations();
|
|
1144
|
+
}
|
|
1128
1145
|
/** Serialize a persisted message with a STABLE id + cached object reference. */
|
|
1129
1146
|
serializeCached(m) {
|
|
1130
1147
|
const conv = this.conv;
|
|
@@ -1745,6 +1762,11 @@ export class ClientSession {
|
|
|
1745
1762
|
* after the current turn settles, skipping remaining planned tool calls.
|
|
1746
1763
|
*/
|
|
1747
1764
|
queue = false) {
|
|
1765
|
+
// Captured at the START (before any await): the conversation being
|
|
1766
|
+
// addressed by this prompt. See the naming block below — a concurrent
|
|
1767
|
+
// switch/new_chat while prompt() is in flight must never target a
|
|
1768
|
+
// different conversation.
|
|
1769
|
+
const conv = this.conv;
|
|
1748
1770
|
try {
|
|
1749
1771
|
const s = this.session;
|
|
1750
1772
|
// Native slash commands (see NATIVE_COMMANDS) are executed here and
|
|
@@ -1760,6 +1782,19 @@ export class ClientSession {
|
|
|
1760
1782
|
// is refused until admission reopens.
|
|
1761
1783
|
if (this.quiesceBlocked())
|
|
1762
1784
|
return;
|
|
1785
|
+
// Name the conversation from its FIRST prompt immediately, before any
|
|
1786
|
+
// await: the typed text IS the name. The `conv` reference was captured
|
|
1787
|
+
// before the try block, so a concurrent switch/new_chat while prompt()
|
|
1788
|
+
// is in flight can never rename a DIFFERENT conversation — or miss the
|
|
1789
|
+
// rename entirely. A failed send still leaves the name, which matches
|
|
1790
|
+
// what the user typed intent-wise; the entry_appended fallback below
|
|
1791
|
+
// re-derives it from the persisted transcript when needed.
|
|
1792
|
+
if (conv.title === DEFAULT_CONV_TITLE && text.trim()) {
|
|
1793
|
+
const trimmed = text.trim().replace(/\s+/g, " ");
|
|
1794
|
+
conv.title =
|
|
1795
|
+
trimmed.length > 30 ? `${trimmed.slice(0, 30)}…` : trimmed;
|
|
1796
|
+
this.emitConversations();
|
|
1797
|
+
}
|
|
1763
1798
|
// Attach files as independent nextTurn context messages (asides) so the
|
|
1764
1799
|
// user message stays clean; they render as separate attachment cards.
|
|
1765
1800
|
const asides = await buildAttachmentMessages({
|
|
@@ -1798,16 +1833,10 @@ export class ClientSession {
|
|
|
1798
1833
|
text: `提示发送失败:${err.message}`,
|
|
1799
1834
|
});
|
|
1800
1835
|
}
|
|
1801
|
-
//
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
conv.title = trimmed.length > 30 ? `${trimmed.slice(0, 30)}…` : trimmed;
|
|
1806
|
-
this.emitConversations();
|
|
1807
|
-
}
|
|
1808
|
-
// The active conversation has been continued since it was opened — it
|
|
1809
|
-
// must not be dismissed when the user switches away. (Also bumps the
|
|
1810
|
-
// per-project "most recently active" order used by set_cwd.)
|
|
1836
|
+
// The active conversation (captured at prompt start — see above) has been
|
|
1837
|
+
// continued since it was opened — it must not be dismissed when the user
|
|
1838
|
+
// switches away. (Also bumps the per-project "most recently active"
|
|
1839
|
+
// order used by set_cwd.)
|
|
1811
1840
|
conv.promptedSinceActive = true;
|
|
1812
1841
|
conv.lastActiveAt = Date.now();
|
|
1813
1842
|
// Fresh run — restart the stall watchdog window.
|
|
@@ -2133,9 +2162,12 @@ export class ClientSession {
|
|
|
2133
2162
|
return;
|
|
2134
2163
|
const displaced = this.displaceActive();
|
|
2135
2164
|
this.activeId = id;
|
|
2136
|
-
|
|
2137
|
-
//
|
|
2138
|
-
//
|
|
2165
|
+
const newCwd = this.conv.cwd;
|
|
2166
|
+
// A listed conversation may belong to ANOTHER project (cross-project
|
|
2167
|
+
// running list). Switching to it must also switch the active workspace
|
|
2168
|
+
// — otherwise the file tree / session history / recent-projects order
|
|
2169
|
+
// would keep showing the OLD project while the chat shows the new one.
|
|
2170
|
+
const cwdChanged = newCwd !== this.cwd;
|
|
2139
2171
|
if (displaced)
|
|
2140
2172
|
this.removeConversation(displaced.id);
|
|
2141
2173
|
this.conv.promptedSinceActive = false;
|
|
@@ -2146,15 +2178,33 @@ export class ClientSession {
|
|
|
2146
2178
|
this.pushTerminals();
|
|
2147
2179
|
// The switched-to conversation has its own runtime (own resource cache).
|
|
2148
2180
|
void this.pushSlashCommands();
|
|
2181
|
+
if (cwdChanged) {
|
|
2182
|
+
this.cwd = newCwd;
|
|
2183
|
+
// Mirror set_cwd's project-switch side-effects so the whole UI follows
|
|
2184
|
+
// the new workspace, not just the chat pane.
|
|
2185
|
+
try {
|
|
2186
|
+
this.onCwdChanged?.(newCwd);
|
|
2187
|
+
}
|
|
2188
|
+
catch {
|
|
2189
|
+
/* hook failure must not break the switch */
|
|
2190
|
+
}
|
|
2191
|
+
this.stateStore.remember(this.clientId, newCwd);
|
|
2192
|
+
void this.pushProjects();
|
|
2193
|
+
void this.refreshSessions();
|
|
2194
|
+
void this.listFiles(undefined);
|
|
2195
|
+
void this.listCommands();
|
|
2196
|
+
}
|
|
2149
2197
|
this.flushSnapshot();
|
|
2150
2198
|
}
|
|
2151
|
-
/** Push
|
|
2199
|
+
/** Push every running conversation across ALL projects to the client. The
|
|
2200
|
+
* running-conversation list is global so a background run from another
|
|
2201
|
+
* workspace stays visible; clicking one switches both the conversation and
|
|
2202
|
+
* its project (see switchConversation). The client groups the list by cwd. */
|
|
2152
2203
|
emitConversations() {
|
|
2153
2204
|
const conversations = [];
|
|
2154
2205
|
for (const conv of this.convs.values()) {
|
|
2155
|
-
//
|
|
2156
|
-
|
|
2157
|
-
if (conv.cwd !== this.cwd || !conv.listed)
|
|
2206
|
+
// Only conversations that were displaced to the background while running.
|
|
2207
|
+
if (!conv.listed)
|
|
2158
2208
|
continue;
|
|
2159
2209
|
let messageCount = 0;
|
|
2160
2210
|
let isStreaming = false;
|
|
@@ -2261,26 +2311,86 @@ export class ClientSession {
|
|
|
2261
2311
|
});
|
|
2262
2312
|
}
|
|
2263
2313
|
}
|
|
2264
|
-
/**
|
|
2314
|
+
/** Open a persisted session as the active conversation (from listSessions).
|
|
2315
|
+
*
|
|
2316
|
+
* A persisted-session click must follow the same ownership rule as
|
|
2317
|
+
* new_chat/switch_conversation: every open conversation keeps its own
|
|
2318
|
+
* runtime. AgentSessionRuntime.switchSession() tears down (and aborts) the
|
|
2319
|
+
* current runtime, which would otherwise stop a response merely because the
|
|
2320
|
+
* user opened history while it was streaming.
|
|
2321
|
+
*/
|
|
2265
2322
|
async switchSession(path) {
|
|
2266
2323
|
if (this.quiesceBlocked())
|
|
2267
2324
|
return;
|
|
2325
|
+
let openedRuntime = null;
|
|
2326
|
+
let openedTerminals = null;
|
|
2268
2327
|
try {
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
//
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2328
|
+
const targetPath = resolve(path);
|
|
2329
|
+
// A session may already be open in the running-conversation map. Reuse it
|
|
2330
|
+
// instead of creating a second writer for the same JSONL transcript.
|
|
2331
|
+
for (const conv of this.convs.values()) {
|
|
2332
|
+
const sessionFile = conv.session.sessionFile;
|
|
2333
|
+
if (sessionFile && resolve(sessionFile) === targetPath) {
|
|
2334
|
+
await this.switchConversation(conv.id);
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
const sessionManager = SessionManager.open(targetPath);
|
|
2339
|
+
const targetCwd = sessionManager.getCwd();
|
|
2340
|
+
const conversationId = this.nextConversationId();
|
|
2341
|
+
openedTerminals = this.makeTerminalManager(conversationId, targetCwd);
|
|
2342
|
+
openedRuntime = await createAgentSessionRuntime(this.makeRuntimeFactory(openedTerminals), {
|
|
2343
|
+
cwd: targetCwd,
|
|
2344
|
+
agentDir: this.agentDir,
|
|
2345
|
+
sessionManager,
|
|
2346
|
+
});
|
|
2347
|
+
// Only displace the old active conversation after the replacement runtime
|
|
2348
|
+
// is known-good. This keeps a failed history open entirely non-destructive.
|
|
2349
|
+
const oldListed = this.conv.listed;
|
|
2350
|
+
const displaced = this.displaceActive();
|
|
2351
|
+
const openInProject = [...this.convs.values()].filter((c) => c.cwd === targetCwd).length +
|
|
2352
|
+
1 -
|
|
2353
|
+
(displaced?.cwd === targetCwd ? 1 : 0);
|
|
2354
|
+
if (openInProject > MAX_OPEN_CONVERSATIONS) {
|
|
2355
|
+
// displaceActive() may have promoted a streaming conversation into the
|
|
2356
|
+
// running list. Roll that presentation-only mutation back because no
|
|
2357
|
+
// switch will take place.
|
|
2358
|
+
this.conv.listed = oldListed;
|
|
2359
|
+
openedTerminals.killAll();
|
|
2360
|
+
await openedRuntime.dispose();
|
|
2361
|
+
openedRuntime = null;
|
|
2362
|
+
openedTerminals = null;
|
|
2363
|
+
this.emit({
|
|
2364
|
+
type: "notice",
|
|
2365
|
+
level: "warning",
|
|
2366
|
+
text: `当前项目运行的对话已达上限(${MAX_OPEN_CONVERSATIONS} 个),请先打开某个对话并离开(不继续对话)以移出列表`,
|
|
2367
|
+
});
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
const conv = this.makeConversation(openedRuntime, conversationId, openedTerminals);
|
|
2276
2371
|
// Deliberately resumed — must not be dismissed when the user later
|
|
2277
2372
|
// switches away without sending a new message.
|
|
2278
|
-
|
|
2373
|
+
conv.promptedSinceActive = true;
|
|
2374
|
+
this.convs.set(conv.id, conv);
|
|
2375
|
+
this.activeId = conv.id;
|
|
2376
|
+
openedRuntime = null;
|
|
2377
|
+
openedTerminals = null;
|
|
2378
|
+
if (displaced)
|
|
2379
|
+
this.removeConversation(displaced.id);
|
|
2380
|
+
await this.bindSession();
|
|
2381
|
+
this.cwd = targetCwd;
|
|
2382
|
+
this.conv.lastActiveAt = Date.now();
|
|
2383
|
+
this.webUi.refresh();
|
|
2279
2384
|
this.emitConversations();
|
|
2280
|
-
|
|
2385
|
+
this.goalSvc.emitGoalStatus();
|
|
2386
|
+
this.pushTerminals();
|
|
2387
|
+
// The restored conversation has a fresh project-bound resource cache.
|
|
2281
2388
|
void this.pushSlashCommands();
|
|
2282
2389
|
}
|
|
2283
2390
|
catch (err) {
|
|
2391
|
+
openedTerminals?.killAll();
|
|
2392
|
+
if (openedRuntime)
|
|
2393
|
+
await openedRuntime.dispose().catch(() => { });
|
|
2284
2394
|
this.emit({
|
|
2285
2395
|
type: "notice",
|
|
2286
2396
|
level: "error",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as d,j as n}from"./markdown-DRBrS2Nf.js";import{b as $,T as R,u as O,F as P,a as K,c as q,d as L,e as B,f as J,g as X,h as G,r as Q}from"./index-
|
|
1
|
+
import{a as d,j as n}from"./markdown-DRBrS2Nf.js";import{b as $,T as R,u as O,F as P,a as K,c as q,d as L,e as B,f as J,g as X,h as G,r as Q}from"./index-BvfQTKan.js";import{D as U,o as V}from"./xterm-D1D2FVe3.js";import"./react-C9ovnpIm.js";function W({conversationId:s,terminalId:r,command:x,cwd:a,active:f,send:m,register:v}){const w=d.useRef(null),N=d.useRef(null),b=x?JSON.stringify(x):"";return d.useEffect(()=>{const p=w.current;if(!p)return;const t=new U({theme:$(),fontFamily:'"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',fontSize:13,cursorBlink:!0,scrollback:8e3}),c=new V;t.loadAddon(c),t.open(p),N.current={term:t,fit:c},f&&t.focus();const h=()=>{t.options.theme=$()};window.addEventListener(R,h),t.attachCustomKeyEventHandler(o=>{var F;if(o.type!=="keydown")return!0;const E=(F=o.key)==null?void 0:F.toLowerCase();if((o.ctrlKey||o.metaKey)&&E==="v")return!1;if(o.ctrlKey&&!o.shiftKey&&!o.altKey&&E==="c"&&t.hasSelection()){const T=t.textarea;return T&&(T.value=t.getSelection(),T.select()),!1}return!0});const g=v(s,r,{write:o=>t.write(o),dispose:()=>t.dispose()}),C=()=>{try{c.fit(),m({type:"terminal_resize",terminalId:r,conversationId:s,cols:t.cols,rows:t.rows})}catch{}},j=requestAnimationFrame(()=>{try{c.fit()}catch{}m(x?{type:"run_command",terminalId:r,conversationId:s,command:x,cols:t.cols,rows:t.rows}:{type:"terminal_create",terminalId:r,conversationId:s,cwd:a,cols:t.cols,rows:t.rows})}),k=t.onData(o=>{m({type:"terminal_input",terminalId:r,conversationId:s,data:o})});let y=null;return typeof ResizeObserver<"u"&&(y=new ResizeObserver(()=>{p.offsetWidth>0&&p.offsetHeight>0&&C()}),y.observe(p)),()=>{cancelAnimationFrame(j),k.dispose(),window.removeEventListener(R,h),y==null||y.disconnect(),g(),t.dispose(),N.current=null}},[s,r,b,m,v]),d.useEffect(()=>{if(!f)return;const p=requestAnimationFrame(()=>{const t=N.current;if(t){try{t.fit.fit(),m({type:"terminal_resize",terminalId:r,conversationId:s,cols:t.term.cols,rows:t.term.rows})}catch{}t.term.focus()}});return()=>cancelAnimationFrame(p)},[f]),n.jsx("div",{ref:w,className:`term-xterm ${f?"":"hidden"}`})}const A={name:"",command:"",cwd:"${pwd}"};function se({chat:s,send:r,terminal:x}){const a=O(),[f,m]=d.useState(null),[v,w]=d.useState(!1),[N,b]=d.useState(!1),[p,t]=d.useState(null),[c,h]=d.useState(A),[g,C]=d.useState(null),j=d.useRef(null);d.useEffect(()=>{s.terminals.length===0?m(null):s.terminals.some(e=>e.id===f)||m(s.terminals[s.terminals.length-1].id)},[s.terminals,f]),d.useEffect(()=>()=>{j.current&&clearTimeout(j.current)},[]);const k=e=>{var u;if(!s.ready)return;const i=Q(),l=s.activeConversationId||((u=s.state)==null?void 0:u.conversationId)||"";x.create({...e,id:i,conversationId:l,cols:e.cols??80,rows:e.rows??24,running:!0,exitCode:null}),m(i),w(!1)},y=()=>{var e;return k({title:a("terminalTitle",{n:s.terminals.length+1}),cwd:((e=s.state)==null?void 0:e.cwd)??""})},o=e=>{var u;const i=e.name||e.command,l=s.terminals.find(_=>_.title===i);if(l){x.restart(l.id),m(l.id),r({type:"run_command",terminalId:l.id,conversationId:l.conversationId,command:e,cols:80,rows:24});return}k({title:i,cwd:((u=s.state)==null?void 0:u.cwd)??"",command:e})},E=e=>{const i=s.terminals.find(l=>l.id===e);if(i&&r({type:"terminal_kill",terminalId:e,conversationId:i.conversationId}),x.close(e),f===e){const l=s.terminals.filter(u=>u.id!==e);m(l.length>0?l[l.length-1].id:null)}},F=()=>{b(!0),t(null),h(A)},T=e=>{const i=s.commands[e];i&&(b(!1),t(e),h({name:i.name,command:i.command,cwd:i.cwd??""}))},D=()=>{b(!1),t(null)},S=()=>{const e=c.name.trim(),i=c.command.trim();if(!e||!i)return;const l=c.cwd.trim(),u={name:e,command:i,cwd:l||void 0},_=N?[...s.commands,u]:p!==null?s.commands.map((z,H)=>H===p?u:z):s.commands;r({type:"save_commands",commands:_}),D()},I=e=>{if(g===e){const i=s.commands.filter((l,u)=>u!==e);r({type:"save_commands",commands:i}),C(null),j.current&&clearTimeout(j.current)}else C(e),j.current&&clearTimeout(j.current),j.current=setTimeout(()=>C(null),2500)},M=N||p!==null;return n.jsxs("div",{className:"terminal-view",children:[n.jsxs("aside",{className:`term-side term-commands ${v?"open":""}`,children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("commands")}),n.jsxs("div",{className:"panel-header-actions",children:[n.jsx("button",{type:"button",className:"panel-refresh",title:a("rerun"),onClick:()=>r({type:"list_commands"}),children:n.jsx(P,{})}),n.jsx("button",{type:"button",className:"panel-new",title:a("newCommand"),onClick:F,children:n.jsx(K,{})})]})]}),n.jsx("div",{className:"panel-body",children:M?n.jsxs("div",{className:"cmd-form",children:[n.jsx("label",{htmlFor:"cmd-name",children:a("name")}),n.jsx("input",{id:"cmd-name",className:"cmd-input",value:c.name,placeholder:a("exampleName"),autoFocus:!0,onChange:e=>h({...c,name:e.target.value})}),n.jsx("label",{htmlFor:"cmd-command",children:a("command")}),n.jsx("input",{id:"cmd-command",className:"cmd-input",value:c.command,placeholder:a("exampleCommand"),onChange:e=>h({...c,command:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&S()}}),n.jsxs("label",{htmlFor:"cmd-cwd",children:[a("directory")," ",n.jsx("span",{className:"cmd-hint",children:a("cwdHint")})]}),n.jsx("input",{id:"cmd-cwd",className:"cmd-input",value:c.cwd,placeholder:"${pwd}",onChange:e=>h({...c,cwd:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&S()}}),n.jsxs("div",{className:"cmd-form-actions",children:[n.jsx("button",{type:"button",className:"btn",onClick:D,children:a("cancel")}),n.jsx("button",{type:"button",className:"btn primary",disabled:!c.name.trim()||!c.command.trim(),onClick:S,children:a("save")})]})]}):n.jsxs(n.Fragment,{children:[s.commands.length===0&&n.jsx("div",{className:"panel-empty",children:a("noCommands")}),s.commands.map((e,i)=>n.jsxs("div",{className:"cmd-item",children:[n.jsx("button",{type:"button",className:"cmd-run",title:a("clickToRun"),onClick:()=>o(e),children:n.jsx(q,{})}),n.jsxs("button",{type:"button",className:"cmd-main",title:a("clickToRun"),onClick:()=>o(e),children:[n.jsx("span",{className:"cmd-name",children:e.name}),n.jsx("span",{className:"cmd-command",children:e.command}),e.cwd&&n.jsx("span",{className:"cmd-cwd",children:e.cwd})]}),n.jsx("button",{type:"button",className:"cmd-act",title:a("edit"),onClick:()=>T(i),children:n.jsx(L,{})}),n.jsx("button",{type:"button",className:`cmd-act del ${g===i?"confirm":""}`,title:a("delete"),onClick:()=>I(i),children:g===i?a("confirmQ"):n.jsx(B,{})})]},i))]})}),n.jsxs("div",{className:"term-tabs-block",children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("terminal")}),n.jsx("button",{type:"button",className:"panel-new",title:a("newTerminal"),onClick:y,children:n.jsx(K,{})})]}),n.jsxs("div",{className:"panel-body",children:[s.terminals.length===0&&n.jsx("div",{className:"panel-empty",children:a("noTerminal")}),s.terminals.map(e=>n.jsxs("div",{className:`term-tab ${e.id===f?"active":""}`,children:[n.jsxs("button",{type:"button",className:"term-tab-main",title:`${e.cwd}${e.command?`
|
|
2
2
|
> ${e.command.command}`:""}`,onClick:()=>{m(e.id),w(!1)},children:[n.jsx("span",{className:`term-tab-dot ${e.running?"run":"exit"}`}),n.jsxs("span",{className:"term-tab-title",children:[e.title,!e.running&&n.jsx("span",{className:"term-tab-exit",children:a("exited",{code:e.exitCode===null?"":` ${e.exitCode}`})})]})]}),n.jsx("button",{type:"button",className:"term-tab-close",title:a("closeTerminal"),onClick:()=>E(e.id),children:n.jsx(J,{})})]},e.id))]})]})]}),n.jsxs("div",{className:"term-main",children:[v&&n.jsx("div",{className:"drawer-backdrop",onClick:()=>w(!1)}),n.jsx("button",{type:"button",className:"term-side-toggle",title:a("commands"),onClick:()=>w(e=>!e),children:n.jsx(X,{})}),s.terminals.length===0?n.jsxs("div",{className:"term-empty",children:[n.jsx(G,{className:"term-empty-icon"}),n.jsx("div",{className:"term-empty-title",children:a("builtinTerminal")}),n.jsx("div",{className:"term-empty-sub",children:a("termEmptySub")})]}):s.terminals.map(e=>n.jsx(W,{conversationId:e.conversationId,terminalId:e.id,command:e.command,cwd:e.cwd,active:e.id===f,send:r,register:x.register},`${e.conversationId}:${e.id}`))]})]})}export{se as TerminalPanel};
|