flawed-avatar 0.2.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.
Files changed (152) hide show
  1. package/assets/animations/idle/Breathing Idle.fbx +0 -0
  2. package/assets/animations/idle/look away gesture.fbx +0 -0
  3. package/assets/animations/idle/weight shift.fbx +0 -0
  4. package/assets/animations/speaking/Agreeing.fbx +0 -0
  5. package/assets/animations/speaking/Talking (1).fbx +0 -0
  6. package/assets/animations/speaking/Talking (2).fbx +0 -0
  7. package/assets/animations/speaking/Talking (3).fbx +0 -0
  8. package/assets/animations/speaking/Talking.fbx +0 -0
  9. package/assets/animations/speaking/head nod yes.fbx +0 -0
  10. package/assets/animations/thinking/Thinking.fbx +0 -0
  11. package/assets/animations/thinking/thoughtful head shake.fbx +0 -0
  12. package/assets/animations/working/acknowledging.fbx +0 -0
  13. package/assets/animations/working/lengthy head nod.fbx +0 -0
  14. package/assets/icon.png +0 -0
  15. package/assets/models/CaptainLobster.vrm +0 -0
  16. package/assets/models/default-avatar.vrm +0 -0
  17. package/dist/chat-preload.cjs +87 -0
  18. package/dist/chat-renderer-bundle/chat-index.html +16 -0
  19. package/dist/chat-renderer-bundle/chat-renderer.js +355 -0
  20. package/dist/chat-renderer-bundle/styles/base.css +106 -0
  21. package/dist/chat-renderer-bundle/styles/chat.css +516 -0
  22. package/dist/chat-renderer-bundle/styles/components/button.css +221 -0
  23. package/dist/chat-renderer-bundle/styles/components/indicator.css +216 -0
  24. package/dist/chat-renderer-bundle/styles/components/input.css +139 -0
  25. package/dist/chat-renderer-bundle/styles/components/toast.css +204 -0
  26. package/dist/chat-renderer-bundle/styles/controls.css +279 -0
  27. package/dist/chat-renderer-bundle/styles/settings.css +310 -0
  28. package/dist/chat-renderer-bundle/styles/tokens.css +220 -0
  29. package/dist/chat-renderer-bundle/styles/utilities.css +349 -0
  30. package/dist/main/main/display-utils.d.ts +12 -0
  31. package/dist/main/main/display-utils.js +29 -0
  32. package/dist/main/main/gateway-client.d.ts +13 -0
  33. package/dist/main/main/gateway-client.js +265 -0
  34. package/dist/main/main/main.d.ts +1 -0
  35. package/dist/main/main/main.js +157 -0
  36. package/dist/main/main/persistence/chat-store.d.ts +8 -0
  37. package/dist/main/main/persistence/chat-store.js +110 -0
  38. package/dist/main/main/persistence/file-store.d.ts +17 -0
  39. package/dist/main/main/persistence/file-store.js +183 -0
  40. package/dist/main/main/persistence/index.d.ts +8 -0
  41. package/dist/main/main/persistence/index.js +8 -0
  42. package/dist/main/main/persistence/migrations.d.ts +23 -0
  43. package/dist/main/main/persistence/migrations.js +191 -0
  44. package/dist/main/main/persistence/settings-store.d.ts +32 -0
  45. package/dist/main/main/persistence/settings-store.js +174 -0
  46. package/dist/main/main/persistence/types.d.ts +72 -0
  47. package/dist/main/main/persistence/types.js +69 -0
  48. package/dist/main/main/settings-broadcast.d.ts +3 -0
  49. package/dist/main/main/settings-broadcast.js +9 -0
  50. package/dist/main/main/stdin-listener.d.ts +15 -0
  51. package/dist/main/main/stdin-listener.js +27 -0
  52. package/dist/main/main/tray.d.ts +3 -0
  53. package/dist/main/main/tray.js +59 -0
  54. package/dist/main/main/window-manager.d.ts +23 -0
  55. package/dist/main/main/window-manager.js +232 -0
  56. package/dist/main/main/window.d.ts +3 -0
  57. package/dist/main/main/window.js +528 -0
  58. package/dist/main/shared/config.d.ts +91 -0
  59. package/dist/main/shared/config.js +111 -0
  60. package/dist/main/shared/ipc-channels.d.ts +54 -0
  61. package/dist/main/shared/ipc-channels.js +68 -0
  62. package/dist/main/shared/types.d.ts +6 -0
  63. package/dist/main/shared/types.js +1 -0
  64. package/dist/preload.cjs +256 -0
  65. package/dist/renderer-bundle/index.html +63 -0
  66. package/dist/renderer-bundle/renderer.js +100734 -0
  67. package/dist/renderer-bundle/styles/base.css +106 -0
  68. package/dist/renderer-bundle/styles/chat.css +516 -0
  69. package/dist/renderer-bundle/styles/components/button.css +221 -0
  70. package/dist/renderer-bundle/styles/components/indicator.css +216 -0
  71. package/dist/renderer-bundle/styles/components/input.css +139 -0
  72. package/dist/renderer-bundle/styles/components/toast.css +204 -0
  73. package/dist/renderer-bundle/styles/controls.css +279 -0
  74. package/dist/renderer-bundle/styles/settings.css +310 -0
  75. package/dist/renderer-bundle/styles/tokens.css +220 -0
  76. package/dist/renderer-bundle/styles/utilities.css +349 -0
  77. package/index.ts +32 -0
  78. package/openclaw.plugin.json +22 -0
  79. package/package.json +45 -0
  80. package/src/electron-launcher.ts +63 -0
  81. package/src/main/chat-preload.cjs +87 -0
  82. package/src/main/display-utils.ts +39 -0
  83. package/src/main/gateway-client.ts +312 -0
  84. package/src/main/main.ts +169 -0
  85. package/src/main/persistence/chat-store.ts +143 -0
  86. package/src/main/persistence/file-store.ts +221 -0
  87. package/src/main/persistence/index.ts +69 -0
  88. package/src/main/persistence/migrations.ts +232 -0
  89. package/src/main/persistence/settings-store.ts +219 -0
  90. package/src/main/persistence/types.ts +107 -0
  91. package/src/main/preload.cjs +256 -0
  92. package/src/main/settings-broadcast.ts +13 -0
  93. package/src/main/settings-preload.cjs +153 -0
  94. package/src/main/stdin-listener.ts +34 -0
  95. package/src/main/tray.ts +65 -0
  96. package/src/main/window-manager.ts +298 -0
  97. package/src/main/window.ts +614 -0
  98. package/src/renderer/audio/audio-player.ts +161 -0
  99. package/src/renderer/audio/frequency-analyzer.ts +104 -0
  100. package/src/renderer/audio/index.ts +36 -0
  101. package/src/renderer/audio/kokoro-model-loader.ts +128 -0
  102. package/src/renderer/audio/kokoro-tts-service.ts +370 -0
  103. package/src/renderer/audio/lip-sync-profile.json +1 -0
  104. package/src/renderer/audio/phoneme-mapper.ts +120 -0
  105. package/src/renderer/audio/tts-controller.ts +344 -0
  106. package/src/renderer/audio/tts-service-factory.ts +75 -0
  107. package/src/renderer/audio/tts-service.ts +16 -0
  108. package/src/renderer/audio/types.ts +120 -0
  109. package/src/renderer/audio/web-speech-tts.ts +177 -0
  110. package/src/renderer/audio/wlipsync-analyzer.ts +145 -0
  111. package/src/renderer/avatar/animation-loader.ts +114 -0
  112. package/src/renderer/avatar/animator.ts +322 -0
  113. package/src/renderer/avatar/expressions.ts +165 -0
  114. package/src/renderer/avatar/eye-gaze.ts +255 -0
  115. package/src/renderer/avatar/eye-saccades.ts +133 -0
  116. package/src/renderer/avatar/hover-awareness.ts +125 -0
  117. package/src/renderer/avatar/ibl-enhancer.ts +163 -0
  118. package/src/renderer/avatar/lip-sync.ts +258 -0
  119. package/src/renderer/avatar/mixamo-retarget.ts +169 -0
  120. package/src/renderer/avatar/pixel-transparency.ts +65 -0
  121. package/src/renderer/avatar/scene.ts +70 -0
  122. package/src/renderer/avatar/spring-bones.ts +27 -0
  123. package/src/renderer/avatar/state-machine.ts +117 -0
  124. package/src/renderer/avatar/vrm-loader.ts +71 -0
  125. package/src/renderer/chat-window/chat-index.html +16 -0
  126. package/src/renderer/chat-window/chat-renderer.ts +28 -0
  127. package/src/renderer/index.html +63 -0
  128. package/src/renderer/renderer.ts +329 -0
  129. package/src/renderer/settings-window/settings-controls.ts +223 -0
  130. package/src/renderer/settings-window/settings-index.html +16 -0
  131. package/src/renderer/settings-window/settings-panel.ts +346 -0
  132. package/src/renderer/settings-window/settings-renderer.ts +5 -0
  133. package/src/renderer/styles/base.css +106 -0
  134. package/src/renderer/styles/chat.css +516 -0
  135. package/src/renderer/styles/components/button.css +221 -0
  136. package/src/renderer/styles/components/indicator.css +216 -0
  137. package/src/renderer/styles/components/input.css +139 -0
  138. package/src/renderer/styles/components/toast.css +204 -0
  139. package/src/renderer/styles/controls.css +279 -0
  140. package/src/renderer/styles/settings.css +310 -0
  141. package/src/renderer/styles/tokens.css +220 -0
  142. package/src/renderer/styles/utilities.css +349 -0
  143. package/src/renderer/types/avatar-bridge.d.ts +86 -0
  144. package/src/renderer/types/chat-bridge.d.ts +37 -0
  145. package/src/renderer/types/settings-bridge.d.ts +54 -0
  146. package/src/renderer/ui/chat-bubble.ts +435 -0
  147. package/src/renderer/ui/icons.ts +47 -0
  148. package/src/renderer/ui/typing-indicator.ts +41 -0
  149. package/src/service.ts +163 -0
  150. package/src/shared/config.ts +135 -0
  151. package/src/shared/ipc-channels.ts +81 -0
  152. package/src/shared/types.ts +7 -0
