treesap 0.1.10 → 0.1.11
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/components/ChatInput.d.ts +7 -0
- package/dist/components/ChatInput.d.ts.map +1 -0
- package/dist/components/ChatInput.js +11 -0
- package/dist/components/ChatInput.js.map +1 -0
- package/dist/components/Sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar.js +2 -1
- package/dist/components/Sidebar.js.map +1 -1
- package/dist/static/components/ChatInput.js +237 -0
- package/dist/static/components/Terminal.js +2 -0
- package/dist/static/styles/main.css +69 -0
- package/package.json +1 -1
- package/src/components/ChatInput.tsx +56 -0
- package/src/components/Sidebar.tsx +9 -2
- package/src/static/components/ChatInput.js +237 -0
- package/src/static/components/Terminal.js +2 -0
- package/src/static/styles/main.css +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatInput.d.ts","sourceRoot":"","sources":["../../src/components/ChatInput.tsx"],"names":[],"mappings":"AAAA,UAAU,cAAc;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAiB,EAAE,UAAyB,EAAE,EAAE,cAAc,kDAkDzF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
+
export function ChatInput({ id = "chat-input", terminalId = "terminal-1" }) {
|
|
3
|
+
return (_jsxs("sapling-island", { loading: "visible", children: [_jsx("template", { children: _jsx("script", { type: "module", src: "/components/ChatInput.js" }) }), _jsx("div", { id: id, class: "border-t border-[#3c3c3c] bg-[#2d2d30] p-3", children: _jsxs("div", { class: "flex items-end gap-2", children: [_jsx("div", { class: "flex-1 relative", children: _jsx("textarea", { id: `${id}-textarea`, placeholder: "Type your command or message...", class: "w-full min-h-[40px] max-h-[120px] px-3 py-2 bg-[#1e1e1e] border border-[#3c3c3c] rounded-lg text-[#cccccc] placeholder-[#888] resize-none focus:outline-none focus:border-[#0e639c] transition-colors text-sm font-mono", rows: 1 }) }), _jsx("button", { type: "button", id: `${id}-send-btn`, class: "px-3 py-2 bg-[#0e639c] hover:bg-[#1177bb] disabled:bg-[#3c3c3c] disabled:text-[#888] text-white rounded-lg transition-colors flex-shrink-0 flex items-center justify-center min-h-[40px]", title: "Send to Input Field", children: _jsx("iconify-icon", { icon: "tabler:arrow-up", width: "16", height: "16" }) }), _jsx("button", { type: "button", id: `${id}-execute-btn`, class: "px-3 py-2 bg-[#28a745] hover:bg-[#218838] disabled:bg-[#3c3c3c] disabled:text-[#888] text-white rounded-lg transition-colors flex-shrink-0 flex items-center justify-center min-h-[40px]", title: "Execute Command", children: _jsx("iconify-icon", { icon: "tabler:player-play", width: "16", height: "16" }) })] }) }), _jsx("script", { dangerouslySetInnerHTML: { __html: `
|
|
4
|
+
// Pass chat input data to JavaScript
|
|
5
|
+
window.chatInputData_${id.replace(/-/g, '_')} = {
|
|
6
|
+
chatInputId: '${id}',
|
|
7
|
+
terminalId: '${terminalId}'
|
|
8
|
+
};
|
|
9
|
+
` } })] }));
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=ChatInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatInput.js","sourceRoot":"","sources":["../../src/components/ChatInput.tsx"],"names":[],"mappings":";AAKA,MAAM,UAAU,SAAS,CAAC,EAAE,EAAE,GAAG,YAAY,EAAE,UAAU,GAAG,YAAY,EAAkB;IACxF,OAAO,CACL,0BAAgB,OAAO,EAAC,SAAS,aAC/B,6BACE,iBAAQ,IAAI,EAAC,QAAQ,EAAC,GAAG,EAAC,0BAA0B,GAAU,GACrD,EAEX,cAAK,EAAE,EAAE,EAAE,EAAE,KAAK,EAAC,4CAA4C,YAC7D,eAAK,KAAK,EAAC,sBAAsB,aAE/B,cAAK,KAAK,EAAC,iBAAiB,YAC1B,mBACE,EAAE,EAAE,GAAG,EAAE,WAAW,EACpB,WAAW,EAAC,iCAAiC,EAC7C,KAAK,EAAC,yNAAyN,EAC/N,IAAI,EAAE,CAAC,GACG,GACR,EAGN,iBACE,IAAI,EAAC,QAAQ,EACb,EAAE,EAAE,GAAG,EAAE,WAAW,EACpB,KAAK,EAAC,0LAA0L,EAChM,KAAK,EAAC,qBAAqB,YAE3B,uBAAc,IAAI,EAAC,iBAAiB,EAAC,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,GAAgB,GACpE,EAGT,iBACE,IAAI,EAAC,QAAQ,EACb,EAAE,EAAE,GAAG,EAAE,cAAc,EACvB,KAAK,EAAC,0LAA0L,EAChM,KAAK,EAAC,iBAAiB,YAEvB,uBAAc,IAAI,EAAC,oBAAoB,EAAC,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,GAAgB,GACvE,IACL,GACF,EAEN,iBAAQ,uBAAuB,EAAE,EAAC,MAAM,EAAE;;+BAEjB,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;0BAC1B,EAAE;yBACH,UAAU;;OAE5B,EAAC,GAAW,IACE,CAClB,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../src/components/Sidebar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../src/components/Sidebar.tsx"],"names":[],"mappings":"AAGA,UAAU,YAAY;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAc,EAAE,WAAkB,EAAE,gBAAgB,EAAE,EAAE,YAAY,kDAyF7F"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
2
|
import { Terminal as TerminalComponent } from "./Terminal.js";
|
|
3
|
+
import { ChatInput } from "./ChatInput.js";
|
|
3
4
|
export function Sidebar({ id = "sidebar", previewPort = 1234, workingDirectory }) {
|
|
4
|
-
return (_jsxs("sapling-island", { loading: "visible", children: [_jsxs("template", { children: [_jsx("script", { type: "module", src: "https://code.iconify.design/iconify-icon/2.0.0/iconify-icon.min.js" }), _jsx("script", { type: "module", src: "/components/Sidebar.js" })] }), _jsx("div", { id: `${id}-backdrop`, class: "fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-40 transition-opacity duration-300 opacity-0 pointer-events-none md:hidden" }), _jsxs("div", { id: `${id}-pane`, class: "fixed left-0 top-0 h-full w-full z-50 transform -translate-x-full transition-transform duration-300 ease-in-out md:relative md:translate-x-0 md:w-2/5 md:z-auto border-r border-[#3c3c3c] flex flex-col bg-[#252526]", children: [_jsx("div", { class: "p-3 border-b border-[#3c3c3c] bg-[#2d2d30] flex-shrink-0", children: _jsxs("div", { class: "flex items-center gap-2", children: [_jsx("button", { type: "button", id: `${id}-close-btn`, class: "p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white md:hidden", title: "Close Sidebar", children: _jsx("iconify-icon", { icon: "tabler:x", width: "16", height: "16" }) }), _jsx("a", { href: "/", class: "p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white", title: "Back to Home", children: _jsx("iconify-icon", { icon: "tabler:arrow-left", width: "16", height: "16" }) }), _jsx("button", { type: "button", id: "live-preview-refresh-btn", class: "p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white", title: "Reload", children: _jsx("iconify-icon", { icon: "tabler:refresh", width: "16", height: "16" }) }), _jsxs("div", { class: "flex-1 flex items-center bg-[#1e1e1e] border border-[#3c3c3c] rounded px-3 py-2 hover:border-[#0e639c] focus-within:border-[#0e639c] transition-all", children: [_jsx("iconify-icon", { icon: "tabler:world", width: "16", height: "16", class: "text-[#cccccc] mr-2" }), _jsxs("span", { class: "text-[#cccccc] text-sm", children: ["localhost:", previewPort, "/"] }), _jsx("input", { id: "live-preview-url-input", type: "text", placeholder: "path", defaultValue: "", class: "flex-1 bg-transparent text-sm focus:outline-none text-[#cccccc] ml-1" }), _jsx("button", { type: "button", id: "live-preview-load-btn", class: "ml-2 p-1 hover:bg-[#3c3c3c] rounded transition-colors flex-shrink-0 text-[#cccccc] hover:text-white", title: "Go", children: _jsx("iconify-icon", { icon: "tabler:chevron-right", width: "16", height: "16" }) })] })] }) }),
|
|
5
|
+
return (_jsxs("sapling-island", { loading: "visible", children: [_jsxs("template", { children: [_jsx("script", { type: "module", src: "https://code.iconify.design/iconify-icon/2.0.0/iconify-icon.min.js" }), _jsx("script", { type: "module", src: "/components/Sidebar.js" })] }), _jsx("div", { id: `${id}-backdrop`, class: "fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-40 transition-opacity duration-300 opacity-0 pointer-events-none md:hidden" }), _jsxs("div", { id: `${id}-pane`, class: "fixed left-0 top-0 h-full w-full z-50 transform -translate-x-full transition-transform duration-300 ease-in-out md:relative md:translate-x-0 md:w-2/5 md:z-auto border-r border-[#3c3c3c] flex flex-col bg-[#252526]", children: [_jsx("div", { class: "p-3 border-b border-[#3c3c3c] bg-[#2d2d30] flex-shrink-0", children: _jsxs("div", { class: "flex items-center gap-2", children: [_jsx("button", { type: "button", id: `${id}-close-btn`, class: "p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white md:hidden", title: "Close Sidebar", children: _jsx("iconify-icon", { icon: "tabler:x", width: "16", height: "16" }) }), _jsx("a", { href: "/", class: "p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white", title: "Back to Home", children: _jsx("iconify-icon", { icon: "tabler:arrow-left", width: "16", height: "16" }) }), _jsx("button", { type: "button", id: "live-preview-refresh-btn", class: "p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white", title: "Reload", children: _jsx("iconify-icon", { icon: "tabler:refresh", width: "16", height: "16" }) }), _jsxs("div", { class: "flex-1 flex items-center bg-[#1e1e1e] border border-[#3c3c3c] rounded px-3 py-2 hover:border-[#0e639c] focus-within:border-[#0e639c] transition-all", children: [_jsx("iconify-icon", { icon: "tabler:world", width: "16", height: "16", class: "text-[#cccccc] mr-2" }), _jsxs("span", { class: "text-[#cccccc] text-sm", children: ["localhost:", previewPort, "/"] }), _jsx("input", { id: "live-preview-url-input", type: "text", placeholder: "path", defaultValue: "", class: "flex-1 bg-transparent text-sm focus:outline-none text-[#cccccc] ml-1" }), _jsx("button", { type: "button", id: "live-preview-load-btn", class: "ml-2 p-1 hover:bg-[#3c3c3c] rounded transition-colors flex-shrink-0 text-[#cccccc] hover:text-white", title: "Go", children: _jsx("iconify-icon", { icon: "tabler:chevron-right", width: "16", height: "16" }) })] })] }) }), _jsxs("div", { class: "flex-1 overflow-hidden bg-[#1e1e1e] flex flex-col", children: [_jsx("div", { class: "flex-1 overflow-hidden", children: _jsx(TerminalComponent, { index: 1 }) }), _jsx("div", { class: "flex-shrink-0", children: _jsx(ChatInput, { id: "chat-input", terminalId: "terminal-1" }) })] })] })] }));
|
|
5
6
|
}
|
|
6
7
|
//# sourceMappingURL=Sidebar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.js","sourceRoot":"","sources":["../../src/components/Sidebar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"Sidebar.js","sourceRoot":"","sources":["../../src/components/Sidebar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAQ3C,MAAM,UAAU,OAAO,CAAC,EAAE,EAAE,GAAG,SAAS,EAAE,WAAW,GAAG,IAAI,EAAE,gBAAgB,EAAgB;IAC5F,OAAO,CACL,0BAAgB,OAAO,EAAC,SAAS,aAC/B,+BACE,iBAAQ,IAAI,EAAC,QAAQ,EAAC,GAAG,EAAC,oEAAoE,GAAU,EACxG,iBAAQ,IAAI,EAAC,QAAQ,EAAC,GAAG,EAAC,wBAAwB,GAAU,IACnD,EAGX,cACE,EAAE,EAAE,GAAG,EAAE,WAAW,EACpB,KAAK,EAAC,oIAAoI,GACrI,EAGP,eACE,EAAE,EAAE,GAAG,EAAE,OAAO,EAChB,KAAK,EAAC,sNAAsN,aAG5N,cAAK,KAAK,EAAC,0DAA0D,YACnE,eAAK,KAAK,EAAC,yBAAyB,aAElC,iBACE,IAAI,EAAC,QAAQ,EACb,EAAE,EAAE,GAAG,EAAE,YAAY,EACrB,KAAK,EAAC,6GAA6G,EACnH,KAAK,EAAC,eAAe,YAErB,uBAAc,IAAI,EAAC,UAAU,EAAC,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,GAAgB,GAC7D,EAGT,YACE,IAAI,EAAC,GAAG,EACR,KAAK,EAAC,mGAAmG,EACzG,KAAK,EAAC,cAAc,YAEpB,uBAAc,IAAI,EAAC,mBAAmB,EAAC,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,GAAgB,GAC3E,EAGJ,iBACE,IAAI,EAAC,QAAQ,EACb,EAAE,EAAC,0BAA0B,EAC7B,KAAK,EAAC,mGAAmG,EACzG,KAAK,EAAC,QAAQ,YAEd,uBAAc,IAAI,EAAC,gBAAgB,EAAC,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,GAAgB,GACnE,EAGT,eAAK,KAAK,EAAC,qJAAqJ,aAC9J,uBAAc,IAAI,EAAC,cAAc,EAAC,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAC,KAAK,EAAC,qBAAqB,GAAgB,EACpG,gBAAM,KAAK,EAAC,wBAAwB,2BAAY,WAAW,SAAS,EACpE,gBACE,EAAE,EAAC,wBAAwB,EAC3B,IAAI,EAAC,MAAM,EACX,WAAW,EAAC,MAAM,EAClB,YAAY,EAAC,EAAE,EACf,KAAK,EAAC,sEAAsE,GAC5E,EACF,iBACE,IAAI,EAAC,QAAQ,EACb,EAAE,EAAC,uBAAuB,EAC1B,KAAK,EAAC,qGAAqG,EAC3G,KAAK,EAAC,IAAI,YAEV,uBAAc,IAAI,EAAC,sBAAsB,EAAC,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,GAAgB,GACzE,IACL,IACF,GACF,EAGN,eAAK,KAAK,EAAC,mDAAmD,aAE5D,cAAK,KAAK,EAAC,wBAAwB,YACjC,KAAC,iBAAiB,IAAC,KAAK,EAAE,CAAC,GAAI,GAC3B,EAGN,cAAK,KAAK,EAAC,eAAe,YACxB,KAAC,SAAS,IAAC,EAAE,EAAC,YAAY,EAAC,UAAU,EAAC,YAAY,GAAG,GACjD,IACF,IACF,IACS,CAClB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// ChatInput component JavaScript for chat-style terminal input
|
|
2
|
+
class ChatInputManager {
|
|
3
|
+
constructor(chatInputId) {
|
|
4
|
+
this.chatInputId = chatInputId;
|
|
5
|
+
this.container = document.getElementById(chatInputId);
|
|
6
|
+
this.textarea = document.getElementById(`${chatInputId}-textarea`);
|
|
7
|
+
this.sendBtn = document.getElementById(`${chatInputId}-send-btn`);
|
|
8
|
+
this.executeBtn = document.getElementById(`${chatInputId}-execute-btn`);
|
|
9
|
+
|
|
10
|
+
// Get chat input data from window
|
|
11
|
+
const chatInputData = window[`chatInputData_${chatInputId.replace(/-/g, '_')}`];
|
|
12
|
+
if (!chatInputData) {
|
|
13
|
+
console.error(`No chat input data found for ${chatInputId}`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.terminalId = chatInputData.terminalId;
|
|
18
|
+
|
|
19
|
+
console.log(`ChatInput ${chatInputId} initialized for terminal:`, this.terminalId);
|
|
20
|
+
|
|
21
|
+
this.init();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
init() {
|
|
25
|
+
if (!this.container || !this.textarea || !this.sendBtn || !this.executeBtn) {
|
|
26
|
+
console.error('ChatInput elements not found!', {
|
|
27
|
+
container: !!this.container,
|
|
28
|
+
textarea: !!this.textarea,
|
|
29
|
+
sendBtn: !!this.sendBtn,
|
|
30
|
+
executeBtn: !!this.executeBtn
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.setupEventListeners();
|
|
36
|
+
this.setupAutoResize();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setupEventListeners() {
|
|
40
|
+
// Handle send to input button click
|
|
41
|
+
this.sendBtn.addEventListener('click', () => {
|
|
42
|
+
this.sendToInput();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handle execute button click
|
|
46
|
+
this.executeBtn.addEventListener('click', () => {
|
|
47
|
+
this.executeCommand();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Handle textarea key events
|
|
51
|
+
this.textarea.addEventListener('keydown', (e) => {
|
|
52
|
+
// Enter without shift sends to input field
|
|
53
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
this.sendToInput();
|
|
56
|
+
}
|
|
57
|
+
// Ctrl+Enter or Cmd+Enter executes
|
|
58
|
+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
this.executeCommand();
|
|
61
|
+
}
|
|
62
|
+
// Allow Shift+Enter for new lines (default behavior)
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Handle input to auto-resize textarea
|
|
66
|
+
this.textarea.addEventListener('input', () => {
|
|
67
|
+
this.adjustTextareaHeight();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Disable buttons when textarea is empty
|
|
71
|
+
this.textarea.addEventListener('input', () => {
|
|
72
|
+
this.updateButtonState();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Focus textarea when container is clicked
|
|
76
|
+
this.container.addEventListener('click', (e) => {
|
|
77
|
+
if (e.target === this.container) {
|
|
78
|
+
this.textarea.focus();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setupAutoResize() {
|
|
84
|
+
// Initial state
|
|
85
|
+
this.adjustTextareaHeight();
|
|
86
|
+
this.updateButtonState();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
adjustTextareaHeight() {
|
|
90
|
+
if (!this.textarea) return;
|
|
91
|
+
|
|
92
|
+
// Reset height to auto to get the correct scrollHeight
|
|
93
|
+
this.textarea.style.height = 'auto';
|
|
94
|
+
|
|
95
|
+
// Calculate the new height based on content
|
|
96
|
+
const scrollHeight = this.textarea.scrollHeight;
|
|
97
|
+
const maxHeight = 120; // Max height from CSS
|
|
98
|
+
const minHeight = 40; // Min height from CSS
|
|
99
|
+
|
|
100
|
+
const newHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight);
|
|
101
|
+
this.textarea.style.height = `${newHeight}px`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
updateButtonState() {
|
|
105
|
+
if (!this.textarea || !this.sendBtn || !this.executeBtn) return;
|
|
106
|
+
|
|
107
|
+
const hasContent = this.textarea.value.trim().length > 0;
|
|
108
|
+
this.sendBtn.disabled = !hasContent;
|
|
109
|
+
this.executeBtn.disabled = false; // Execute button is always enabled
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
sendToInput() {
|
|
113
|
+
if (!this.textarea) return;
|
|
114
|
+
|
|
115
|
+
const input = this.textarea.value.trim();
|
|
116
|
+
if (!input) return;
|
|
117
|
+
|
|
118
|
+
// Get the terminal manager for the associated terminal
|
|
119
|
+
const terminalManager = this.getTerminalManager();
|
|
120
|
+
if (!terminalManager) {
|
|
121
|
+
console.error(`No terminal manager found for terminal: ${this.terminalId}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Send the input to the terminal input field (without executing)
|
|
126
|
+
console.log(`Sending text to terminal input field ${this.terminalId}:`, input);
|
|
127
|
+
console.log('ChatInput sending (programmatic):', JSON.stringify(input), 'char codes:', input.split('').map(c => c.charCodeAt(0)));
|
|
128
|
+
|
|
129
|
+
// Simplest approach: just send the text directly
|
|
130
|
+
// This should work for most cases in Claude Code
|
|
131
|
+
terminalManager.sendInput(input);
|
|
132
|
+
|
|
133
|
+
// Clear the textarea
|
|
134
|
+
this.textarea.value = '';
|
|
135
|
+
this.adjustTextareaHeight();
|
|
136
|
+
this.updateButtonState();
|
|
137
|
+
|
|
138
|
+
// Focus back to textarea for next input
|
|
139
|
+
this.textarea.focus();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
executeCommand() {
|
|
143
|
+
// Get the terminal manager for the associated terminal
|
|
144
|
+
const terminalManager = this.getTerminalManager();
|
|
145
|
+
if (!terminalManager) {
|
|
146
|
+
console.error(`No terminal manager found for terminal: ${this.terminalId}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Execute whatever is currently in the terminal input field
|
|
151
|
+
console.log(`Executing command in terminal ${this.terminalId}`);
|
|
152
|
+
console.log('ChatInput executing (programmatic):', JSON.stringify('\r'), 'char code:', '\r'.charCodeAt(0));
|
|
153
|
+
terminalManager.sendInput('\r');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getTerminalManager() {
|
|
157
|
+
// Access global terminal managers
|
|
158
|
+
if (window.terminalManagers && window.terminalManagers.has(this.terminalId)) {
|
|
159
|
+
return window.terminalManagers.get(this.terminalId);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Fallback: try to find by checking all managers
|
|
163
|
+
if (window.terminalManagers) {
|
|
164
|
+
for (const [terminalId, manager] of window.terminalManagers.entries()) {
|
|
165
|
+
if (terminalId === this.terminalId) {
|
|
166
|
+
return manager;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If not found, log available terminals for debugging
|
|
172
|
+
if (window.terminalManagers) {
|
|
173
|
+
console.log('Available terminal managers:', Array.from(window.terminalManagers.keys()));
|
|
174
|
+
console.log('Looking for terminal ID:', this.terminalId);
|
|
175
|
+
} else {
|
|
176
|
+
console.log('No terminal managers found in window object');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
focus() {
|
|
183
|
+
if (this.textarea) {
|
|
184
|
+
this.textarea.focus();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
destroy() {
|
|
189
|
+
// Clean up event listeners if needed
|
|
190
|
+
// The component will be removed from DOM, so most cleanup is automatic
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Auto-initialize when script loads
|
|
195
|
+
console.log('ChatInput.js loaded, looking for chat input containers...');
|
|
196
|
+
let chatInputManagers = new Map();
|
|
197
|
+
|
|
198
|
+
function initializeChatInputs() {
|
|
199
|
+
// Look for all sapling-islands with chat input content
|
|
200
|
+
const saplingIslands = document.querySelectorAll('sapling-island');
|
|
201
|
+
|
|
202
|
+
for (const island of saplingIslands) {
|
|
203
|
+
// Look for chat input container
|
|
204
|
+
const chatInputDiv = island.querySelector('div[id*="chat-input"]');
|
|
205
|
+
if (chatInputDiv && chatInputDiv.id) {
|
|
206
|
+
const chatInputId = chatInputDiv.id;
|
|
207
|
+
console.log('Found chat input component with ID:', chatInputId);
|
|
208
|
+
|
|
209
|
+
// Check if we already have a manager for this chat input
|
|
210
|
+
if (!chatInputManagers.has(chatInputId)) {
|
|
211
|
+
const manager = new ChatInputManager(chatInputId);
|
|
212
|
+
chatInputManagers.set(chatInputId, manager);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(`Initialized ${chatInputManagers.size} chat input(s)`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Try immediate initialization
|
|
221
|
+
if (document.readyState === 'loading') {
|
|
222
|
+
document.addEventListener('DOMContentLoaded', initializeChatInputs);
|
|
223
|
+
} else {
|
|
224
|
+
initializeChatInputs();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Make available globally
|
|
228
|
+
window.chatInputManagers = chatInputManagers;
|
|
229
|
+
window.ChatInputManager = ChatInputManager;
|
|
230
|
+
window.initializeChatInputs = initializeChatInputs;
|
|
231
|
+
|
|
232
|
+
// Cleanup on page unload
|
|
233
|
+
window.addEventListener('beforeunload', () => {
|
|
234
|
+
for (const manager of chatInputManagers.values()) {
|
|
235
|
+
manager.destroy();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
@@ -99,6 +99,8 @@ class TerminalManager {
|
|
|
99
99
|
|
|
100
100
|
// Handle terminal input - pass through to shell
|
|
101
101
|
this.terminal.onData((data) => {
|
|
102
|
+
// Log the input data for debugging
|
|
103
|
+
console.log('Terminal input (manual typing):', JSON.stringify(data), 'char codes:', data.split('').map(c => c.charCodeAt(0)));
|
|
102
104
|
// Send all input directly to the shell session
|
|
103
105
|
this.sendInput(data);
|
|
104
106
|
});
|
|
@@ -1221,6 +1221,14 @@ video {
|
|
|
1221
1221
|
height: 100vh;
|
|
1222
1222
|
}
|
|
1223
1223
|
|
|
1224
|
+
.max-h-\[120px\] {
|
|
1225
|
+
max-height: 120px;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
.min-h-\[40px\] {
|
|
1229
|
+
min-height: 40px;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1224
1232
|
.min-h-screen {
|
|
1225
1233
|
min-height: 100vh;
|
|
1226
1234
|
}
|
|
@@ -1281,6 +1289,10 @@ video {
|
|
|
1281
1289
|
cursor: not-allowed;
|
|
1282
1290
|
}
|
|
1283
1291
|
|
|
1292
|
+
.resize-none {
|
|
1293
|
+
resize: none;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1284
1296
|
.resize {
|
|
1285
1297
|
resize: both;
|
|
1286
1298
|
}
|
|
@@ -1289,6 +1301,10 @@ video {
|
|
|
1289
1301
|
flex-direction: column;
|
|
1290
1302
|
}
|
|
1291
1303
|
|
|
1304
|
+
.items-end {
|
|
1305
|
+
align-items: flex-end;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1292
1308
|
.items-center {
|
|
1293
1309
|
align-items: center;
|
|
1294
1310
|
}
|
|
@@ -1346,6 +1362,10 @@ video {
|
|
|
1346
1362
|
border-right-width: 1px;
|
|
1347
1363
|
}
|
|
1348
1364
|
|
|
1365
|
+
.border-t {
|
|
1366
|
+
border-top-width: 1px;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1349
1369
|
.border-t-2 {
|
|
1350
1370
|
border-top-width: 2px;
|
|
1351
1371
|
}
|
|
@@ -1370,6 +1390,11 @@ video {
|
|
|
1370
1390
|
border-color: rgb(156 163 175 / var(--tw-border-opacity, 1));
|
|
1371
1391
|
}
|
|
1372
1392
|
|
|
1393
|
+
.bg-\[\#0e639c\] {
|
|
1394
|
+
--tw-bg-opacity: 1;
|
|
1395
|
+
background-color: rgb(14 99 156 / var(--tw-bg-opacity, 1));
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1373
1398
|
.bg-\[\#1e1e1e\] {
|
|
1374
1399
|
--tw-bg-opacity: 1;
|
|
1375
1400
|
background-color: rgb(30 30 30 / var(--tw-bg-opacity, 1));
|
|
@@ -1380,6 +1405,11 @@ video {
|
|
|
1380
1405
|
background-color: rgb(37 37 38 / var(--tw-bg-opacity, 1));
|
|
1381
1406
|
}
|
|
1382
1407
|
|
|
1408
|
+
.bg-\[\#28a745\] {
|
|
1409
|
+
--tw-bg-opacity: 1;
|
|
1410
|
+
background-color: rgb(40 167 69 / var(--tw-bg-opacity, 1));
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1383
1413
|
.bg-\[\#2a2a2a\] {
|
|
1384
1414
|
--tw-bg-opacity: 1;
|
|
1385
1415
|
background-color: rgb(42 42 42 / var(--tw-bg-opacity, 1));
|
|
@@ -1470,6 +1500,10 @@ video {
|
|
|
1470
1500
|
text-align: center;
|
|
1471
1501
|
}
|
|
1472
1502
|
|
|
1503
|
+
.font-mono {
|
|
1504
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1473
1507
|
.font-sans {
|
|
1474
1508
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
1475
1509
|
}
|
|
@@ -1545,6 +1579,16 @@ video {
|
|
|
1545
1579
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
|
1546
1580
|
}
|
|
1547
1581
|
|
|
1582
|
+
.placeholder-\[\#888\]::-moz-placeholder {
|
|
1583
|
+
--tw-placeholder-opacity: 1;
|
|
1584
|
+
color: rgb(136 136 136 / var(--tw-placeholder-opacity, 1));
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.placeholder-\[\#888\]::placeholder {
|
|
1588
|
+
--tw-placeholder-opacity: 1;
|
|
1589
|
+
color: rgb(136 136 136 / var(--tw-placeholder-opacity, 1));
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1548
1592
|
.opacity-0 {
|
|
1549
1593
|
opacity: 0;
|
|
1550
1594
|
}
|
|
@@ -1615,6 +1659,16 @@ video {
|
|
|
1615
1659
|
border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
|
|
1616
1660
|
}
|
|
1617
1661
|
|
|
1662
|
+
.hover\:bg-\[\#1177bb\]:hover {
|
|
1663
|
+
--tw-bg-opacity: 1;
|
|
1664
|
+
background-color: rgb(17 119 187 / var(--tw-bg-opacity, 1));
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
.hover\:bg-\[\#218838\]:hover {
|
|
1668
|
+
--tw-bg-opacity: 1;
|
|
1669
|
+
background-color: rgb(33 136 56 / var(--tw-bg-opacity, 1));
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1618
1672
|
.hover\:bg-\[\#2d2d30\]:hover {
|
|
1619
1673
|
--tw-bg-opacity: 1;
|
|
1620
1674
|
background-color: rgb(45 45 48 / var(--tw-bg-opacity, 1));
|
|
@@ -1651,11 +1705,26 @@ video {
|
|
|
1651
1705
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
1652
1706
|
}
|
|
1653
1707
|
|
|
1708
|
+
.focus\:border-\[\#0e639c\]:focus {
|
|
1709
|
+
--tw-border-opacity: 1;
|
|
1710
|
+
border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1654
1713
|
.focus\:outline-none:focus {
|
|
1655
1714
|
outline: 2px solid transparent;
|
|
1656
1715
|
outline-offset: 2px;
|
|
1657
1716
|
}
|
|
1658
1717
|
|
|
1718
|
+
.disabled\:bg-\[\#3c3c3c\]:disabled {
|
|
1719
|
+
--tw-bg-opacity: 1;
|
|
1720
|
+
background-color: rgb(60 60 60 / var(--tw-bg-opacity, 1));
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.disabled\:text-\[\#888\]:disabled {
|
|
1724
|
+
--tw-text-opacity: 1;
|
|
1725
|
+
color: rgb(136 136 136 / var(--tw-text-opacity, 1));
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1659
1728
|
.group:hover .group-hover\:text-white {
|
|
1660
1729
|
--tw-text-opacity: 1;
|
|
1661
1730
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
interface ChatInputProps {
|
|
2
|
+
id?: string;
|
|
3
|
+
terminalId?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function ChatInput({ id = "chat-input", terminalId = "terminal-1" }: ChatInputProps) {
|
|
7
|
+
return (
|
|
8
|
+
<sapling-island loading="visible">
|
|
9
|
+
<template>
|
|
10
|
+
<script type="module" src="/components/ChatInput.js"></script>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<div id={id} class="border-t border-[#3c3c3c] bg-[#2d2d30] p-3">
|
|
14
|
+
<div class="flex items-end gap-2">
|
|
15
|
+
{/* Textarea for multi-line input */}
|
|
16
|
+
<div class="flex-1 relative">
|
|
17
|
+
<textarea
|
|
18
|
+
id={`${id}-textarea`}
|
|
19
|
+
placeholder="Type your command or message..."
|
|
20
|
+
class="w-full min-h-[40px] max-h-[120px] px-3 py-2 bg-[#1e1e1e] border border-[#3c3c3c] rounded-lg text-[#cccccc] placeholder-[#888] resize-none focus:outline-none focus:border-[#0e639c] transition-colors text-sm font-mono"
|
|
21
|
+
rows={1}
|
|
22
|
+
></textarea>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
{/* Send to Input button */}
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
id={`${id}-send-btn`}
|
|
29
|
+
class="px-3 py-2 bg-[#0e639c] hover:bg-[#1177bb] disabled:bg-[#3c3c3c] disabled:text-[#888] text-white rounded-lg transition-colors flex-shrink-0 flex items-center justify-center min-h-[40px]"
|
|
30
|
+
title="Send to Input Field"
|
|
31
|
+
>
|
|
32
|
+
<iconify-icon icon="tabler:arrow-up" width="16" height="16"></iconify-icon>
|
|
33
|
+
</button>
|
|
34
|
+
|
|
35
|
+
{/* Execute button */}
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
id={`${id}-execute-btn`}
|
|
39
|
+
class="px-3 py-2 bg-[#28a745] hover:bg-[#218838] disabled:bg-[#3c3c3c] disabled:text-[#888] text-white rounded-lg transition-colors flex-shrink-0 flex items-center justify-center min-h-[40px]"
|
|
40
|
+
title="Execute Command"
|
|
41
|
+
>
|
|
42
|
+
<iconify-icon icon="tabler:player-play" width="16" height="16"></iconify-icon>
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<script dangerouslySetInnerHTML={{__html: `
|
|
48
|
+
// Pass chat input data to JavaScript
|
|
49
|
+
window.chatInputData_${id.replace(/-/g, '_')} = {
|
|
50
|
+
chatInputId: '${id}',
|
|
51
|
+
terminalId: '${terminalId}'
|
|
52
|
+
};
|
|
53
|
+
`}}></script>
|
|
54
|
+
</sapling-island>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Terminal as TerminalComponent } from "./Terminal.js";
|
|
2
|
+
import { ChatInput } from "./ChatInput.js";
|
|
2
3
|
|
|
3
4
|
interface SidebarProps {
|
|
4
5
|
id?: string;
|
|
@@ -81,10 +82,16 @@ export function Sidebar({ id = "sidebar", previewPort = 1234, workingDirectory }
|
|
|
81
82
|
</div>
|
|
82
83
|
|
|
83
84
|
{/* Terminal Content */}
|
|
84
|
-
<div class="flex-1 overflow-hidden bg-[#1e1e1e]">
|
|
85
|
-
|
|
85
|
+
<div class="flex-1 overflow-hidden bg-[#1e1e1e] flex flex-col">
|
|
86
|
+
{/* Terminal Display */}
|
|
87
|
+
<div class="flex-1 overflow-hidden">
|
|
86
88
|
<TerminalComponent index={1} />
|
|
87
89
|
</div>
|
|
90
|
+
|
|
91
|
+
{/* Chat Input */}
|
|
92
|
+
<div class="flex-shrink-0">
|
|
93
|
+
<ChatInput id="chat-input" terminalId="terminal-1" />
|
|
94
|
+
</div>
|
|
88
95
|
</div>
|
|
89
96
|
</div>
|
|
90
97
|
</sapling-island>
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// ChatInput component JavaScript for chat-style terminal input
|
|
2
|
+
class ChatInputManager {
|
|
3
|
+
constructor(chatInputId) {
|
|
4
|
+
this.chatInputId = chatInputId;
|
|
5
|
+
this.container = document.getElementById(chatInputId);
|
|
6
|
+
this.textarea = document.getElementById(`${chatInputId}-textarea`);
|
|
7
|
+
this.sendBtn = document.getElementById(`${chatInputId}-send-btn`);
|
|
8
|
+
this.executeBtn = document.getElementById(`${chatInputId}-execute-btn`);
|
|
9
|
+
|
|
10
|
+
// Get chat input data from window
|
|
11
|
+
const chatInputData = window[`chatInputData_${chatInputId.replace(/-/g, '_')}`];
|
|
12
|
+
if (!chatInputData) {
|
|
13
|
+
console.error(`No chat input data found for ${chatInputId}`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.terminalId = chatInputData.terminalId;
|
|
18
|
+
|
|
19
|
+
console.log(`ChatInput ${chatInputId} initialized for terminal:`, this.terminalId);
|
|
20
|
+
|
|
21
|
+
this.init();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
init() {
|
|
25
|
+
if (!this.container || !this.textarea || !this.sendBtn || !this.executeBtn) {
|
|
26
|
+
console.error('ChatInput elements not found!', {
|
|
27
|
+
container: !!this.container,
|
|
28
|
+
textarea: !!this.textarea,
|
|
29
|
+
sendBtn: !!this.sendBtn,
|
|
30
|
+
executeBtn: !!this.executeBtn
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.setupEventListeners();
|
|
36
|
+
this.setupAutoResize();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setupEventListeners() {
|
|
40
|
+
// Handle send to input button click
|
|
41
|
+
this.sendBtn.addEventListener('click', () => {
|
|
42
|
+
this.sendToInput();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handle execute button click
|
|
46
|
+
this.executeBtn.addEventListener('click', () => {
|
|
47
|
+
this.executeCommand();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Handle textarea key events
|
|
51
|
+
this.textarea.addEventListener('keydown', (e) => {
|
|
52
|
+
// Enter without shift sends to input field
|
|
53
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
this.sendToInput();
|
|
56
|
+
}
|
|
57
|
+
// Ctrl+Enter or Cmd+Enter executes
|
|
58
|
+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
this.executeCommand();
|
|
61
|
+
}
|
|
62
|
+
// Allow Shift+Enter for new lines (default behavior)
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Handle input to auto-resize textarea
|
|
66
|
+
this.textarea.addEventListener('input', () => {
|
|
67
|
+
this.adjustTextareaHeight();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Disable buttons when textarea is empty
|
|
71
|
+
this.textarea.addEventListener('input', () => {
|
|
72
|
+
this.updateButtonState();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Focus textarea when container is clicked
|
|
76
|
+
this.container.addEventListener('click', (e) => {
|
|
77
|
+
if (e.target === this.container) {
|
|
78
|
+
this.textarea.focus();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setupAutoResize() {
|
|
84
|
+
// Initial state
|
|
85
|
+
this.adjustTextareaHeight();
|
|
86
|
+
this.updateButtonState();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
adjustTextareaHeight() {
|
|
90
|
+
if (!this.textarea) return;
|
|
91
|
+
|
|
92
|
+
// Reset height to auto to get the correct scrollHeight
|
|
93
|
+
this.textarea.style.height = 'auto';
|
|
94
|
+
|
|
95
|
+
// Calculate the new height based on content
|
|
96
|
+
const scrollHeight = this.textarea.scrollHeight;
|
|
97
|
+
const maxHeight = 120; // Max height from CSS
|
|
98
|
+
const minHeight = 40; // Min height from CSS
|
|
99
|
+
|
|
100
|
+
const newHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight);
|
|
101
|
+
this.textarea.style.height = `${newHeight}px`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
updateButtonState() {
|
|
105
|
+
if (!this.textarea || !this.sendBtn || !this.executeBtn) return;
|
|
106
|
+
|
|
107
|
+
const hasContent = this.textarea.value.trim().length > 0;
|
|
108
|
+
this.sendBtn.disabled = !hasContent;
|
|
109
|
+
this.executeBtn.disabled = false; // Execute button is always enabled
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
sendToInput() {
|
|
113
|
+
if (!this.textarea) return;
|
|
114
|
+
|
|
115
|
+
const input = this.textarea.value.trim();
|
|
116
|
+
if (!input) return;
|
|
117
|
+
|
|
118
|
+
// Get the terminal manager for the associated terminal
|
|
119
|
+
const terminalManager = this.getTerminalManager();
|
|
120
|
+
if (!terminalManager) {
|
|
121
|
+
console.error(`No terminal manager found for terminal: ${this.terminalId}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Send the input to the terminal input field (without executing)
|
|
126
|
+
console.log(`Sending text to terminal input field ${this.terminalId}:`, input);
|
|
127
|
+
console.log('ChatInput sending (programmatic):', JSON.stringify(input), 'char codes:', input.split('').map(c => c.charCodeAt(0)));
|
|
128
|
+
|
|
129
|
+
// Simplest approach: just send the text directly
|
|
130
|
+
// This should work for most cases in Claude Code
|
|
131
|
+
terminalManager.sendInput(input);
|
|
132
|
+
|
|
133
|
+
// Clear the textarea
|
|
134
|
+
this.textarea.value = '';
|
|
135
|
+
this.adjustTextareaHeight();
|
|
136
|
+
this.updateButtonState();
|
|
137
|
+
|
|
138
|
+
// Focus back to textarea for next input
|
|
139
|
+
this.textarea.focus();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
executeCommand() {
|
|
143
|
+
// Get the terminal manager for the associated terminal
|
|
144
|
+
const terminalManager = this.getTerminalManager();
|
|
145
|
+
if (!terminalManager) {
|
|
146
|
+
console.error(`No terminal manager found for terminal: ${this.terminalId}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Execute whatever is currently in the terminal input field
|
|
151
|
+
console.log(`Executing command in terminal ${this.terminalId}`);
|
|
152
|
+
console.log('ChatInput executing (programmatic):', JSON.stringify('\r'), 'char code:', '\r'.charCodeAt(0));
|
|
153
|
+
terminalManager.sendInput('\r');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getTerminalManager() {
|
|
157
|
+
// Access global terminal managers
|
|
158
|
+
if (window.terminalManagers && window.terminalManagers.has(this.terminalId)) {
|
|
159
|
+
return window.terminalManagers.get(this.terminalId);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Fallback: try to find by checking all managers
|
|
163
|
+
if (window.terminalManagers) {
|
|
164
|
+
for (const [terminalId, manager] of window.terminalManagers.entries()) {
|
|
165
|
+
if (terminalId === this.terminalId) {
|
|
166
|
+
return manager;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If not found, log available terminals for debugging
|
|
172
|
+
if (window.terminalManagers) {
|
|
173
|
+
console.log('Available terminal managers:', Array.from(window.terminalManagers.keys()));
|
|
174
|
+
console.log('Looking for terminal ID:', this.terminalId);
|
|
175
|
+
} else {
|
|
176
|
+
console.log('No terminal managers found in window object');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
focus() {
|
|
183
|
+
if (this.textarea) {
|
|
184
|
+
this.textarea.focus();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
destroy() {
|
|
189
|
+
// Clean up event listeners if needed
|
|
190
|
+
// The component will be removed from DOM, so most cleanup is automatic
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Auto-initialize when script loads
|
|
195
|
+
console.log('ChatInput.js loaded, looking for chat input containers...');
|
|
196
|
+
let chatInputManagers = new Map();
|
|
197
|
+
|
|
198
|
+
function initializeChatInputs() {
|
|
199
|
+
// Look for all sapling-islands with chat input content
|
|
200
|
+
const saplingIslands = document.querySelectorAll('sapling-island');
|
|
201
|
+
|
|
202
|
+
for (const island of saplingIslands) {
|
|
203
|
+
// Look for chat input container
|
|
204
|
+
const chatInputDiv = island.querySelector('div[id*="chat-input"]');
|
|
205
|
+
if (chatInputDiv && chatInputDiv.id) {
|
|
206
|
+
const chatInputId = chatInputDiv.id;
|
|
207
|
+
console.log('Found chat input component with ID:', chatInputId);
|
|
208
|
+
|
|
209
|
+
// Check if we already have a manager for this chat input
|
|
210
|
+
if (!chatInputManagers.has(chatInputId)) {
|
|
211
|
+
const manager = new ChatInputManager(chatInputId);
|
|
212
|
+
chatInputManagers.set(chatInputId, manager);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(`Initialized ${chatInputManagers.size} chat input(s)`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Try immediate initialization
|
|
221
|
+
if (document.readyState === 'loading') {
|
|
222
|
+
document.addEventListener('DOMContentLoaded', initializeChatInputs);
|
|
223
|
+
} else {
|
|
224
|
+
initializeChatInputs();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Make available globally
|
|
228
|
+
window.chatInputManagers = chatInputManagers;
|
|
229
|
+
window.ChatInputManager = ChatInputManager;
|
|
230
|
+
window.initializeChatInputs = initializeChatInputs;
|
|
231
|
+
|
|
232
|
+
// Cleanup on page unload
|
|
233
|
+
window.addEventListener('beforeunload', () => {
|
|
234
|
+
for (const manager of chatInputManagers.values()) {
|
|
235
|
+
manager.destroy();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
@@ -99,6 +99,8 @@ class TerminalManager {
|
|
|
99
99
|
|
|
100
100
|
// Handle terminal input - pass through to shell
|
|
101
101
|
this.terminal.onData((data) => {
|
|
102
|
+
// Log the input data for debugging
|
|
103
|
+
console.log('Terminal input (manual typing):', JSON.stringify(data), 'char codes:', data.split('').map(c => c.charCodeAt(0)));
|
|
102
104
|
// Send all input directly to the shell session
|
|
103
105
|
this.sendInput(data);
|
|
104
106
|
});
|
|
@@ -1221,6 +1221,14 @@ video {
|
|
|
1221
1221
|
height: 100vh;
|
|
1222
1222
|
}
|
|
1223
1223
|
|
|
1224
|
+
.max-h-\[120px\] {
|
|
1225
|
+
max-height: 120px;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
.min-h-\[40px\] {
|
|
1229
|
+
min-height: 40px;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1224
1232
|
.min-h-screen {
|
|
1225
1233
|
min-height: 100vh;
|
|
1226
1234
|
}
|
|
@@ -1281,6 +1289,10 @@ video {
|
|
|
1281
1289
|
cursor: not-allowed;
|
|
1282
1290
|
}
|
|
1283
1291
|
|
|
1292
|
+
.resize-none {
|
|
1293
|
+
resize: none;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1284
1296
|
.resize {
|
|
1285
1297
|
resize: both;
|
|
1286
1298
|
}
|
|
@@ -1289,6 +1301,10 @@ video {
|
|
|
1289
1301
|
flex-direction: column;
|
|
1290
1302
|
}
|
|
1291
1303
|
|
|
1304
|
+
.items-end {
|
|
1305
|
+
align-items: flex-end;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1292
1308
|
.items-center {
|
|
1293
1309
|
align-items: center;
|
|
1294
1310
|
}
|
|
@@ -1346,6 +1362,10 @@ video {
|
|
|
1346
1362
|
border-right-width: 1px;
|
|
1347
1363
|
}
|
|
1348
1364
|
|
|
1365
|
+
.border-t {
|
|
1366
|
+
border-top-width: 1px;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1349
1369
|
.border-t-2 {
|
|
1350
1370
|
border-top-width: 2px;
|
|
1351
1371
|
}
|
|
@@ -1370,6 +1390,11 @@ video {
|
|
|
1370
1390
|
border-color: rgb(156 163 175 / var(--tw-border-opacity, 1));
|
|
1371
1391
|
}
|
|
1372
1392
|
|
|
1393
|
+
.bg-\[\#0e639c\] {
|
|
1394
|
+
--tw-bg-opacity: 1;
|
|
1395
|
+
background-color: rgb(14 99 156 / var(--tw-bg-opacity, 1));
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1373
1398
|
.bg-\[\#1e1e1e\] {
|
|
1374
1399
|
--tw-bg-opacity: 1;
|
|
1375
1400
|
background-color: rgb(30 30 30 / var(--tw-bg-opacity, 1));
|
|
@@ -1380,6 +1405,11 @@ video {
|
|
|
1380
1405
|
background-color: rgb(37 37 38 / var(--tw-bg-opacity, 1));
|
|
1381
1406
|
}
|
|
1382
1407
|
|
|
1408
|
+
.bg-\[\#28a745\] {
|
|
1409
|
+
--tw-bg-opacity: 1;
|
|
1410
|
+
background-color: rgb(40 167 69 / var(--tw-bg-opacity, 1));
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1383
1413
|
.bg-\[\#2a2a2a\] {
|
|
1384
1414
|
--tw-bg-opacity: 1;
|
|
1385
1415
|
background-color: rgb(42 42 42 / var(--tw-bg-opacity, 1));
|
|
@@ -1470,6 +1500,10 @@ video {
|
|
|
1470
1500
|
text-align: center;
|
|
1471
1501
|
}
|
|
1472
1502
|
|
|
1503
|
+
.font-mono {
|
|
1504
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1473
1507
|
.font-sans {
|
|
1474
1508
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
1475
1509
|
}
|
|
@@ -1545,6 +1579,16 @@ video {
|
|
|
1545
1579
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
|
1546
1580
|
}
|
|
1547
1581
|
|
|
1582
|
+
.placeholder-\[\#888\]::-moz-placeholder {
|
|
1583
|
+
--tw-placeholder-opacity: 1;
|
|
1584
|
+
color: rgb(136 136 136 / var(--tw-placeholder-opacity, 1));
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.placeholder-\[\#888\]::placeholder {
|
|
1588
|
+
--tw-placeholder-opacity: 1;
|
|
1589
|
+
color: rgb(136 136 136 / var(--tw-placeholder-opacity, 1));
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1548
1592
|
.opacity-0 {
|
|
1549
1593
|
opacity: 0;
|
|
1550
1594
|
}
|
|
@@ -1615,6 +1659,16 @@ video {
|
|
|
1615
1659
|
border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
|
|
1616
1660
|
}
|
|
1617
1661
|
|
|
1662
|
+
.hover\:bg-\[\#1177bb\]:hover {
|
|
1663
|
+
--tw-bg-opacity: 1;
|
|
1664
|
+
background-color: rgb(17 119 187 / var(--tw-bg-opacity, 1));
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
.hover\:bg-\[\#218838\]:hover {
|
|
1668
|
+
--tw-bg-opacity: 1;
|
|
1669
|
+
background-color: rgb(33 136 56 / var(--tw-bg-opacity, 1));
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1618
1672
|
.hover\:bg-\[\#2d2d30\]:hover {
|
|
1619
1673
|
--tw-bg-opacity: 1;
|
|
1620
1674
|
background-color: rgb(45 45 48 / var(--tw-bg-opacity, 1));
|
|
@@ -1651,11 +1705,26 @@ video {
|
|
|
1651
1705
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
1652
1706
|
}
|
|
1653
1707
|
|
|
1708
|
+
.focus\:border-\[\#0e639c\]:focus {
|
|
1709
|
+
--tw-border-opacity: 1;
|
|
1710
|
+
border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1654
1713
|
.focus\:outline-none:focus {
|
|
1655
1714
|
outline: 2px solid transparent;
|
|
1656
1715
|
outline-offset: 2px;
|
|
1657
1716
|
}
|
|
1658
1717
|
|
|
1718
|
+
.disabled\:bg-\[\#3c3c3c\]:disabled {
|
|
1719
|
+
--tw-bg-opacity: 1;
|
|
1720
|
+
background-color: rgb(60 60 60 / var(--tw-bg-opacity, 1));
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.disabled\:text-\[\#888\]:disabled {
|
|
1724
|
+
--tw-text-opacity: 1;
|
|
1725
|
+
color: rgb(136 136 136 / var(--tw-text-opacity, 1));
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1659
1728
|
.group:hover .group-hover\:text-white {
|
|
1660
1729
|
--tw-text-opacity: 1;
|
|
1661
1730
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|