lonny-agent 0.2.3 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +326 -326
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/llm.d.ts +4 -0
- package/dist/agent/llm.d.ts.map +1 -1
- package/dist/agent/project.d.ts +26 -0
- package/dist/agent/project.d.ts.map +1 -0
- package/dist/agent/project.js +303 -0
- package/dist/agent/project.js.map +1 -0
- package/dist/agent/prompt-builder.d.ts +1 -1
- package/dist/agent/prompt-builder.d.ts.map +1 -1
- package/dist/agent/prompt-builder.js +22 -13
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/anthropic.d.ts.map +1 -1
- package/dist/agent/providers/anthropic.js +20 -2
- package/dist/agent/providers/anthropic.js.map +1 -1
- package/dist/agent/providers/google.d.ts.map +1 -1
- package/dist/agent/providers/google.js +1 -1
- package/dist/agent/providers/google.js.map +1 -1
- package/dist/agent/providers/ollama.d.ts.map +1 -1
- package/dist/agent/providers/ollama.js +2 -1
- package/dist/agent/providers/ollama.js.map +1 -1
- package/dist/agent/providers/openai.d.ts.map +1 -1
- package/dist/agent/providers/openai.js +63 -3
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/session.d.ts +8 -2
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +250 -153
- package/dist/agent/session.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +7 -1
- package/dist/config/index.js.map +1 -1
- package/dist/tools/__tests__/bash.test.js +1 -1
- package/dist/tools/__tests__/bash.test.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +875 -371
- package/dist/tools/__tests__/edit.test.js.map +1 -1
- package/dist/tools/__tests__/grep.test.js +32 -0
- package/dist/tools/__tests__/grep.test.js.map +1 -1
- package/dist/tools/__tests__/sed.test.d.ts +2 -0
- package/dist/tools/__tests__/sed.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sed.test.js +228 -0
- package/dist/tools/__tests__/sed.test.js.map +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +83 -10
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +58 -0
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +366 -149
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +54 -30
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/read.d.ts +6 -0
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +42 -15
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/registry.d.ts +0 -6
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +2 -33
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/sed.d.ts +4 -0
- package/dist/tools/sed.d.ts.map +1 -0
- package/dist/tools/sed.js +121 -0
- package/dist/tools/sed.js.map +1 -0
- package/dist/tools/write_plan.d.ts +1 -1
- package/dist/tools/write_plan.d.ts.map +1 -1
- package/dist/tools/write_plan.js +14 -4
- package/dist/tools/write_plan.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +5 -4
- package/dist/tui/index.js.map +1 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +30 -7
- package/dist/web/index.js.map +1 -1
- package/dist/web/public/app.js +5 -855
- package/dist/web/public/confirm.js +70 -0
- package/dist/web/public/index.html +121 -120
- package/dist/web/public/input.js +107 -0
- package/dist/web/public/messages.js +395 -0
- package/dist/web/public/sidebar.js +82 -0
- package/dist/web/public/state.js +72 -0
- package/dist/web/public/style.css +996 -949
- package/dist/web/public/utils.js +37 -0
- package/dist/web/public/websocket.js +267 -0
- package/dist/web/public/ws.js +19 -0
- package/dist/web/session-bridge.d.ts.map +1 -1
- package/dist/web/session-bridge.js +3 -7
- package/dist/web/session-bridge.js.map +1 -1
- package/package.json +55 -54
- package/scripts/copy-native.mjs +24 -0
- package/scripts/copy-web.mjs +15 -0
- package/src/agent/index.ts +25 -25
- package/src/agent/llm.ts +4 -0
- package/src/agent/project.ts +366 -0
- package/src/agent/prompt-builder.ts +23 -13
- package/src/agent/providers/anthropic.ts +179 -172
- package/src/agent/providers/google.ts +210 -211
- package/src/agent/providers/ollama.ts +186 -182
- package/src/agent/providers/openai.ts +73 -5
- package/src/agent/session.ts +275 -153
- package/src/config/index.ts +342 -335
- package/src/tools/__tests__/bash.test.ts +1 -1
- package/src/tools/__tests__/edit.test.ts +981 -385
- package/src/tools/__tests__/grep.test.ts +35 -0
- package/src/tools/bash.ts +97 -11
- package/src/tools/edit.ts +414 -154
- package/src/tools/git.ts +76 -76
- package/src/tools/grep.ts +57 -30
- package/src/tools/read.ts +61 -25
- package/src/tools/registry.ts +231 -265
- package/src/tools/write_plan.ts +21 -5
- package/src/tui/index.ts +786 -785
- package/src/web/index.ts +29 -7
- package/src/web/public/app.js +5 -855
- package/src/web/public/confirm.js +70 -0
- package/src/web/public/index.html +121 -120
- package/src/web/public/input.js +107 -0
- package/src/web/public/messages.js +395 -0
- package/src/web/public/sidebar.js +82 -0
- package/src/web/public/state.js +72 -0
- package/src/web/public/style.css +996 -949
- package/src/web/public/utils.js +37 -0
- package/src/web/public/websocket.js +267 -0
- package/src/web/public/ws.js +19 -0
- package/src/web/session-bridge.ts +193 -194
- package/.kilo/plans/1780064105789-mighty-pixel.md +0 -171
- package/.kilo/plans/1780293725888-quick-wizard.md +0 -62
- package/.lonny/plan-web-cwd-status.md +0 -38
- package/src/tools/exec.ts +0 -348
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* ── Tool Confirm Dialog ── */
|
|
2
|
+
|
|
3
|
+
import { state } from './state.js'
|
|
4
|
+
import { escapeHtml } from './utils.js'
|
|
5
|
+
import { sendWsMsg } from './ws.js'
|
|
6
|
+
|
|
7
|
+
export function showConfirmDialog(toolCalls) {
|
|
8
|
+
const overlay = document.getElementById('confirm-overlay')
|
|
9
|
+
const list = document.getElementById('confirm-tool-list')
|
|
10
|
+
if (!overlay || !list) return
|
|
11
|
+
list.innerHTML = ''
|
|
12
|
+
for (const tc of toolCalls) {
|
|
13
|
+
const detail = tc.input?.file_path || tc.input?.command || tc.input?.package_name || ''
|
|
14
|
+
const item = document.createElement('div')
|
|
15
|
+
item.className = 'confirm-tool-item'
|
|
16
|
+
item.innerHTML =
|
|
17
|
+
`<span class="confirm-tool-name">${escapeHtml(tc.name)}</span>` +
|
|
18
|
+
(detail
|
|
19
|
+
? ` <span class="confirm-tool-input">${escapeHtml(detail.length > 80 ? `${detail.slice(0, 80)}…` : detail)}</span>`
|
|
20
|
+
: '')
|
|
21
|
+
list.appendChild(item)
|
|
22
|
+
}
|
|
23
|
+
overlay.classList.remove('hidden')
|
|
24
|
+
|
|
25
|
+
state.pendingConfirmResolve = null
|
|
26
|
+
|
|
27
|
+
function cleanup(approved) {
|
|
28
|
+
overlay.classList.add('hidden')
|
|
29
|
+
allowBtn.removeEventListener('click', onAllow)
|
|
30
|
+
rejectBtn.removeEventListener('click', onReject)
|
|
31
|
+
if (state.pendingConfirmResolve) {
|
|
32
|
+
state.pendingConfirmResolve(approved)
|
|
33
|
+
state.pendingConfirmResolve = null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const allowBtn = document.getElementById('confirm-allow-btn')
|
|
38
|
+
const rejectBtn = document.getElementById('confirm-reject-btn')
|
|
39
|
+
const onAllow = () => {
|
|
40
|
+
sendWsMsg({ type: 'tool_confirm_response', approved: true })
|
|
41
|
+
cleanup(true)
|
|
42
|
+
}
|
|
43
|
+
const onReject = () => {
|
|
44
|
+
sendWsMsg({ type: 'tool_confirm_response', approved: false })
|
|
45
|
+
cleanup(false)
|
|
46
|
+
}
|
|
47
|
+
allowBtn.addEventListener('click', onAllow)
|
|
48
|
+
rejectBtn.addEventListener('click', onReject)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function closeConfirmDialog() {
|
|
52
|
+
const overlay = document.getElementById('confirm-overlay')
|
|
53
|
+
if (overlay && !overlay.classList.contains('hidden')) {
|
|
54
|
+
overlay.classList.add('hidden')
|
|
55
|
+
if (state.pendingConfirmResolve) {
|
|
56
|
+
state.pendingConfirmResolve(false)
|
|
57
|
+
state.pendingConfirmResolve = null
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Click overlay background to close (added once)
|
|
63
|
+
const confirmOverlay = document.getElementById('confirm-overlay')
|
|
64
|
+
if (confirmOverlay) {
|
|
65
|
+
confirmOverlay.addEventListener('click', e => {
|
|
66
|
+
if (e.target === e.currentTarget) {
|
|
67
|
+
closeConfirmDialog()
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
@@ -1,120 +1,121 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Lonny - AI 编码代理</title>
|
|
7
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css">
|
|
8
|
-
<link rel="stylesheet" href="/style.css">
|
|
9
|
-
</head>
|
|
10
|
-
<body>
|
|
11
|
-
<!-- Status Bar -->
|
|
12
|
-
<header id="status-bar">
|
|
13
|
-
<div class="status-left">
|
|
14
|
-
<span class="logo">lonny</span>
|
|
15
|
-
<span class="separator">|</span>
|
|
16
|
-
<span id="status-indicator" class="status-idle">○ idle</span>
|
|
17
|
-
<span class="separator">|</span>
|
|
18
|
-
<span id="mode-display" class="mode-badge">code</span>
|
|
19
|
-
<span class="separator">|</span>
|
|
20
|
-
<span id="cwd-display" class="cwd-text"></span>
|
|
21
|
-
</div>
|
|
22
|
-
<div class="status-right">
|
|
23
|
-
<span id="model-display" class="model-name">loading...</span>
|
|
24
|
-
<span class="separator">|</span>
|
|
25
|
-
<span class="token-stats">
|
|
26
|
-
<span class="token-in" id="token-in">0</span>
|
|
27
|
-
<span class="token-sep">/</span>
|
|
28
|
-
<span class="token-out" id="token-out">0</span>
|
|
29
|
-
<span class="token-calls" id="token-calls">(0)</span>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<span
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<div class="slash-hint-
|
|
54
|
-
<div class="slash-hint-item" data-cmd="
|
|
55
|
-
<div class="slash-hint-item" data-cmd="
|
|
56
|
-
<div class="slash-hint-item" data-cmd="
|
|
57
|
-
<div class="slash-hint-item" data-cmd="
|
|
58
|
-
<div class="slash-hint-item" data-cmd="
|
|
59
|
-
<div class="slash-hint-item" data-cmd="
|
|
60
|
-
<div class="slash-hint-item" data-cmd="
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<button class="sidebar-tab" data-tab="
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<div id="plans-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<div id="todos-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<div
|
|
110
|
-
<div class="confirm-
|
|
111
|
-
|
|
112
|
-
<button id="confirm-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<script src="/
|
|
119
|
-
|
|
120
|
-
</
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Lonny - AI 编码代理</title>
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css">
|
|
8
|
+
<link rel="stylesheet" href="/style.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<!-- Status Bar -->
|
|
12
|
+
<header id="status-bar">
|
|
13
|
+
<div class="status-left">
|
|
14
|
+
<span class="logo">lonny</span>
|
|
15
|
+
<span class="separator">|</span>
|
|
16
|
+
<span id="status-indicator" class="status-idle">○ idle</span>
|
|
17
|
+
<span class="separator">|</span>
|
|
18
|
+
<span id="mode-display" class="mode-badge">code</span>
|
|
19
|
+
<span class="separator">|</span>
|
|
20
|
+
<span id="cwd-display" class="cwd-text"></span>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="status-right">
|
|
23
|
+
<span id="model-display" class="model-name">loading...</span>
|
|
24
|
+
<span class="separator">|</span>
|
|
25
|
+
<span class="token-stats">
|
|
26
|
+
<span class="token-in" id="token-in">0</span>
|
|
27
|
+
<span class="token-sep">/</span>
|
|
28
|
+
<span class="token-out" id="token-out">0</span>
|
|
29
|
+
<span class="token-calls" id="token-calls">(0)</span>
|
|
30
|
+
<span class="token-cache hidden" id="token-cache"></span>
|
|
31
|
+
</span>
|
|
32
|
+
<span class="separator" id="balance-sep" style="display:none">|</span>
|
|
33
|
+
<span id="balance-display" class="balance-display" style="display:none"></span>
|
|
34
|
+
</div>
|
|
35
|
+
</header>
|
|
36
|
+
|
|
37
|
+
<!-- App Content (chat + sidebar) -->
|
|
38
|
+
<div id="app-content">
|
|
39
|
+
<!-- Chat Panel -->
|
|
40
|
+
<div id="chat-panel">
|
|
41
|
+
<!-- Chat Container -->
|
|
42
|
+
<main id="chat-container">
|
|
43
|
+
<div id="messages">
|
|
44
|
+
<!-- Messages will be rendered here -->
|
|
45
|
+
</div>
|
|
46
|
+
</main>
|
|
47
|
+
|
|
48
|
+
<!-- Input Area -->
|
|
49
|
+
<footer id="input-area">
|
|
50
|
+
<div class="input-wrapper">
|
|
51
|
+
<div class="input-container">
|
|
52
|
+
<div id="slash-hint" class="slash-hint hidden">
|
|
53
|
+
<div class="slash-hint-header">Commands</div>
|
|
54
|
+
<div class="slash-hint-item" data-cmd="mode code|plan|ask">/mode <span class="hint-arg">code|plan|ask</span> <span class="hint-desc">Switch mode</span></div>
|
|
55
|
+
<div class="slash-hint-item" data-cmd="model ">/model <span class="hint-arg"><name></span> <span class="hint-desc">Switch model</span></div>
|
|
56
|
+
<div class="slash-hint-item" data-cmd="new">/new <span class="hint-desc">Start a new session</span></div>
|
|
57
|
+
<div class="slash-hint-item" data-cmd="stop">/stop <span class="hint-desc">Stop the running agent</span></div>
|
|
58
|
+
<div class="slash-hint-item" data-cmd="skills">/skills <span class="hint-desc">List active skills</span></div>
|
|
59
|
+
<div class="slash-hint-item" data-cmd="prompts">/prompts <span class="hint-desc">List prompt templates</span></div>
|
|
60
|
+
<div class="slash-hint-item" data-cmd="init">/init <span class="hint-desc">Create .lonny/skills/ & prompts/</span></div>
|
|
61
|
+
<div class="slash-hint-item" data-cmd="help">/help <span class="hint-desc">Show this help</span></div>
|
|
62
|
+
</div>
|
|
63
|
+
<textarea id="chat-input" rows="3" placeholder="Type a message... (type / for commands)"></textarea>
|
|
64
|
+
</div>
|
|
65
|
+
<button id="send-btn" title="Send">
|
|
66
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
67
|
+
<path d="M22 2L11 13"/><path d="M22 2L15 22L11 13L2 9L22 2Z"/>
|
|
68
|
+
</svg>
|
|
69
|
+
</button>
|
|
70
|
+
<button id="stop-btn" class="stop-btn hidden" title="Stop">
|
|
71
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
72
|
+
<rect x="6" y="6" width="12" height="12" rx="2"/>
|
|
73
|
+
</svg>
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
</footer>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Right Sidebar -->
|
|
80
|
+
<aside id="sidebar">
|
|
81
|
+
<div class="sidebar-tabs">
|
|
82
|
+
<button class="sidebar-tab active" data-tab="plans">░ Plans</button>
|
|
83
|
+
<button class="sidebar-tab" data-tab="todos">☑ Todo</button>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="sidebar-content">
|
|
86
|
+
<div class="sidebar-pane active" id="plans-pane">
|
|
87
|
+
<div id="plans-placeholder" class="sidebar-placeholder">Loading plans...</div>
|
|
88
|
+
<div id="plans-list"></div>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="sidebar-pane" id="todos-pane">
|
|
91
|
+
<div id="todos-placeholder" class="sidebar-placeholder">Loading todos...</div>
|
|
92
|
+
<div id="todos-list"></div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</aside>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- Connection status overlay -->
|
|
99
|
+
<div id="connection-overlay" class="overlay hidden">
|
|
100
|
+
<div class="overlay-content">
|
|
101
|
+
<div class="spinner"></div>
|
|
102
|
+
<p>Connecting to Lonny...</p>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<!-- Tool confirm dialog -->
|
|
107
|
+
<div id="confirm-overlay" class="overlay hidden">
|
|
108
|
+
<div class="confirm-dialog">
|
|
109
|
+
<div class="confirm-header">Allow these tool calls?</div>
|
|
110
|
+
<div id="confirm-tool-list" class="confirm-tool-list"></div>
|
|
111
|
+
<div class="confirm-actions">
|
|
112
|
+
<button id="confirm-reject-btn" class="confirm-btn confirm-btn-reject">Reject</button>
|
|
113
|
+
<button id="confirm-allow-btn" class="confirm-btn confirm-btn-allow">Allow</button>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<script src="https://cdn.jsdelivr.net/npm/marked@15.0.4/marked.min.js"></script>
|
|
119
|
+
<script type="module" src="/app.js"></script>
|
|
120
|
+
</body>
|
|
121
|
+
</html>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* ── Input Handling, Event Listeners, Slash Hints ── */
|
|
2
|
+
|
|
3
|
+
import { addErrorMessage, addUserMessage } from './messages.js'
|
|
4
|
+
import { switchTab } from './sidebar.js'
|
|
5
|
+
import { chatInput, sendBtn, sidebarTabs, slashHint, state, stopBtn } from './state.js'
|
|
6
|
+
import { sendWsMsg } from './ws.js'
|
|
7
|
+
|
|
8
|
+
function sendMessage() {
|
|
9
|
+
const text = chatInput.value.trim()
|
|
10
|
+
if (!text || state.isRunning) return
|
|
11
|
+
|
|
12
|
+
addUserMessage(text)
|
|
13
|
+
chatInput.value = ''
|
|
14
|
+
|
|
15
|
+
if (!sendWsMsg({ type: 'message', text })) {
|
|
16
|
+
addErrorMessage('Not connected to server.')
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function showSlashHint() {
|
|
21
|
+
slashHint.classList.remove('hidden')
|
|
22
|
+
const items = slashHint.querySelectorAll('.slash-hint-item')
|
|
23
|
+
items.forEach((item, i) => {
|
|
24
|
+
item.classList.toggle('selected', i === 0)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function hideSlashHint() {
|
|
29
|
+
slashHint.classList.add('hidden')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function initInput() {
|
|
33
|
+
// Send button
|
|
34
|
+
sendBtn.addEventListener('click', sendMessage)
|
|
35
|
+
|
|
36
|
+
// Stop button
|
|
37
|
+
stopBtn.addEventListener('click', () => {
|
|
38
|
+
sendWsMsg({ type: 'stop' })
|
|
39
|
+
stopBtn.disabled = true
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Chat input keyboard
|
|
43
|
+
chatInput.addEventListener('keydown', e => {
|
|
44
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
45
|
+
e.preventDefault()
|
|
46
|
+
sendMessage()
|
|
47
|
+
}
|
|
48
|
+
if (e.key === 'Tab' && !slashHint.classList.contains('hidden')) {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
const selected = slashHint.querySelector('.slash-hint-item.selected')
|
|
51
|
+
if (selected) {
|
|
52
|
+
const cmd = selected.dataset.cmd
|
|
53
|
+
chatInput.value = `/${cmd}`
|
|
54
|
+
hideSlashHint()
|
|
55
|
+
chatInput.focus()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (e.key === 'Escape') {
|
|
59
|
+
hideSlashHint()
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Slash hint: click to select
|
|
64
|
+
slashHint.addEventListener('click', e => {
|
|
65
|
+
const item = e.target.closest('.slash-hint-item')
|
|
66
|
+
if (item) {
|
|
67
|
+
const cmd = item.dataset.cmd
|
|
68
|
+
chatInput.value = `/${cmd}`
|
|
69
|
+
hideSlashHint()
|
|
70
|
+
chatInput.focus()
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Slash hint: hover to change selection
|
|
75
|
+
slashHint.addEventListener('mouseover', e => {
|
|
76
|
+
const item = e.target.closest('.slash-hint-item')
|
|
77
|
+
if (item) {
|
|
78
|
+
slashHint.querySelectorAll('.slash-hint-item').forEach(el => el.classList.remove('selected'))
|
|
79
|
+
item.classList.add('selected')
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// Show/hide hint based on input content
|
|
84
|
+
chatInput.addEventListener('input', () => {
|
|
85
|
+
chatInput.style.height = 'auto'
|
|
86
|
+
chatInput.style.height = `${Math.min(chatInput.scrollHeight, 150)}px`
|
|
87
|
+
|
|
88
|
+
const text = chatInput.value
|
|
89
|
+
if (text === '/') {
|
|
90
|
+
showSlashHint()
|
|
91
|
+
} else if (!text.startsWith('/') || text.includes(' ')) {
|
|
92
|
+
hideSlashHint()
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Sidebar tab switching
|
|
97
|
+
sidebarTabs.forEach(tab => {
|
|
98
|
+
tab.addEventListener('click', () => {
|
|
99
|
+
switchTab(tab.dataset.tab)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Heartbeat
|
|
104
|
+
setInterval(() => {
|
|
105
|
+
sendWsMsg({ type: 'ping' })
|
|
106
|
+
}, 30000)
|
|
107
|
+
}
|