Binary file
Binary file
Binary file
@@ -0,0 +1,87 @@
1
+ const { contextBridge, ipcRenderer } = require("electron");
2
+
3
+ const IPC = {
4
+ AGENT_STATE: "avatar:agent-state",
5
+ SEND_CHAT: "avatar:send-chat",
6
+ SET_IGNORE_MOUSE_CHAT: "chat:set-ignore-mouse",
7
+ CHAT_CONTENT_HIDDEN: "chat:content-hidden",
8
+ CHAT_CONTENT_SHOWN: "chat:content-shown",
9
+ SHOW_CHAT_BUBBLE: "chat:show-bubble",
10
+ // Chat history
11
+ GET_CHAT_HISTORY: "chat:get-history",
12
+ APPEND_CHAT_MESSAGE: "chat:append-message",
13
+ CLEAR_CHAT_HISTORY: "chat:clear-history",
14
+ CHAT_HISTORY_CLEARED: "chat:history-cleared",
15
+ // Idle timeout
16
+ GET_IDLE_TIMEOUT: "chat:get-idle-timeout",
17
+ SET_IDLE_TIMEOUT: "chat:set-idle-timeout",
18
+ IDLE_TIMEOUT_CHANGED: "chat:idle-timeout-changed",
19
+ };
20
+
21
+ contextBridge.exposeInMainWorld("chatBridge", {
22
+ onAgentState(callback) {
23
+ ipcRenderer.removeAllListeners(IPC.AGENT_STATE);
24
+ ipcRenderer.on(IPC.AGENT_STATE, (_event, state) => {
25
+ callback(state);
26
+ });
27
+ },
28
+
29
+ sendChat(text) {
30
+ ipcRenderer.send(IPC.SEND_CHAT, text);
31
+ },
32
+
33
+ setIgnoreMouseEvents(ignore) {
34
+ ipcRenderer.send(IPC.SET_IGNORE_MOUSE_CHAT, ignore);
35
+ },
36
+
37
+ notifyContentHidden() {
38
+ ipcRenderer.send(IPC.CHAT_CONTENT_HIDDEN);
39
+ },
40
+
41
+ notifyContentShown() {
42
+ ipcRenderer.send(IPC.CHAT_CONTENT_SHOWN);
43
+ },
44
+
45
+ onShowBubble(callback) {
46
+ ipcRenderer.removeAllListeners(IPC.SHOW_CHAT_BUBBLE);
47
+ ipcRenderer.on(IPC.SHOW_CHAT_BUBBLE, () => {
48
+ callback();
49
+ });
50
+ },
51
+
52
+ // Chat history
53
+ getChatHistory() {
54
+ return ipcRenderer.invoke(IPC.GET_CHAT_HISTORY);
55
+ },
56
+
57
+ appendChatMessage(role, text, agentId) {
58
+ ipcRenderer.send(IPC.APPEND_CHAT_MESSAGE, role, text, agentId);
59
+ },
60
+
61
+ clearChatHistory() {
62
+ ipcRenderer.send(IPC.CLEAR_CHAT_HISTORY);
63
+ },
64
+
65
+ onChatHistoryCleared(callback) {
66
+ ipcRenderer.removeAllListeners(IPC.CHAT_HISTORY_CLEARED);
67
+ ipcRenderer.on(IPC.CHAT_HISTORY_CLEARED, () => {
68
+ callback();
69
+ });
70
+ },
71
+
72
+ // Idle timeout
73
+ getIdleTimeout() {
74
+ return ipcRenderer.invoke(IPC.GET_IDLE_TIMEOUT);
75
+ },
76
+
77
+ setIdleTimeout(ms) {
78
+ ipcRenderer.send(IPC.SET_IDLE_TIMEOUT, ms);
79
+ },
80
+
81
+ onIdleTimeoutChanged(callback) {
82
+ ipcRenderer.removeAllListeners(IPC.IDLE_TIMEOUT_CHANGED);
83
+ ipcRenderer.on(IPC.IDLE_TIMEOUT_CHANGED, (_event, ms) => {
84
+ callback(ms);
85
+ });
86
+ },
87
+ });
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self' file: data: blob:; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' file: data: blob:; connect-src 'self' file: data: blob:" />
6
+ <title>OpenClaw Chat</title>
7
+ <link rel="stylesheet" href="./styles/chat.css" />
8
+ </head>
9
+ <body>
10
+ <div id="chat-container" class="chat">
11
+ <div id="chat-body" class="chat__body"></div>
12
+ <div id="chat-tail" class="chat__tail"></div>
13
+ </div>
14
+ <script type="module" src="./chat-renderer.js"></script>
15
+ </body>
16
+ </html>
@@ -0,0 +1,355 @@
1
+ //#region dist/renderer/shared/config.js
2
+ const CHAT_IDLE_FADE_MS = 1e4;
3
+ const CHAT_MAX_HISTORY = 200;
4
+ const CHAT_INPUT_MAX_LENGTH = 4096;
5
+ const TYPING_DOT_DELAY_MS = 200;
6
+ const INPUT_MAX_DISPLAY_CHARS = 100;
7
+
8
+ //#endregion
9
+ //#region dist/renderer/renderer/ui/typing-indicator.js
10
+ function createTypingIndicator() {
11
+ const container = document.createElement("div");
12
+ container.className = "typing-indicator";
13
+ container.setAttribute("role", "status");
14
+ container.setAttribute("aria-live", "polite");
15
+ for (let i = 0; i < 3; i++) {
16
+ const dot = document.createElement("span");
17
+ dot.className = "typing-dot";
18
+ dot.style.animationDelay = `${i * TYPING_DOT_DELAY_MS}ms`;
19
+ container.appendChild(dot);
20
+ }
21
+ const srText = document.createElement("span");
22
+ srText.className = "sr-only";
23
+ srText.textContent = "Agent is thinking";
24
+ container.appendChild(srText);
25
+ function setPhase(phase) {
26
+ container.dataset.phase = phase;
27
+ srText.textContent = phase === "thinking" ? "Agent is thinking" : "Agent is working";
28
+ }
29
+ function destroy() {
30
+ container.remove();
31
+ }
32
+ return {
33
+ element: container,
34
+ setPhase,
35
+ destroy
36
+ };
37
+ }
38
+
39
+ //#endregion
40
+ //#region dist/renderer/renderer/ui/icons.js
41
+ const ICONS = {
42
+ drag: `<circle cx="5" cy="4" r="1"/><circle cx="11" cy="4" r="1"/><circle cx="5" cy="8" r="1"/><circle cx="11" cy="8" r="1"/><circle cx="5" cy="12" r="1"/><circle cx="11" cy="12" r="1"/>`,
43
+ chat: `<path d="M2.5 3.5a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-5l-3.5 2.5v-2.5h-.5a1 1 0 0 1-1-1v-6z"/>`,
44
+ settings: `<circle cx="8" cy="8" r="2"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M2.9 2.9l1.4 1.4M11.7 11.7l1.4 1.4M2.9 13.1l1.4-1.4M11.7 4.3l1.4-1.4"/>`,
45
+ send: `<path d="M14 2L7 9M14 2l-5 12-2-5-5-2 12-5z"/>`,
46
+ close: `<path d="M4 4l8 8M12 4l-8 8"/>`
47
+ };
48
+ function createIcon(name, options = {}) {
49
+ const { size = 16, ariaLabel, className } = options;
50
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
51
+ svg.setAttribute("viewBox", "0 0 16 16");
52
+ svg.setAttribute("width", String(size));
53
+ svg.setAttribute("height", String(size));
54
+ svg.setAttribute("fill", name === "drag" ? "currentColor" : "none");
55
+ svg.setAttribute("stroke", "currentColor");
56
+ svg.setAttribute("stroke-width", "1.5");
57
+ svg.setAttribute("stroke-linecap", "round");
58
+ svg.setAttribute("stroke-linejoin", "round");
59
+ if (ariaLabel) {
60
+ svg.setAttribute("aria-label", ariaLabel);
61
+ svg.setAttribute("role", "img");
62
+ } else svg.setAttribute("aria-hidden", "true");
63
+ if (className) svg.setAttribute("class", className);
64
+ svg.innerHTML = ICONS[name];
65
+ return svg;
66
+ }
67
+
68
+ //#endregion
69
+ //#region dist/renderer/renderer/ui/chat-bubble.js
70
+ function createChatBubble(parent, bridge, options) {
71
+ const container = document.createElement("div");
72
+ container.id = "chat-bubble";
73
+ container.className = "chat__bubble";
74
+ const messagesEl = document.createElement("div");
75
+ messagesEl.id = "chat-messages";
76
+ messagesEl.className = "chat__messages";
77
+ messagesEl.setAttribute("role", "log");
78
+ messagesEl.setAttribute("aria-live", "polite");
79
+ messagesEl.setAttribute("aria-label", "Chat messages");
80
+ const inputRow = document.createElement("div");
81
+ inputRow.id = "chat-input-row";
82
+ inputRow.className = "chat__input-row";
83
+ const inputEl = document.createElement("input");
84
+ inputEl.id = "chat-input";
85
+ inputEl.className = "input chat__input";
86
+ inputEl.type = "text";
87
+ inputEl.placeholder = "Send a message...";
88
+ inputEl.maxLength = CHAT_INPUT_MAX_LENGTH;
89
+ inputEl.setAttribute("aria-label", "Message input");
90
+ const charCounter = document.createElement("span");
91
+ charCounter.id = "char-counter";
92
+ charCounter.className = "chat__char-counter";
93
+ charCounter.setAttribute("aria-live", "polite");
94
+ const sendBtn = document.createElement("button");
95
+ sendBtn.id = "send-btn";
96
+ sendBtn.className = "btn btn--icon chat__send";
97
+ sendBtn.type = "button";
98
+ sendBtn.disabled = true;
99
+ sendBtn.setAttribute("aria-label", "Send message");
100
+ sendBtn.appendChild(createIcon("send", { size: 14 }));
101
+ inputRow.appendChild(inputEl);
102
+ inputRow.appendChild(charCounter);
103
+ inputRow.appendChild(sendBtn);
104
+ container.appendChild(messagesEl);
105
+ container.appendChild(inputRow);
106
+ parent.appendChild(container);
107
+ let currentMsgEl = null;
108
+ let currentMsgText = "";
109
+ let currentAgentId;
110
+ let indicator = null;
111
+ let visible = false;
112
+ let idleTimer = null;
113
+ let userScrolled = false;
114
+ let idleTimeoutMs = CHAT_IDLE_FADE_MS;
115
+ function clearTimers() {
116
+ if (idleTimer !== null) {
117
+ clearTimeout(idleTimer);
118
+ idleTimer = null;
119
+ }
120
+ }
121
+ function startIdleTimer() {
122
+ clearTimers();
123
+ if (idleTimeoutMs > 0) idleTimer = setTimeout(() => hide(), idleTimeoutMs);
124
+ }
125
+ function show() {
126
+ visible = true;
127
+ container.style.opacity = "1";
128
+ container.style.pointerEvents = "auto";
129
+ inputEl.focus();
130
+ options?.onVisibilityChange?.(true);
131
+ }
132
+ function hide() {
133
+ visible = false;
134
+ container.style.opacity = "0";
135
+ container.style.pointerEvents = "none";
136
+ options?.onVisibilityChange?.(false);
137
+ }
138
+ function removeStatus() {
139
+ if (indicator) {
140
+ indicator.destroy();
141
+ indicator = null;
142
+ }
143
+ }
144
+ function createStatus(phase) {
145
+ removeStatus();
146
+ indicator = createTypingIndicator();
147
+ indicator.setPhase(phase);
148
+ messagesEl.appendChild(indicator.element);
149
+ autoScroll();
150
+ }
151
+ function autoScroll() {
152
+ if (!userScrolled) messagesEl.scrollTop = messagesEl.scrollHeight;
153
+ }
154
+ function onScroll() {
155
+ userScrolled = !(messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight < 10);
156
+ }
157
+ messagesEl.addEventListener("scroll", onScroll);
158
+ function pruneHistory() {
159
+ while (messagesEl.children.length > CHAT_MAX_HISTORY) messagesEl.removeChild(messagesEl.firstChild);
160
+ }
161
+ function clearMessages() {
162
+ messagesEl.textContent = "";
163
+ currentMsgEl = null;
164
+ currentMsgText = "";
165
+ currentAgentId = void 0;
166
+ removeStatus();
167
+ userScrolled = false;
168
+ }
169
+ function appendMessageToDOM(role, text) {
170
+ const msgDiv = document.createElement("div");
171
+ msgDiv.className = role === "user" ? "message message--user chat-user-msg" : "message message--assistant chat-assistant-msg";
172
+ msgDiv.textContent = text;
173
+ messagesEl.appendChild(msgDiv);
174
+ pruneHistory();
175
+ }
176
+ function sendMessage(text) {
177
+ bridge.sendChat(text);
178
+ const msgDiv = document.createElement("div");
179
+ msgDiv.className = "message message--user chat-user-msg";
180
+ msgDiv.textContent = text;
181
+ messagesEl.appendChild(msgDiv);
182
+ pruneHistory();
183
+ bridge.appendChatMessage?.("user", text);
184
+ inputEl.value = "";
185
+ sendBtn.disabled = true;
186
+ updateCharCounter(0);
187
+ autoScroll();
188
+ }
189
+ function updateCharCounter(len) {
190
+ if (len > INPUT_MAX_DISPLAY_CHARS) {
191
+ charCounter.classList.add("visible", "is-visible");
192
+ charCounter.textContent = `${len}/${CHAT_INPUT_MAX_LENGTH}`;
193
+ if (len >= CHAT_INPUT_MAX_LENGTH) {
194
+ charCounter.classList.remove("warning", "chat__char-counter--warning");
195
+ charCounter.classList.add("error", "chat__char-counter--error");
196
+ } else if (len >= CHAT_INPUT_MAX_LENGTH * .9) {
197
+ charCounter.classList.add("warning", "chat__char-counter--warning");
198
+ charCounter.classList.remove("error", "chat__char-counter--error");
199
+ } else {
200
+ charCounter.classList.remove("warning", "chat__char-counter--warning");
201
+ charCounter.classList.remove("error", "chat__char-counter--error");
202
+ }
203
+ } else {
204
+ charCounter.classList.remove("visible", "is-visible");
205
+ charCounter.classList.remove("warning", "chat__char-counter--warning");
206
+ charCounter.classList.remove("error", "chat__char-counter--error");
207
+ }
208
+ }
209
+ function persistCurrentAssistantMessage() {
210
+ if (currentMsgText.length > 0) {
211
+ bridge.appendChatMessage?.("assistant", currentMsgText, currentAgentId);
212
+ currentMsgText = "";
213
+ currentAgentId = void 0;
214
+ }
215
+ }
216
+ function handleThinking(state) {
217
+ clearTimers();
218
+ persistCurrentAssistantMessage();
219
+ removeStatus();
220
+ createStatus("thinking");
221
+ currentMsgEl = null;
222
+ currentAgentId = state.agentId;
223
+ show();
224
+ }
225
+ function handleSpeaking(state) {
226
+ clearTimers();
227
+ removeStatus();
228
+ if (currentMsgEl === null) {
229
+ currentMsgEl = document.createElement("div");
230
+ currentMsgEl.className = "message message--assistant chat-assistant-msg";
231
+ messagesEl.appendChild(currentMsgEl);
232
+ pruneHistory();
233
+ currentMsgText = "";
234
+ currentAgentId = state.agentId;
235
+ }
236
+ if (state.text) {
237
+ currentMsgEl.textContent = state.text;
238
+ currentMsgText = state.text;
239
+ }
240
+ autoScroll();
241
+ startIdleTimer();
242
+ }
243
+ function handleWorking(_state) {
244
+ clearTimers();
245
+ persistCurrentAssistantMessage();
246
+ removeStatus();
247
+ createStatus("working");
248
+ currentMsgEl = null;
249
+ show();
250
+ }
251
+ function handleIdle(_state) {
252
+ clearTimers();
253
+ persistCurrentAssistantMessage();
254
+ removeStatus();
255
+ currentMsgEl = null;
256
+ startIdleTimer();
257
+ }
258
+ function onInput() {
259
+ const len = inputEl.value.length;
260
+ sendBtn.disabled = len === 0;
261
+ updateCharCounter(len);
262
+ }
263
+ function onKeydown(e) {
264
+ if (e.key === "Escape") {
265
+ hide();
266
+ return;
267
+ }
268
+ if (e.key === "Enter" && !e.shiftKey) {
269
+ e.preventDefault();
270
+ const text = inputEl.value.trim();
271
+ if (text) sendMessage(text);
272
+ }
273
+ }
274
+ function onSendClick() {
275
+ const text = inputEl.value.trim();
276
+ if (text) sendMessage(text);
277
+ }
278
+ inputEl.addEventListener("keydown", onKeydown);
279
+ inputEl.addEventListener("input", onInput);
280
+ sendBtn.addEventListener("click", onSendClick);
281
+ bridge.onChatHistoryCleared?.(() => {
282
+ clearMessages();
283
+ });
284
+ bridge.onIdleTimeoutChanged?.((ms) => {
285
+ idleTimeoutMs = ms;
286
+ });
287
+ bridge.getIdleTimeout?.().then((ms) => {
288
+ idleTimeoutMs = ms;
289
+ });
290
+ function handleAgentState(state) {
291
+ state.phase;
292
+ switch (state.phase) {
293
+ case "thinking":
294
+ handleThinking(state);
295
+ break;
296
+ case "speaking":
297
+ handleSpeaking(state);
298
+ break;
299
+ case "working":
300
+ handleWorking(state);
301
+ break;
302
+ case "idle":
303
+ handleIdle(state);
304
+ break;
305
+ }
306
+ }
307
+ function toggle() {
308
+ if (visible) hide();
309
+ else show();
310
+ }
311
+ async function loadHistory() {
312
+ if (!bridge.getChatHistory) return;
313
+ try {
314
+ const recent = (await bridge.getChatHistory()).messages.slice(-50);
315
+ for (const msg of recent) appendMessageToDOM(msg.role, msg.text);
316
+ autoScroll();
317
+ } catch (err) {
318
+ console.warn("[chat-bubble] Failed to load chat history:", err);
319
+ }
320
+ }
321
+ function destroy() {
322
+ clearTimers();
323
+ removeStatus();
324
+ inputEl.removeEventListener("keydown", onKeydown);
325
+ inputEl.removeEventListener("input", onInput);
326
+ sendBtn.removeEventListener("click", onSendClick);
327
+ messagesEl.removeEventListener("scroll", onScroll);
328
+ container.remove();
329
+ }
330
+ return {
331
+ handleAgentState,
332
+ show,
333
+ hide,
334
+ toggle,
335
+ destroy,
336
+ loadHistory
337
+ };
338
+ }
339
+
340
+ //#endregion
341
+ //#region dist/renderer/renderer/chat-window/chat-renderer.js
342
+ const bridge = window.chatBridge;
343
+ const chatBubble = createChatBubble(document.getElementById("chat-body"), bridge, { onVisibilityChange(visible) {
344
+ if (visible) bridge.notifyContentShown();
345
+ else bridge.notifyContentHidden();
346
+ } });
347
+ chatBubble.loadHistory();
348
+ bridge.onAgentState((state) => {
349
+ chatBubble.handleAgentState(state);
350
+ });
351
+ bridge.onShowBubble(() => {
352
+ chatBubble.show();
353
+ });
354
+
355
+ //#endregion
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Base styles - Reset and foundation
3
+ * Provides consistent cross-browser defaults
4
+ */
5
+
6
+ /* Box-sizing reset */
7
+ *,
8
+ *::before,
9
+ *::after {
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ /* Remove default margins */
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ }
18
+
19
+ /* Core document styles */
20
+ html {
21
+ -webkit-text-size-adjust: 100%;
22
+ -webkit-font-smoothing: antialiased;
23
+ -moz-osx-font-smoothing: grayscale;
24
+ text-rendering: optimizeLegibility;
25
+ }
26
+
27
+ html,
28
+ body {
29
+ width: 100%;
30
+ height: 100%;
31
+ overflow: hidden;
32
+ background: transparent;
33
+ }
34
+
35
+ body {
36
+ font-family: var(--font-sans);
37
+ font-size: var(--font-size-md);
38
+ line-height: var(--line-height-normal);
39
+ color: var(--text-primary);
40
+ }
41
+
42
+ /* Screen reader only utility */
43
+ .sr-only {
44
+ position: absolute;
45
+ width: 1px;
46
+ height: 1px;
47
+ padding: 0;
48
+ margin: -1px;
49
+ overflow: hidden;
50
+ clip: rect(0, 0, 0, 0);
51
+ white-space: nowrap;
52
+ border: 0;
53
+ }
54
+
55
+ /* Remove default button styles */
56
+ button {
57
+ font: inherit;
58
+ color: inherit;
59
+ background: none;
60
+ border: none;
61
+ cursor: pointer;
62
+ }
63
+
64
+ button:disabled {
65
+ cursor: not-allowed;
66
+ }
67
+
68
+ /* Remove default input styles */
69
+ input,
70
+ textarea {
71
+ font: inherit;
72
+ color: inherit;
73
+ background: none;
74
+ border: none;
75
+ }
76
+
77
+ input:focus,
78
+ textarea:focus,
79
+ button:focus {
80
+ outline: none;
81
+ }
82
+
83
+ /* Scrollbar styling */
84
+ ::-webkit-scrollbar {
85
+ width: 6px;
86
+ height: 6px;
87
+ }
88
+
89
+ ::-webkit-scrollbar-track {
90
+ background: transparent;
91
+ }
92
+
93
+ ::-webkit-scrollbar-thumb {
94
+ background: var(--scrollbar-thumb);
95
+ border-radius: 3px;
96
+ }
97
+
98
+ ::-webkit-scrollbar-thumb:hover {
99
+ background: var(--scrollbar-thumb-hover);
100
+ }
101
+
102
+ /* Firefox scrollbar */
103
+ * {
104
+ scrollbar-width: thin;
105
+ scrollbar-color: var(--scrollbar-thumb) transparent;
106
+ }