@cremini/skillpack 1.0.8 → 1.0.9-im.1
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 +3 -1
- package/dist/cli.js +45 -11
- package/package.json +3 -2
- package/runtime/server/dist/adapters/markdown.js +74 -0
- package/runtime/server/dist/adapters/markdown.js.map +1 -0
- package/runtime/server/dist/adapters/slack.js +369 -0
- package/runtime/server/dist/adapters/slack.js.map +1 -0
- package/runtime/server/dist/adapters/telegram.js +199 -0
- package/runtime/server/dist/adapters/telegram.js.map +1 -0
- package/runtime/server/dist/adapters/types.js +2 -0
- package/runtime/server/dist/adapters/types.js.map +1 -0
- package/runtime/server/dist/adapters/web.js +168 -0
- package/runtime/server/dist/adapters/web.js.map +1 -0
- package/runtime/server/dist/agent.js +219 -0
- package/runtime/server/dist/agent.js.map +1 -0
- package/runtime/server/dist/config.js +73 -0
- package/runtime/server/dist/config.js.map +1 -0
- package/runtime/server/dist/index.js +122 -0
- package/runtime/server/dist/index.js.map +1 -0
- package/runtime/server/package-lock.json +2768 -121
- package/runtime/server/package.json +13 -3
- package/runtime/start.bat +2 -2
- package/runtime/start.sh +1 -1
- package/runtime/web/index.html +58 -15
- package/runtime/web/js/api.js +13 -0
- package/runtime/web/{app.js → js/chat.js} +126 -193
- package/runtime/web/js/config.js +15 -0
- package/runtime/web/js/main.js +47 -0
- package/runtime/web/js/settings.js +132 -0
- package/runtime/web/styles.css +171 -0
- package/runtime/server/chat-proxy.js +0 -161
- package/runtime/server/index.js +0 -63
- package/runtime/server/routes.js +0 -104
|
@@ -3,11 +3,21 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"
|
|
6
|
+
"build": "tsc",
|
|
7
|
+
"start": "node dist/index.js"
|
|
7
8
|
},
|
|
8
9
|
"dependencies": {
|
|
9
|
-
"express": "^5.1.0",
|
|
10
10
|
"@mariozechner/pi-coding-agent": "^0.57.1",
|
|
11
|
+
"@slack/bolt": "^4.6.0",
|
|
12
|
+
"express": "^5.1.0",
|
|
13
|
+
"node-telegram-bot-api": "^0.66.0",
|
|
11
14
|
"ws": "^8.19.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/express": "^5.0.0",
|
|
18
|
+
"@types/node": "^22.0.0",
|
|
19
|
+
"@types/node-telegram-bot-api": "^0.64.0",
|
|
20
|
+
"@types/ws": "^8.18.0",
|
|
21
|
+
"typescript": "^5.9.3"
|
|
12
22
|
}
|
|
13
|
-
}
|
|
23
|
+
}
|
package/runtime/start.bat
CHANGED
|
@@ -11,5 +11,5 @@ if not exist "server\node_modules" (
|
|
|
11
11
|
echo.
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
rem Start the server (port detection and browser launch are handled by server\index.js)
|
|
15
|
-
cd server && node index.js
|
|
14
|
+
rem Start the server (port detection and browser launch are handled by server\dist\index.js)
|
|
15
|
+
cd server && node dist/index.js
|
package/runtime/start.sh
CHANGED
package/runtime/web/index.html
CHANGED
|
@@ -25,20 +25,10 @@
|
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
27
|
<div class="sidebar-section">
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<option value="anthropic">Anthropic</option>
|
|
33
|
-
</select>
|
|
34
|
-
</div>
|
|
35
|
-
<div class="api-key-form">
|
|
36
|
-
<input type="password" id="api-key-input" placeholder="sk-..." />
|
|
37
|
-
</div>
|
|
38
|
-
<div class="api-key-footer">
|
|
39
|
-
<p id="key-status" class="status-text"></p>
|
|
40
|
-
<button id="save-key-btn">Save</button>
|
|
41
|
-
</div>
|
|
28
|
+
<button id="open-settings-btn" class="settings-trigger-btn">
|
|
29
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="btn-icon"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
30
|
+
Settings
|
|
31
|
+
</button>
|
|
42
32
|
</div>
|
|
43
33
|
</aside>
|
|
44
34
|
|
|
@@ -65,8 +55,61 @@
|
|
|
65
55
|
</div>
|
|
66
56
|
</main>
|
|
67
57
|
</div>
|
|
58
|
+
|
|
59
|
+
<!-- Settings Dialog -->
|
|
60
|
+
<dialog id="settings-dialog" class="settings-modal">
|
|
61
|
+
<div class="settings-modal-header">
|
|
62
|
+
<h2>Settings</h2>
|
|
63
|
+
<button id="close-settings-btn" class="close-btn" aria-label="Close">
|
|
64
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="settings-modal-body">
|
|
68
|
+
<div class="settings-sections">
|
|
69
|
+
<!-- General Section -->
|
|
70
|
+
<div class="settings-section">
|
|
71
|
+
<h3 class="section-title">General</h3>
|
|
72
|
+
<div class="form-group">
|
|
73
|
+
<label>Provider</label>
|
|
74
|
+
<div class="provider-select-wrapper">
|
|
75
|
+
<select id="provider-select">
|
|
76
|
+
<option value="openai">OpenAI</option>
|
|
77
|
+
<option value="anthropic">Anthropic</option>
|
|
78
|
+
</select>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="form-group">
|
|
82
|
+
<label>API Key</label>
|
|
83
|
+
<input type="password" id="api-key-input" placeholder="sk-..." class="form-input" />
|
|
84
|
+
<p id="key-status" class="status-text" style="margin-top: 8px; min-height: 18px;"></p>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- IM Bots Section -->
|
|
89
|
+
<div class="settings-section">
|
|
90
|
+
<h3 class="section-title">IM Bots</h3>
|
|
91
|
+
<div class="form-group">
|
|
92
|
+
<label>Telegram Bot Token</label>
|
|
93
|
+
<input type="password" id="telegram-token-input" placeholder="123456:ABC-DEF..." class="form-input" />
|
|
94
|
+
</div>
|
|
95
|
+
<div class="form-group">
|
|
96
|
+
<label>Slack Bot Token</label>
|
|
97
|
+
<input type="password" id="slack-bot-token-input" placeholder="xoxb-..." class="form-input" />
|
|
98
|
+
</div>
|
|
99
|
+
<div class="form-group">
|
|
100
|
+
<label>Slack App Token</label>
|
|
101
|
+
<input type="password" id="slack-app-token-input" placeholder="xapp-..." class="form-input" />
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<div class="settings-modal-footer">
|
|
107
|
+
<button id="save-settings-btn" class="primary-btn">Save Settings</button>
|
|
108
|
+
</div>
|
|
109
|
+
</dialog>
|
|
110
|
+
|
|
68
111
|
<script src="marked.min.js"></script>
|
|
69
|
-
<script src="
|
|
112
|
+
<script type="module" src="js/main.js"></script>
|
|
70
113
|
</body>
|
|
71
114
|
|
|
72
115
|
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { state } from "./config.js";
|
|
2
|
+
|
|
3
|
+
export async function saveConfigData(updates) {
|
|
4
|
+
const res = await fetch(state.API_BASE + "/api/config/update", {
|
|
5
|
+
method: "POST",
|
|
6
|
+
headers: { "Content-Type": "application/json" },
|
|
7
|
+
body: JSON.stringify(updates),
|
|
8
|
+
});
|
|
9
|
+
if (!res.ok) {
|
|
10
|
+
throw new Error("Save Config Failed");
|
|
11
|
+
}
|
|
12
|
+
return await res.json();
|
|
13
|
+
}
|
|
@@ -1,69 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
let chatHistory = [];
|
|
1
|
+
import { state } from "./config.js";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
setupEventListeners();
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async function loadConfig() {
|
|
11
|
-
try {
|
|
12
|
-
const res = await fetch(API_BASE + "/api/config");
|
|
13
|
-
const config = await res.json();
|
|
14
|
-
|
|
15
|
-
document.getElementById("pack-name").textContent = config.name;
|
|
16
|
-
document.getElementById("pack-desc").textContent = config.description;
|
|
17
|
-
document.title = config.name;
|
|
18
|
-
|
|
19
|
-
// Skills
|
|
20
|
-
const skillsList = document.getElementById("skills-list");
|
|
21
|
-
skillsList.innerHTML = config.skills
|
|
22
|
-
.map(
|
|
23
|
-
(s) =>
|
|
24
|
-
`<li><div class="skill-name">${s.name}</div><div class="skill-desc">${s.description}</div></li>`,
|
|
25
|
-
)
|
|
26
|
-
.join("");
|
|
27
|
-
|
|
28
|
-
// Pre-fill when there is exactly one prompt
|
|
29
|
-
if (config.prompts && config.prompts.length === 1) {
|
|
30
|
-
const input = document.getElementById("user-input");
|
|
31
|
-
input.value = config.prompts[0];
|
|
32
|
-
input.style.height = "auto";
|
|
33
|
-
input.style.height = Math.min(input.scrollHeight, 120) + "px";
|
|
34
|
-
}
|
|
3
|
+
export const chatHistory = [];
|
|
4
|
+
let ws = null;
|
|
5
|
+
let currentAssistantMsg = null;
|
|
35
6
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
keyStatus.textContent = "API key configured";
|
|
40
|
-
keyStatus.className = "status-text success";
|
|
41
|
-
}
|
|
7
|
+
export function initChat() {
|
|
8
|
+
// Send button
|
|
9
|
+
document.getElementById("send-btn").addEventListener("click", sendMessage);
|
|
42
10
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
11
|
+
// Send on Enter
|
|
12
|
+
document.getElementById("user-input").addEventListener("keydown", (e) => {
|
|
13
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
sendMessage();
|
|
46
16
|
}
|
|
17
|
+
});
|
|
47
18
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
else input.placeholder = "sk-...";
|
|
54
|
-
};
|
|
19
|
+
// Auto-resize the input box
|
|
20
|
+
document.getElementById("user-input").addEventListener("input", (e) => {
|
|
21
|
+
e.target.style.height = "auto";
|
|
22
|
+
e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
|
|
23
|
+
});
|
|
55
24
|
|
|
56
|
-
|
|
57
|
-
|
|
25
|
+
// Prompt click (Welcome message prompts)
|
|
26
|
+
const welcomeContent = document.getElementById("welcome-content");
|
|
27
|
+
if (welcomeContent) {
|
|
28
|
+
welcomeContent.addEventListener("click", (e) => {
|
|
29
|
+
const item = e.target.closest(".prompt-card");
|
|
30
|
+
if (!item) return;
|
|
31
|
+
const index = parseInt(item.dataset.index);
|
|
58
32
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
33
|
+
if (state.config && state.config.prompts[index]) {
|
|
34
|
+
const input = document.getElementById("user-input");
|
|
35
|
+
input.value = state.config.prompts[index];
|
|
36
|
+
input.focus();
|
|
37
|
+
input.style.height = "auto";
|
|
38
|
+
input.style.height = Math.min(input.scrollHeight, 120) + "px";
|
|
39
|
+
}
|
|
40
|
+
});
|
|
63
41
|
}
|
|
64
42
|
}
|
|
65
43
|
|
|
66
|
-
function showWelcome(config) {
|
|
44
|
+
export function showWelcome(config) {
|
|
67
45
|
const welcomeContent = document.getElementById("welcome-content");
|
|
68
46
|
|
|
69
47
|
let promptsHtml = "";
|
|
@@ -94,94 +72,69 @@ function showWelcome(config) {
|
|
|
94
72
|
}
|
|
95
73
|
}
|
|
96
74
|
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
|
|
75
|
+
export async function sendMessage() {
|
|
76
|
+
const input = document.getElementById("user-input");
|
|
77
|
+
const text = input.value.trim();
|
|
78
|
+
if (!text) return;
|
|
100
79
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
});
|
|
80
|
+
const chatArea = document.getElementById("chat-area");
|
|
81
|
+
if (chatArea.classList.contains("mode-welcome")) {
|
|
82
|
+
chatArea.classList.remove("mode-welcome");
|
|
83
|
+
chatArea.classList.add("mode-chat");
|
|
84
|
+
}
|
|
108
85
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
e.target.style.height = "auto";
|
|
112
|
-
e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
|
|
113
|
-
});
|
|
86
|
+
input.value = "";
|
|
87
|
+
input.style.height = "auto";
|
|
114
88
|
|
|
115
|
-
|
|
116
|
-
|
|
89
|
+
appendMessage("user", text);
|
|
90
|
+
chatHistory.push({ role: "user", content: text });
|
|
117
91
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (welcomeContent) {
|
|
121
|
-
welcomeContent.addEventListener("click", (e) => {
|
|
122
|
-
const item = e.target.closest(".prompt-card");
|
|
123
|
-
if (!item) return;
|
|
124
|
-
const index = parseInt(item.dataset.index);
|
|
92
|
+
const sendBtn = document.getElementById("send-btn");
|
|
93
|
+
sendBtn.disabled = true;
|
|
125
94
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
input.style.height = "auto";
|
|
135
|
-
input.style.height = Math.min(input.scrollHeight, 120) + "px";
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
});
|
|
95
|
+
currentAssistantMsg = appendMessage("assistant", "");
|
|
96
|
+
showLoadingIndicator();
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const socket = await getOrCreateWs();
|
|
100
|
+
socket.send(JSON.stringify({ text }));
|
|
101
|
+
} catch (err) {
|
|
102
|
+
handleError(err.message);
|
|
139
103
|
}
|
|
140
104
|
}
|
|
141
105
|
|
|
142
|
-
async function
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const provider = providerSelect ? providerSelect.value : "openai";
|
|
148
|
-
|
|
149
|
-
if (!key) {
|
|
150
|
-
status.textContent = "Enter an API key";
|
|
151
|
-
status.className = "status-text error";
|
|
152
|
-
return;
|
|
106
|
+
export async function sendBotCommand(cmdText) {
|
|
107
|
+
const chatArea = document.getElementById("chat-area");
|
|
108
|
+
if (chatArea.classList.contains("mode-welcome")) {
|
|
109
|
+
chatArea.classList.remove("mode-welcome");
|
|
110
|
+
chatArea.classList.add("mode-chat");
|
|
153
111
|
}
|
|
154
112
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
method: "POST",
|
|
158
|
-
headers: { "Content-Type": "application/json" },
|
|
159
|
-
body: JSON.stringify({ key, provider }),
|
|
160
|
-
});
|
|
113
|
+
appendMessage("user", cmdText);
|
|
114
|
+
chatHistory.push({ role: "user", content: cmdText });
|
|
161
115
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
116
|
+
const sendBtn = document.getElementById("send-btn");
|
|
117
|
+
sendBtn.disabled = true;
|
|
118
|
+
|
|
119
|
+
currentAssistantMsg = appendMessage("assistant", "");
|
|
120
|
+
showLoadingIndicator();
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const socket = await getOrCreateWs();
|
|
124
|
+
socket.send(JSON.stringify({ text: cmdText }));
|
|
170
125
|
} catch (err) {
|
|
171
|
-
|
|
172
|
-
status.className = "status-text error";
|
|
126
|
+
handleError(err.message);
|
|
173
127
|
}
|
|
174
128
|
}
|
|
175
129
|
|
|
176
|
-
|
|
177
|
-
let currentAssistantMsg = null;
|
|
130
|
+
// ========== Internal Rendering & WS logic ==========
|
|
178
131
|
|
|
179
132
|
function renderMarkdown(mdText, { renderEmbeddedMarkdown = true } = {}) {
|
|
180
|
-
if (typeof marked === "undefined") {
|
|
133
|
+
if (typeof window.marked === "undefined") {
|
|
181
134
|
return escapeHtml(mdText);
|
|
182
135
|
}
|
|
183
136
|
|
|
184
|
-
const html = marked.parse(mdText);
|
|
137
|
+
const html = ensureLinksOpenInNewTab(window.marked.parse(mdText));
|
|
185
138
|
if (!renderEmbeddedMarkdown) {
|
|
186
139
|
return html;
|
|
187
140
|
}
|
|
@@ -189,6 +142,18 @@ function renderMarkdown(mdText, { renderEmbeddedMarkdown = true } = {}) {
|
|
|
189
142
|
return renderEmbeddedMarkdownBlocks(html);
|
|
190
143
|
}
|
|
191
144
|
|
|
145
|
+
function ensureLinksOpenInNewTab(html) {
|
|
146
|
+
const template = document.createElement("template");
|
|
147
|
+
template.innerHTML = html;
|
|
148
|
+
|
|
149
|
+
template.content.querySelectorAll("a[href]").forEach((linkEl) => {
|
|
150
|
+
linkEl.setAttribute("target", "_blank");
|
|
151
|
+
linkEl.setAttribute("rel", "noopener noreferrer");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return template.innerHTML;
|
|
155
|
+
}
|
|
156
|
+
|
|
192
157
|
function renderEmbeddedMarkdownBlocks(html) {
|
|
193
158
|
const template = document.createElement("template");
|
|
194
159
|
template.innerHTML = html;
|
|
@@ -196,11 +161,9 @@ function renderEmbeddedMarkdownBlocks(html) {
|
|
|
196
161
|
const codeBlocks = template.content.querySelectorAll("pre > code");
|
|
197
162
|
codeBlocks.forEach((codeEl) => {
|
|
198
163
|
const languageClass = Array.from(codeEl.classList).find((className) =>
|
|
199
|
-
className.startsWith("language-")
|
|
164
|
+
className.startsWith("language-")
|
|
200
165
|
);
|
|
201
|
-
const language = languageClass
|
|
202
|
-
? languageClass.slice("language-".length)
|
|
203
|
-
: "";
|
|
166
|
+
const language = languageClass ? languageClass.slice("language-".length) : "";
|
|
204
167
|
|
|
205
168
|
if (!/^(markdown|md)$/i.test(language)) {
|
|
206
169
|
return;
|
|
@@ -228,11 +191,9 @@ async function getOrCreateWs() {
|
|
|
228
191
|
|
|
229
192
|
return new Promise((resolve, reject) => {
|
|
230
193
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
231
|
-
const
|
|
232
|
-
const provider = providerSelect ? providerSelect.value : "openai";
|
|
194
|
+
const provider = state.config && state.config.provider ? state.config.provider : "openai";
|
|
233
195
|
|
|
234
|
-
|
|
235
|
-
const wsUrl = `${protocol}//${window.location.host}${API_BASE}/api/chat?provider=${provider}`;
|
|
196
|
+
const wsUrl = `${protocol}//${window.location.host}${state.API_BASE}/api/chat?provider=${provider}`;
|
|
236
197
|
|
|
237
198
|
ws = new WebSocket(wsUrl);
|
|
238
199
|
|
|
@@ -315,12 +276,27 @@ function handleAgentEvent(event) {
|
|
|
315
276
|
|
|
316
277
|
if (
|
|
317
278
|
["text_delta", "thinking_delta", "tool_start", "tool_end"].includes(
|
|
318
|
-
event.type
|
|
279
|
+
event.type
|
|
319
280
|
)
|
|
320
281
|
) {
|
|
321
282
|
hideLoadingIndicator();
|
|
322
283
|
}
|
|
323
284
|
|
|
285
|
+
// Handle bot command results injected by the backend WebSocket response
|
|
286
|
+
if (event.type === "command_result") {
|
|
287
|
+
const textBlock = getOrCreateTextBlock();
|
|
288
|
+
let resText = `Command \`${event.command}\` succeeded.`;
|
|
289
|
+
if (event.errorMessage) {
|
|
290
|
+
resText = `Command \`${event.command}\` failed: ${event.errorMessage}`;
|
|
291
|
+
} else if (event.result) {
|
|
292
|
+
resText = `Command \`${event.command}\` result:\n\n${event.result}`;
|
|
293
|
+
}
|
|
294
|
+
textBlock.dataset.mdContent += resText;
|
|
295
|
+
textBlock.innerHTML = renderMarkdown(textBlock.dataset.mdContent);
|
|
296
|
+
scrollToBottom();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
324
300
|
switch (event.type) {
|
|
325
301
|
case "agent_start":
|
|
326
302
|
case "message_start":
|
|
@@ -336,7 +312,7 @@ function handleAgentEvent(event) {
|
|
|
336
312
|
const thinkingBlock = getOrCreateThinkingBlock();
|
|
337
313
|
thinkingBlock.dataset.mdContent += event.delta;
|
|
338
314
|
const contentEl = thinkingBlock.querySelector(".thinking-content");
|
|
339
|
-
if (typeof marked !== "undefined") {
|
|
315
|
+
if (typeof window.marked !== "undefined") {
|
|
340
316
|
contentEl.innerHTML = renderMarkdown(thinkingBlock.dataset.mdContent);
|
|
341
317
|
} else {
|
|
342
318
|
contentEl.textContent = thinkingBlock.dataset.mdContent;
|
|
@@ -347,7 +323,7 @@ function handleAgentEvent(event) {
|
|
|
347
323
|
case "text_delta":
|
|
348
324
|
const textBlock = getOrCreateTextBlock();
|
|
349
325
|
textBlock.dataset.mdContent += event.delta;
|
|
350
|
-
if (typeof marked !== "undefined") {
|
|
326
|
+
if (typeof window.marked !== "undefined") {
|
|
351
327
|
textBlock.innerHTML = renderMarkdown(textBlock.dataset.mdContent);
|
|
352
328
|
} else {
|
|
353
329
|
textBlock.textContent = textBlock.dataset.mdContent;
|
|
@@ -364,8 +340,10 @@ function handleAgentEvent(event) {
|
|
|
364
340
|
: JSON.stringify(event.toolInput, null, 2);
|
|
365
341
|
|
|
366
342
|
let inputHtml = "";
|
|
367
|
-
if (typeof marked !== "undefined") {
|
|
368
|
-
inputHtml =
|
|
343
|
+
if (typeof window.marked !== "undefined") {
|
|
344
|
+
inputHtml = ensureLinksOpenInNewTab(
|
|
345
|
+
window.marked.parse("\`\`\`json\n" + safeInput + "\n\`\`\`")
|
|
346
|
+
);
|
|
369
347
|
} else {
|
|
370
348
|
inputHtml = escapeHtml(safeInput);
|
|
371
349
|
}
|
|
@@ -373,7 +351,7 @@ function handleAgentEvent(event) {
|
|
|
373
351
|
toolCard.innerHTML = `
|
|
374
352
|
<div class="tool-header">
|
|
375
353
|
<span class="tool-chevron">
|
|
376
|
-
|
|
354
|
+
<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
|
377
355
|
</span>
|
|
378
356
|
<span class="tool-icon">🛠️</span>
|
|
379
357
|
<span class="tool-name">${escapeHtml(event.toolName)}</span>
|
|
@@ -389,9 +367,7 @@ function handleAgentEvent(event) {
|
|
|
389
367
|
toolCard.classList.toggle("collapsed");
|
|
390
368
|
});
|
|
391
369
|
|
|
392
|
-
|
|
393
|
-
const toolIndicator =
|
|
394
|
-
currentAssistantMsg.querySelector(".loading-indicator");
|
|
370
|
+
const toolIndicator = currentAssistantMsg.querySelector(".loading-indicator");
|
|
395
371
|
if (toolIndicator) {
|
|
396
372
|
currentAssistantMsg.insertBefore(toolCard, toolIndicator);
|
|
397
373
|
} else {
|
|
@@ -400,17 +376,12 @@ function handleAgentEvent(event) {
|
|
|
400
376
|
|
|
401
377
|
toolCard.dataset.toolName = event.toolName;
|
|
402
378
|
scrollToBottom();
|
|
403
|
-
|
|
404
379
|
showLoadingIndicator();
|
|
405
380
|
break;
|
|
406
381
|
|
|
407
382
|
case "tool_end":
|
|
408
|
-
const cards = Array.from(
|
|
409
|
-
|
|
410
|
-
);
|
|
411
|
-
const card = cards
|
|
412
|
-
.reverse()
|
|
413
|
-
.find((c) => c.dataset.toolName === event.toolName);
|
|
383
|
+
const cards = Array.from(currentAssistantMsg.querySelectorAll(".tool-card.running"));
|
|
384
|
+
const card = cards.reverse().find((c) => c.dataset.toolName === event.toolName);
|
|
414
385
|
if (card) {
|
|
415
386
|
card.classList.remove("running");
|
|
416
387
|
card.classList.add(event.isError ? "error" : "success");
|
|
@@ -434,17 +405,16 @@ function handleAgentEvent(event) {
|
|
|
434
405
|
event.result &&
|
|
435
406
|
typeof event.result === "string" &&
|
|
436
407
|
(event.result.includes("\n") || event.result.length > 50)
|
|
437
|
-
? "
|
|
438
|
-
: "
|
|
408
|
+
? "\`\`\`bash\n" + safeResult + "\n\`\`\`"
|
|
409
|
+
: "\`\`\`json\n" + safeResult + "\n\`\`\`";
|
|
439
410
|
|
|
440
|
-
if (typeof marked !== "undefined") {
|
|
441
|
-
resultEl.innerHTML = marked.parse(mdText);
|
|
411
|
+
if (typeof window.marked !== "undefined") {
|
|
412
|
+
resultEl.innerHTML = ensureLinksOpenInNewTab(window.marked.parse(mdText));
|
|
442
413
|
} else {
|
|
443
414
|
resultEl.textContent = safeResult;
|
|
444
415
|
}
|
|
445
416
|
}
|
|
446
417
|
scrollToBottom();
|
|
447
|
-
|
|
448
418
|
showLoadingIndicator();
|
|
449
419
|
break;
|
|
450
420
|
}
|
|
@@ -452,7 +422,7 @@ function handleAgentEvent(event) {
|
|
|
452
422
|
|
|
453
423
|
function getOrCreateThinkingBlock() {
|
|
454
424
|
const children = Array.from(currentAssistantMsg.children).filter(
|
|
455
|
-
(c) => !c.classList.contains("loading-indicator")
|
|
425
|
+
(c) => !c.classList.contains("loading-indicator")
|
|
456
426
|
);
|
|
457
427
|
let lastChild = children[children.length - 1];
|
|
458
428
|
|
|
@@ -464,7 +434,7 @@ function getOrCreateThinkingBlock() {
|
|
|
464
434
|
lastChild.innerHTML = `
|
|
465
435
|
<div class="tool-header thinking-header">
|
|
466
436
|
<span class="tool-chevron">
|
|
467
|
-
|
|
437
|
+
<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
|
468
438
|
</span>
|
|
469
439
|
<span class="tool-icon">🧠</span>
|
|
470
440
|
<span class="tool-name" style="color: var(--text-secondary);">Thinking Process</span>
|
|
@@ -488,7 +458,7 @@ function getOrCreateThinkingBlock() {
|
|
|
488
458
|
|
|
489
459
|
function getOrCreateTextBlock() {
|
|
490
460
|
const children = Array.from(currentAssistantMsg.children).filter(
|
|
491
|
-
(c) => !c.classList.contains("loading-indicator")
|
|
461
|
+
(c) => !c.classList.contains("loading-indicator")
|
|
492
462
|
);
|
|
493
463
|
let lastChild = children[children.length - 1];
|
|
494
464
|
|
|
@@ -513,40 +483,6 @@ function enableInput() {
|
|
|
513
483
|
currentAssistantMsg = null;
|
|
514
484
|
}
|
|
515
485
|
|
|
516
|
-
async function sendMessage() {
|
|
517
|
-
const input = document.getElementById("user-input");
|
|
518
|
-
const text = input.value.trim();
|
|
519
|
-
if (!text) return;
|
|
520
|
-
|
|
521
|
-
const chatArea = document.getElementById("chat-area");
|
|
522
|
-
if (chatArea.classList.contains("mode-welcome")) {
|
|
523
|
-
chatArea.classList.remove("mode-welcome");
|
|
524
|
-
chatArea.classList.add("mode-chat");
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
input.value = "";
|
|
528
|
-
input.style.height = "auto";
|
|
529
|
-
|
|
530
|
-
// Add the user message
|
|
531
|
-
appendMessage("user", text);
|
|
532
|
-
chatHistory.push({ role: "user", content: text });
|
|
533
|
-
|
|
534
|
-
// Disable input while the agent is responding
|
|
535
|
-
const sendBtn = document.getElementById("send-btn");
|
|
536
|
-
sendBtn.disabled = true;
|
|
537
|
-
|
|
538
|
-
// Create an assistant message placeholder
|
|
539
|
-
currentAssistantMsg = appendMessage("assistant", "");
|
|
540
|
-
showLoadingIndicator();
|
|
541
|
-
|
|
542
|
-
try {
|
|
543
|
-
const socket = await getOrCreateWs();
|
|
544
|
-
socket.send(JSON.stringify({ text }));
|
|
545
|
-
} catch (err) {
|
|
546
|
-
handleError(err.message);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
486
|
function appendMessage(role, text) {
|
|
551
487
|
const messages = document.getElementById("messages");
|
|
552
488
|
const div = document.createElement("div");
|
|
@@ -577,6 +513,3 @@ function scrollToBottom() {
|
|
|
577
513
|
const messages = document.getElementById("messages");
|
|
578
514
|
messages.scrollTop = messages.scrollHeight;
|
|
579
515
|
}
|
|
580
|
-
|
|
581
|
-
// Start the app
|
|
582
|
-
init();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const state = {
|
|
2
|
+
config: null,
|
|
3
|
+
API_BASE: ""
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export async function loadConfig() {
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch(state.API_BASE + "/api/config");
|
|
9
|
+
state.config = await res.json();
|
|
10
|
+
return state.config;
|
|
11
|
+
} catch (err) {
|
|
12
|
+
console.error("Failed to load config:", err);
|
|
13
|
+
throw err;
|
|
14
|
+
}
|
|
15
|
+
}
|