gitclaw 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +6 -2
- package/dist/composio/adapter.d.ts +26 -0
- package/dist/composio/adapter.js +92 -0
- package/dist/composio/client.d.ts +39 -0
- package/dist/composio/client.js +170 -0
- package/dist/composio/index.d.ts +2 -0
- package/dist/composio/index.js +2 -0
- package/dist/context.d.ts +20 -0
- package/dist/context.js +211 -0
- package/dist/exports.d.ts +2 -0
- package/dist/exports.js +1 -0
- package/dist/index.js +99 -7
- package/dist/learning/reinforcement.d.ts +11 -0
- package/dist/learning/reinforcement.js +91 -0
- package/dist/loader.js +34 -1
- package/dist/sdk.js +5 -1
- package/dist/skills.d.ts +5 -0
- package/dist/skills.js +58 -7
- package/dist/tools/capture-photo.d.ts +3 -0
- package/dist/tools/capture-photo.js +91 -0
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +12 -2
- package/dist/tools/read.js +4 -0
- package/dist/tools/shared.d.ts +20 -0
- package/dist/tools/shared.js +24 -0
- package/dist/tools/skill-learner.d.ts +3 -0
- package/dist/tools/skill-learner.js +358 -0
- package/dist/tools/task-tracker.d.ts +20 -0
- package/dist/tools/task-tracker.js +275 -0
- package/dist/tools/write.js +4 -0
- package/dist/voice/adapter.d.ts +97 -0
- package/dist/voice/adapter.js +30 -0
- package/dist/voice/chat-history.d.ts +8 -0
- package/dist/voice/chat-history.js +121 -0
- package/dist/voice/gemini-live.d.ts +20 -0
- package/dist/voice/gemini-live.js +279 -0
- package/dist/voice/index.d.ts +4 -0
- package/dist/voice/index.js +3 -0
- package/dist/voice/openai-realtime.d.ts +27 -0
- package/dist/voice/openai-realtime.js +291 -0
- package/dist/voice/server.d.ts +2 -0
- package/dist/voice/server.js +2319 -0
- package/dist/voice/ui.html +2556 -0
- package/package.json +21 -7
|
@@ -0,0 +1,2556 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Gitclaw: {{AGENT_NAME}}</title>
|
|
7
|
+
<style>
|
|
8
|
+
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600&display=swap');
|
|
9
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
10
|
+
body {
|
|
11
|
+
background: #0d1117; color: #e6edf3;
|
|
12
|
+
font-family: 'Inter', 'IBM Plex Mono', monospace;
|
|
13
|
+
display: flex; flex-direction: column;
|
|
14
|
+
height: 100vh; overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
.header {
|
|
17
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
18
|
+
padding: 0 20px; height: 40px; min-height: 40px;
|
|
19
|
+
background: #0e1117; border-bottom: 1px solid #21262d;
|
|
20
|
+
}
|
|
21
|
+
.header-left { display: flex; align-items: center; gap: 10px; }
|
|
22
|
+
.header-logo {
|
|
23
|
+
flex-shrink: 0; width: 22px; height: 22px;
|
|
24
|
+
display: grid; grid-template-columns: repeat(10,2.2px); grid-template-rows: repeat(10,2.2px);
|
|
25
|
+
image-rendering: pixelated;
|
|
26
|
+
filter: drop-shadow(0 0 4px rgba(255,79,99,0.9)) drop-shadow(0 0 12px rgba(255,79,99,0.5));
|
|
27
|
+
}
|
|
28
|
+
.header-logo .px { width: 2.2px; height: 2.2px; }
|
|
29
|
+
.header-logo .px-o { background: #12070b; }
|
|
30
|
+
.header-logo .px-f { background: #ff4f63; }
|
|
31
|
+
.header-logo .px-e { background: transparent; }
|
|
32
|
+
.header-left h1 { font-size: 15px; font-weight: 600; font-family: 'IBM Plex Mono', monospace; }
|
|
33
|
+
.header-left h1 span { color: #58a6ff; }
|
|
34
|
+
.header-right { display: flex; align-items: center; gap: 16px; }
|
|
35
|
+
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: #484f58; display: inline-block; }
|
|
36
|
+
.status-dot.connected { background: #3fb950; }
|
|
37
|
+
.status-dot.error { background: #f85149; }
|
|
38
|
+
.status-text { font-size: 11px; color: #8b949e; margin-left: 6px; }
|
|
39
|
+
.view-tabs { display: flex; gap: 2px; }
|
|
40
|
+
.view-tab {
|
|
41
|
+
font-family: inherit; font-size: 11px; padding: 4px 14px;
|
|
42
|
+
background: transparent; border: 1px solid transparent; border-radius: 4px;
|
|
43
|
+
color: #8b949e; cursor: pointer; transition: all 0.15s;
|
|
44
|
+
}
|
|
45
|
+
.view-tab:hover { color: #b1bac4; background: #161b22; }
|
|
46
|
+
.view-tab.active { border-color: #58a6ff; color: #58a6ff; background: rgba(88,166,255,0.08); }
|
|
47
|
+
.main { display: flex; flex: 1; overflow: hidden; min-height: 0; }
|
|
48
|
+
|
|
49
|
+
/* CHAT SIDEBAR */
|
|
50
|
+
.chat-sidebar {
|
|
51
|
+
width: 220px; min-width: 220px; background: #0a0d12; border-right: 1px solid #21262d;
|
|
52
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
53
|
+
transition: width 0.25s cubic-bezier(0.4,0,0.2,1), min-width 0.25s cubic-bezier(0.4,0,0.2,1), opacity 0.2s ease;
|
|
54
|
+
}
|
|
55
|
+
.chat-sidebar.collapsed {
|
|
56
|
+
width: 0; min-width: 0; opacity: 0; pointer-events: none; border-right: none;
|
|
57
|
+
}
|
|
58
|
+
.chat-sidebar-collapse {
|
|
59
|
+
background: none; border: none; color: #484f58; cursor: pointer; padding: 2px;
|
|
60
|
+
display: flex; align-items: center; transition: color 0.15s;
|
|
61
|
+
}
|
|
62
|
+
.chat-sidebar-collapse:hover { color: #b1bac4; }
|
|
63
|
+
.chat-sidebar-collapse svg { width: 16px; height: 16px; }
|
|
64
|
+
.chat-sidebar-edge-tab {
|
|
65
|
+
position: absolute; left: 0; top: 50%; transform: translateY(-50%);
|
|
66
|
+
width: 16px; height: 48px; background: #161b22; border: 1px solid #21262d;
|
|
67
|
+
border-left: none; border-radius: 0 6px 6px 0; cursor: pointer;
|
|
68
|
+
display: none; align-items: center; justify-content: center; color: #484f58;
|
|
69
|
+
z-index: 10; transition: color 0.15s, background 0.15s;
|
|
70
|
+
}
|
|
71
|
+
.chat-sidebar-edge-tab:hover { color: #b1bac4; background: #1c2129; }
|
|
72
|
+
.chat-sidebar-edge-tab svg { width: 12px; height: 12px; }
|
|
73
|
+
.chat-sidebar-edge-tab.visible { display: flex; }
|
|
74
|
+
.chat-sidebar-toggle {
|
|
75
|
+
background: none; border: 1px solid #21262d; border-radius: 4px; color: #8b949e;
|
|
76
|
+
cursor: pointer; font-size: 11px; font-family: 'IBM Plex Mono', monospace;
|
|
77
|
+
padding: 3px 8px; display: flex; align-items: center; gap: 5px; transition: all 0.15s;
|
|
78
|
+
}
|
|
79
|
+
.chat-sidebar-toggle:hover { border-color: #30363d; color: #b1bac4; }
|
|
80
|
+
.chat-sidebar-toggle svg { width: 14px; height: 14px; }
|
|
81
|
+
.chat-sidebar-header {
|
|
82
|
+
padding: 10px 12px; display: flex; align-items: center; justify-content: space-between;
|
|
83
|
+
border-bottom: 1px solid #21262d;
|
|
84
|
+
}
|
|
85
|
+
.chat-sidebar-header span {
|
|
86
|
+
font-size: 11px; color: #8b949e; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
|
|
87
|
+
}
|
|
88
|
+
.new-chat-btn {
|
|
89
|
+
background: #238636; border: none; border-radius: 4px; color: #fff;
|
|
90
|
+
font-family: 'IBM Plex Mono', monospace; font-size: 11px; font-weight: 600;
|
|
91
|
+
padding: 4px 10px; cursor: pointer; transition: background 0.15s;
|
|
92
|
+
}
|
|
93
|
+
.new-chat-btn:hover { background: #2ea043; }
|
|
94
|
+
.chat-list { flex: 1; overflow-y: auto; padding: 4px 0; }
|
|
95
|
+
.chat-item {
|
|
96
|
+
display: flex; align-items: center; gap: 8px; padding: 8px 12px; font-size: 12px;
|
|
97
|
+
font-family: 'IBM Plex Mono', monospace; color: #8b949e; cursor: pointer;
|
|
98
|
+
transition: background 0.1s; border-left: 2px solid transparent;
|
|
99
|
+
}
|
|
100
|
+
.chat-item:hover { background: #161b22; color: #b1bac4; }
|
|
101
|
+
.chat-item.active { background: rgba(88,166,255,0.08); color: #e6edf3; border-left-color: #58a6ff; }
|
|
102
|
+
.chat-item .chat-icon { width: 14px; height: 14px; flex-shrink: 0; opacity: 0.5; }
|
|
103
|
+
.chat-item .chat-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
104
|
+
.chat-item .chat-time { font-size: 10px; color: #484f58; flex-shrink: 0; }
|
|
105
|
+
.chat-item .chat-delete {
|
|
106
|
+
width: 18px; height: 18px; border: none; background: transparent; color: #484f58;
|
|
107
|
+
cursor: pointer; border-radius: 3px; display: flex; align-items: center; justify-content: center;
|
|
108
|
+
opacity: 0; transition: opacity 0.1s; font-size: 13px;
|
|
109
|
+
}
|
|
110
|
+
.chat-item:hover .chat-delete { opacity: 1; }
|
|
111
|
+
.chat-item .chat-delete:hover { background: #21262d; color: #f85149; }
|
|
112
|
+
.branch-indicator {
|
|
113
|
+
padding: 8px 12px; border-top: 1px solid #21262d; font-size: 10px;
|
|
114
|
+
font-family: 'IBM Plex Mono', monospace; color: #484f58; display: flex; align-items: center; gap: 6px;
|
|
115
|
+
}
|
|
116
|
+
.branch-indicator svg { width: 12px; height: 12px; }
|
|
117
|
+
.branch-indicator .branch-name { color: #8b949e; }
|
|
118
|
+
|
|
119
|
+
/* CHAT VIEW */
|
|
120
|
+
.chat-view { display: flex; flex: 1; overflow: hidden; min-height: 0; }
|
|
121
|
+
.chat-view.hidden { display: none; }
|
|
122
|
+
.panel-cam {
|
|
123
|
+
width: 280px; min-width: 280px; border-right: 1px solid #21262d;
|
|
124
|
+
display: flex; flex-direction: column; padding: 16px; gap: 14px; background: #0e1117;
|
|
125
|
+
}
|
|
126
|
+
.camera-container {
|
|
127
|
+
position: relative; background: #161b22; border: 1px solid #21262d;
|
|
128
|
+
border-radius: 6px; overflow: hidden; aspect-ratio: 4/3;
|
|
129
|
+
}
|
|
130
|
+
.camera-container video { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
131
|
+
.camera-container .camera-off {
|
|
132
|
+
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #484f58; font-size: 12px;
|
|
133
|
+
}
|
|
134
|
+
.camera-container canvas { display: none; }
|
|
135
|
+
.controls { display: flex; gap: 8px; }
|
|
136
|
+
.ctrl-btn {
|
|
137
|
+
flex: 1; padding: 10px; border-radius: 6px; background: #161b22; border: 1px solid #21262d;
|
|
138
|
+
color: #8b949e; font-family: 'IBM Plex Mono', monospace; font-size: 11px;
|
|
139
|
+
cursor: pointer; transition: all 0.15s;
|
|
140
|
+
display: flex; align-items: center; justify-content: center; gap: 6px;
|
|
141
|
+
}
|
|
142
|
+
.ctrl-btn:hover { border-color: #30363d; color: #b1bac4; }
|
|
143
|
+
.ctrl-btn.active { border-color: #58a6ff; color: #58a6ff; background: rgba(88,166,255,0.08); }
|
|
144
|
+
.ctrl-btn svg { width: 14px; height: 14px; }
|
|
145
|
+
/* Agent Vitals */
|
|
146
|
+
.agent-vitals {
|
|
147
|
+
flex: 1; display: flex; flex-direction: column; gap: 0;
|
|
148
|
+
background: #0a0e14; border: 1px solid #21262d; border-radius: 8px; overflow: hidden;
|
|
149
|
+
font-family: 'IBM Plex Mono', monospace; position: relative;
|
|
150
|
+
}
|
|
151
|
+
.vitals-header {
|
|
152
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
153
|
+
padding: 8px 12px; border-bottom: 1px solid #161b22;
|
|
154
|
+
}
|
|
155
|
+
.vitals-title { font-size: 9px; text-transform: uppercase; letter-spacing: 1.5px; color: #3fb950; font-weight: 600; }
|
|
156
|
+
.vitals-status-dot { width: 6px; height: 6px; border-radius: 50%; background: #3fb950; animation: vitalPulse 1.5s ease-in-out infinite; }
|
|
157
|
+
@keyframes vitalPulse { 0%,100% { opacity: 1; box-shadow: 0 0 4px #3fb950; } 50% { opacity: 0.4; box-shadow: none; } }
|
|
158
|
+
.vitals-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: #161b22; flex: 1; }
|
|
159
|
+
.vital-cell {
|
|
160
|
+
background: #0a0e14; padding: 8px 10px; display: flex; flex-direction: column; gap: 4px;
|
|
161
|
+
position: relative; overflow: hidden;
|
|
162
|
+
}
|
|
163
|
+
.vital-label { font-size: 8px; text-transform: uppercase; letter-spacing: 1px; color: #484f58; }
|
|
164
|
+
.vital-value { font-size: 16px; font-weight: 700; line-height: 1; }
|
|
165
|
+
.vital-unit { font-size: 8px; color: #484f58; font-weight: 400; }
|
|
166
|
+
.vital-bar { height: 2px; border-radius: 1px; background: #161b22; margin-top: 4px; }
|
|
167
|
+
.vital-bar-fill { height: 100%; border-radius: 1px; transition: width 0.8s ease; }
|
|
168
|
+
.vital-cell.cpu .vital-value { color: #58a6ff; }
|
|
169
|
+
.vital-cell.cpu .vital-bar-fill { background: #58a6ff; }
|
|
170
|
+
.vital-cell.mem .vital-value { color: #f0883e; }
|
|
171
|
+
.vital-cell.mem .vital-bar-fill { background: #f0883e; }
|
|
172
|
+
.vital-cell.tokens .vital-value { color: #d2a8ff; }
|
|
173
|
+
.vital-cell.tokens .vital-bar-fill { background: #d2a8ff; }
|
|
174
|
+
.vital-cell.uptime .vital-value { color: #3fb950; }
|
|
175
|
+
.vitals-wave {
|
|
176
|
+
height: 32px; padding: 0 10px; border-top: 1px solid #161b22;
|
|
177
|
+
display: flex; align-items: center; gap: 8px;
|
|
178
|
+
}
|
|
179
|
+
.vitals-wave canvas { width: 100%; height: 24px; }
|
|
180
|
+
.vitals-wave-label { font-size: 8px; color: #f85149; text-transform: uppercase; letter-spacing: 1px; flex-shrink: 0; }
|
|
181
|
+
.vital-cell.wide { grid-column: 1 / -1; }
|
|
182
|
+
|
|
183
|
+
.panel-right { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; background: #0d1117; overflow: hidden; position: relative; }
|
|
184
|
+
.conversation {
|
|
185
|
+
flex: 1; overflow-y: auto; padding: 16px 20px; font-size: 12px; line-height: 1.8;
|
|
186
|
+
font-family: 'IBM Plex Mono', monospace;
|
|
187
|
+
}
|
|
188
|
+
.conversation:empty::before { content: "Conversation will appear here..."; color: #484f58; }
|
|
189
|
+
.conv-msg { margin-bottom: 3px; animation: fade-in 0.2s ease-out; }
|
|
190
|
+
.conv-msg.user { color: #b1bac4; }
|
|
191
|
+
.conv-msg.user .label { color: #58a6ff; font-weight: 600; }
|
|
192
|
+
.conv-msg.telegram { border-left: 2px solid #2AABEE; padding-left: 8px; }
|
|
193
|
+
.conv-msg.telegram .label.tg { color: #2AABEE; }
|
|
194
|
+
.conv-msg.assistant { color: #e6edf3; }
|
|
195
|
+
.conv-msg.assistant .label { color: #3fb950; font-weight: 600; }
|
|
196
|
+
.conv-msg.tool { color: #d29922; }
|
|
197
|
+
.conv-msg.agent-working { display: flex; align-items: center; gap: 10px; height: 32px; overflow: hidden; }
|
|
198
|
+
.agent-working-spinner {
|
|
199
|
+
width: 18px; height: 18px; flex-shrink: 0;
|
|
200
|
+
border: 2px solid rgba(255,79,99,0.2); border-top-color: #ff4f63;
|
|
201
|
+
border-radius: 50%; animation: agentSpin 0.8s linear infinite;
|
|
202
|
+
}
|
|
203
|
+
@keyframes agentSpin { to { transform: rotate(360deg); } }
|
|
204
|
+
.agent-working-text { font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
205
|
+
.agent-working-verb { color: #8b949e; font-style: italic; animation: verbPulse 2s ease-in-out infinite; }
|
|
206
|
+
.agent-working-name { color: #ff4f63; font-weight: 600; }
|
|
207
|
+
.agent-working-sep { color: #e6edf3; font-weight: 600; }
|
|
208
|
+
.agent-working-query { color: #8b949e; }
|
|
209
|
+
.conv-msg.memory-saving { display: flex; align-items: center; gap: 8px; height: 28px; overflow: hidden; }
|
|
210
|
+
.memory-saving-spinner {
|
|
211
|
+
width: 14px; height: 14px; flex-shrink: 0;
|
|
212
|
+
border: 2px solid rgba(163,113,247,0.2); border-top-color: #a371f7;
|
|
213
|
+
border-radius: 50%; animation: agentSpin 1.2s linear infinite;
|
|
214
|
+
}
|
|
215
|
+
.memory-saving-text { font-size: 11px; color: #a371f7; font-style: italic; animation: verbPulse 2s ease-in-out infinite; }
|
|
216
|
+
.memory-saving-detail { font-size: 10px; color: #8b949e; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
217
|
+
.conv-msg.tool-call { color: #d2a8ff; font-size: 11px; }
|
|
218
|
+
.conv-msg.tool-call .label { color: #bc8cff; font-weight: 600; }
|
|
219
|
+
.conv-msg.tool-result { color: #7ee787; font-size: 11px; }
|
|
220
|
+
.conv-msg.tool-result.error { color: #f85149; }
|
|
221
|
+
.conv-msg.tool-result .label { font-weight: 600; }
|
|
222
|
+
.tool-activity { margin-bottom: 3px; overflow: hidden; height: 32px; display: flex; align-items: center; gap: 8px; }
|
|
223
|
+
.tool-activity .tool-sprite {
|
|
224
|
+
flex-shrink: 0; width: 24px; height: 24px;
|
|
225
|
+
display: grid; grid-template-columns: repeat(10,2.4px); grid-template-rows: repeat(10,2.4px);
|
|
226
|
+
image-rendering: pixelated;
|
|
227
|
+
animation: spriteFloat 1.8s ease-in-out infinite, spriteGlow 1.4s ease-in-out infinite;
|
|
228
|
+
}
|
|
229
|
+
.tool-activity .tool-sprite .px { width: 2.4px; height: 2.4px; }
|
|
230
|
+
.tool-activity .tool-sprite .px-o { background: #12070b; }
|
|
231
|
+
.tool-activity .tool-sprite .px-f { background: #ff4f63; animation: spriteColor 2.8s ease-in-out infinite; }
|
|
232
|
+
.tool-activity .tool-sprite .px-e { background: transparent; }
|
|
233
|
+
.tool-activity .tool-verb { color: #8b949e; font-size: 11px; font-style: italic; white-space: nowrap; flex-shrink: 0; animation: verbPulse 2s ease-in-out infinite; }
|
|
234
|
+
.tool-activity .tool-activity-body { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; line-height: 32px; }
|
|
235
|
+
.tool-activity .tool-activity-body .tool-call,
|
|
236
|
+
.tool-activity .tool-activity-body .tool-result { display: inline; animation: toolFadeIn 0.2s ease-out; }
|
|
237
|
+
.tool-activity .tool-call,
|
|
238
|
+
.tool-activity .tool-result { color: #d2a8ff; font-size: 11px; }
|
|
239
|
+
.tool-activity .tool-call .label { color: #bc8cff; font-weight: 600; }
|
|
240
|
+
.tool-activity .tool-call .skill-label { color: #f0883e; }
|
|
241
|
+
.tool-activity .tool-call .skill-name { color: #ffa657; font-weight: 500; }
|
|
242
|
+
.tool-activity .tool-call .tool-args { display: inline; margin: 0; font-size: 11px; }
|
|
243
|
+
.tool-activity .tool-result { color: #7ee787; }
|
|
244
|
+
.tool-activity .tool-result.error { color: #f85149; }
|
|
245
|
+
.tool-activity .tool-result .label { font-weight: 600; }
|
|
246
|
+
.tool-activity .tool-result.thinking-fallback .label { color: #8b949e; font-style: italic; font-weight: 400; animation: verbPulse 2s ease-in-out infinite; }
|
|
247
|
+
.tool-activity .tool-result .tool-content { display: inline; margin: 0; padding: 0; background: none; font-size: 11px; max-height: none; overflow: hidden; }
|
|
248
|
+
.tool-activity-summary { cursor: pointer; color: #8b949e; font-size: 11px; line-height: 32px; }
|
|
249
|
+
.tool-activity-summary:hover { color: #c9d1d9; }
|
|
250
|
+
.tool-summary-toggle { user-select: none; }
|
|
251
|
+
.tool-activity.expanded { height: auto; }
|
|
252
|
+
.tool-activity.expanded .tool-activity-body { overflow: visible; white-space: normal; }
|
|
253
|
+
@keyframes toolFadeIn {
|
|
254
|
+
from { opacity: 0; transform: translateY(3px); }
|
|
255
|
+
to { opacity: 1; transform: translateY(0); }
|
|
256
|
+
}
|
|
257
|
+
@keyframes toolSummaryIn {
|
|
258
|
+
from { opacity: 0; }
|
|
259
|
+
to { opacity: 1; }
|
|
260
|
+
}
|
|
261
|
+
@keyframes spriteFloat {
|
|
262
|
+
0%, 100% { transform: translateY(0) scale(1); }
|
|
263
|
+
50% { transform: translateY(-3px) scale(1.05); }
|
|
264
|
+
}
|
|
265
|
+
@keyframes spriteGlow {
|
|
266
|
+
0%, 100% { filter: drop-shadow(0 0 2px rgba(255,79,99,0.6)) drop-shadow(0 0 5px rgba(255,79,99,0.3)); }
|
|
267
|
+
50% { filter: drop-shadow(0 0 5px rgba(255,79,99,1)) drop-shadow(0 0 10px rgba(255,79,99,0.6)); }
|
|
268
|
+
}
|
|
269
|
+
@keyframes spriteColor {
|
|
270
|
+
0%, 100% { background: #ff4f63; }
|
|
271
|
+
33% { background: #ff6b7a; }
|
|
272
|
+
66% { background: #ff2040; }
|
|
273
|
+
}
|
|
274
|
+
@keyframes verbPulse {
|
|
275
|
+
0%, 100% { opacity: 0.6; }
|
|
276
|
+
50% { opacity: 1; }
|
|
277
|
+
}
|
|
278
|
+
.conv-msg.thinking { color: #6e7681; font-size: 11px; font-style: italic; }
|
|
279
|
+
.conv-msg .tool-args { color: #8b949e; font-size: 10px; margin-top: 2px; word-break: break-all; max-height: 60px; overflow: hidden; }
|
|
280
|
+
.conv-msg .tool-content { color: #8b949e; font-size: 10px; margin-top: 2px; white-space: pre-wrap; max-height: 80px; overflow-y: auto; background: #161b22; border-radius: 4px; padding: 4px 8px; }
|
|
281
|
+
.conv-msg.system { color: #8b949e; font-style: italic; }
|
|
282
|
+
.input-bar { display: flex; padding: 12px 16px; border-top: 1px solid #21262d; gap: 8px; }
|
|
283
|
+
.input-bar input {
|
|
284
|
+
flex: 1; background: #161b22; border: 1px solid #21262d; border-radius: 6px;
|
|
285
|
+
padding: 10px 14px; color: #e6edf3; font-family: 'IBM Plex Mono', monospace; font-size: 13px; outline: none;
|
|
286
|
+
}
|
|
287
|
+
.input-bar input:focus { border-color: #58a6ff; }
|
|
288
|
+
.input-bar input::placeholder { color: #484f58; }
|
|
289
|
+
.input-bar button {
|
|
290
|
+
background: #238636; border: none; border-radius: 6px; color: #fff;
|
|
291
|
+
font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 600; padding: 10px 18px; cursor: pointer;
|
|
292
|
+
}
|
|
293
|
+
.input-bar button:hover { background: #2ea043; }
|
|
294
|
+
.attach-btn {
|
|
295
|
+
background: transparent; border: 1px solid #21262d; border-radius: 6px; color: #8b949e;
|
|
296
|
+
font-size: 16px; padding: 6px 10px; cursor: pointer; display: flex; align-items: center;
|
|
297
|
+
transition: border-color 0.15s, color 0.15s;
|
|
298
|
+
}
|
|
299
|
+
.attach-btn:hover { border-color: #58a6ff; color: #58a6ff; }
|
|
300
|
+
.attach-btn svg { width: 18px; height: 18px; }
|
|
301
|
+
.file-preview-bar {
|
|
302
|
+
display: none; padding: 6px 16px; border-top: 1px solid #21262d; gap: 6px; flex-wrap: wrap;
|
|
303
|
+
background: #0d1117;
|
|
304
|
+
}
|
|
305
|
+
.file-preview-bar.active { display: flex; }
|
|
306
|
+
.file-chip {
|
|
307
|
+
display: flex; align-items: center; gap: 6px; background: #161b22; border: 1px solid #21262d;
|
|
308
|
+
border-radius: 4px; padding: 4px 8px; font-size: 11px; color: #8b949e;
|
|
309
|
+
font-family: 'IBM Plex Mono', monospace;
|
|
310
|
+
}
|
|
311
|
+
.file-chip img { max-height: 32px; max-width: 48px; border-radius: 2px; }
|
|
312
|
+
.file-chip .remove-file {
|
|
313
|
+
background: none; border: none; color: #484f58; cursor: pointer; font-size: 14px;
|
|
314
|
+
padding: 0 2px; line-height: 1;
|
|
315
|
+
}
|
|
316
|
+
.file-chip .remove-file:hover { color: #f85149; }
|
|
317
|
+
.drop-overlay {
|
|
318
|
+
display: none; position: absolute; inset: 0; background: rgba(88,166,255,0.08);
|
|
319
|
+
border: 2px dashed #58a6ff; border-radius: 8px; z-index: 100;
|
|
320
|
+
align-items: center; justify-content: center; font-size: 14px; color: #58a6ff;
|
|
321
|
+
font-family: 'IBM Plex Mono', monospace; pointer-events: none;
|
|
322
|
+
}
|
|
323
|
+
.drop-overlay.active { display: flex; }
|
|
324
|
+
|
|
325
|
+
/* Audit mode toggle in header */
|
|
326
|
+
.audit-toggle {
|
|
327
|
+
display: flex; align-items: center; gap: 6px; cursor: pointer;
|
|
328
|
+
font-size: 11px; color: #8b949e; font-family: 'IBM Plex Mono', monospace;
|
|
329
|
+
padding: 3px 10px; border-radius: 4px; border: 1px solid #21262d;
|
|
330
|
+
background: transparent; transition: all 0.2s ease; user-select: none;
|
|
331
|
+
}
|
|
332
|
+
.audit-toggle:hover { border-color: #30363d; color: #b1bac4; }
|
|
333
|
+
.audit-toggle.active { border-color: #3fb950; color: #3fb950; background: rgba(63,185,80,0.08); }
|
|
334
|
+
.audit-toggle .audit-dot {
|
|
335
|
+
width: 6px; height: 6px; border-radius: 50%; background: #484f58;
|
|
336
|
+
transition: background 0.2s, box-shadow 0.2s;
|
|
337
|
+
}
|
|
338
|
+
.audit-toggle.active .audit-dot {
|
|
339
|
+
background: #3fb950;
|
|
340
|
+
box-shadow: 0 0 6px rgba(63,185,80,0.6);
|
|
341
|
+
}
|
|
342
|
+
@keyframes audit-dot-pulse {
|
|
343
|
+
0%,100% { box-shadow: 0 0 6px rgba(63,185,80,0.6); }
|
|
344
|
+
50% { box-shadow: 0 0 12px rgba(63,185,80,0.9); }
|
|
345
|
+
}
|
|
346
|
+
.audit-toggle.active.recording .audit-dot {
|
|
347
|
+
animation: audit-dot-pulse 1.5s ease-in-out infinite;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Files panel (right side of chat) — contains tree + inline diff viewer */
|
|
351
|
+
.files-panel {
|
|
352
|
+
width: 40vw; min-width: 200px; background: #0e1117; border-left: 1px solid #21262d;
|
|
353
|
+
display: flex; flex-direction: column; overflow: hidden; position: relative;
|
|
354
|
+
transition: opacity 0.25s ease;
|
|
355
|
+
}
|
|
356
|
+
.files-panel.collapsed { min-width: 0; }
|
|
357
|
+
.files-panel.collapsed {
|
|
358
|
+
width: 0 !important; min-width: 0 !important; opacity: 0; pointer-events: none; border-left: none;
|
|
359
|
+
}
|
|
360
|
+
/* Resize handles */
|
|
361
|
+
.resize-handle-x {
|
|
362
|
+
position: absolute; left: -3px; top: 0; bottom: 0; width: 6px;
|
|
363
|
+
cursor: col-resize; z-index: 20;
|
|
364
|
+
}
|
|
365
|
+
.resize-handle-x:hover, .resize-handle-x.active { background: rgba(88,166,255,0.3); }
|
|
366
|
+
.resize-handle-y {
|
|
367
|
+
height: 6px; cursor: row-resize; flex-shrink: 0; position: relative;
|
|
368
|
+
}
|
|
369
|
+
.resize-handle-y:hover, .resize-handle-y.active { background: rgba(88,166,255,0.3); }
|
|
370
|
+
.resize-handle-y::after {
|
|
371
|
+
content: ''; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
|
|
372
|
+
width: 24px; height: 2px; background: #30363d; border-radius: 1px;
|
|
373
|
+
}
|
|
374
|
+
.files-panel-header {
|
|
375
|
+
padding: 10px 16px; font-size: 11px; color: #8b949e; font-weight: 600;
|
|
376
|
+
text-transform: uppercase; letter-spacing: 0.5px;
|
|
377
|
+
display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #21262d;
|
|
378
|
+
flex-shrink: 0;
|
|
379
|
+
}
|
|
380
|
+
.files-panel-header .fp-title { display: flex; align-items: center; gap: 8px; }
|
|
381
|
+
.files-panel-header .fp-count {
|
|
382
|
+
font-size: 9px; background: rgba(63,185,80,0.15); color: #3fb950;
|
|
383
|
+
padding: 1px 6px; border-radius: 8px; font-weight: 500; min-width: 18px; text-align: center;
|
|
384
|
+
transition: opacity 0.3s, transform 0.3s;
|
|
385
|
+
}
|
|
386
|
+
.files-panel-header .fp-count.hidden { opacity: 0; transform: scale(0.5); pointer-events: none; }
|
|
387
|
+
.files-panel-header button {
|
|
388
|
+
background: none; border: none; color: #8b949e; cursor: pointer;
|
|
389
|
+
font-size: 14px; padding: 2px 6px; border-radius: 3px; font-family: inherit;
|
|
390
|
+
}
|
|
391
|
+
.files-panel-header button:hover { color: #58a6ff; background: rgba(88,166,255,0.08); }
|
|
392
|
+
|
|
393
|
+
/* File tree area */
|
|
394
|
+
.files-panel .file-tree { flex: 1; overflow-y: auto; padding: 4px 0; min-height: 80px; }
|
|
395
|
+
|
|
396
|
+
/* File tree change animations */
|
|
397
|
+
@keyframes file-pulse {
|
|
398
|
+
0% { background: transparent; box-shadow: none; }
|
|
399
|
+
15% { background: rgba(63,185,80,0.18); box-shadow: inset 0 0 12px rgba(63,185,80,0.15), 0 0 8px rgba(63,185,80,0.1); }
|
|
400
|
+
100% { background: transparent; box-shadow: none; }
|
|
401
|
+
}
|
|
402
|
+
@keyframes file-slide-in {
|
|
403
|
+
0% { opacity: 0; transform: translateX(12px); }
|
|
404
|
+
100% { opacity: 1; transform: translateX(0); }
|
|
405
|
+
}
|
|
406
|
+
.ft-item.changed, summary.changed { animation: file-pulse 2s ease-out; }
|
|
407
|
+
.ft-item.new-file, summary.new-file { animation: file-slide-in 0.3s ease-out, file-pulse 2s ease-out 0.3s; }
|
|
408
|
+
.ft-item { position: relative; }
|
|
409
|
+
.ft-badge {
|
|
410
|
+
margin-left: auto; font-size: 9px; font-weight: 600; padding: 1px 5px;
|
|
411
|
+
border-radius: 3px; letter-spacing: 0.3px; flex-shrink: 0;
|
|
412
|
+
}
|
|
413
|
+
.ft-badge.pop { animation: badge-pop 0.35s cubic-bezier(0.34,1.56,0.64,1); }
|
|
414
|
+
@keyframes badge-pop { 0% { opacity: 0; transform: scale(0.3); } 100% { opacity: 1; transform: scale(1); } }
|
|
415
|
+
.ft-badge.edited { background: rgba(63,185,80,0.15); color: #3fb950; }
|
|
416
|
+
.ft-badge.edited.pop { box-shadow: 0 0 8px rgba(63,185,80,0.4); }
|
|
417
|
+
.ft-badge.new { background: rgba(88,166,255,0.15); color: #58a6ff; }
|
|
418
|
+
.ft-badge.new.pop { box-shadow: 0 0 8px rgba(88,166,255,0.4); }
|
|
419
|
+
.ft-item .ft-gutter {
|
|
420
|
+
position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
|
|
421
|
+
border-radius: 0 2px 2px 0;
|
|
422
|
+
}
|
|
423
|
+
.ft-gutter.edited { background: #3fb950; }
|
|
424
|
+
.ft-gutter.new { background: #58a6ff; }
|
|
425
|
+
|
|
426
|
+
/* Inline diff viewer (inside files-panel, below tree) */
|
|
427
|
+
.diff-viewer {
|
|
428
|
+
background: #0a0d12;
|
|
429
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
430
|
+
height: 0; opacity: 0; flex-shrink: 0;
|
|
431
|
+
transition: height 0.35s cubic-bezier(0.4,0,0.2,1), opacity 0.25s ease;
|
|
432
|
+
}
|
|
433
|
+
.diff-viewer.open {
|
|
434
|
+
height: 280px; opacity: 1;
|
|
435
|
+
}
|
|
436
|
+
.diff-viewer-header {
|
|
437
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
438
|
+
padding: 6px 12px; border-bottom: 1px solid #161b22; flex-shrink: 0;
|
|
439
|
+
}
|
|
440
|
+
.diff-viewer-header .dv-left { display: flex; align-items: center; gap: 8px; overflow: hidden; }
|
|
441
|
+
.diff-viewer-header .dv-path {
|
|
442
|
+
font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: #b1bac4;
|
|
443
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
444
|
+
}
|
|
445
|
+
.diff-viewer-header .dv-status {
|
|
446
|
+
font-size: 8px; font-weight: 700; padding: 2px 6px; border-radius: 3px;
|
|
447
|
+
flex-shrink: 0; text-transform: uppercase; letter-spacing: 0.5px;
|
|
448
|
+
}
|
|
449
|
+
.dv-status.edited { background: rgba(63,185,80,0.15); color: #3fb950; }
|
|
450
|
+
.dv-status.viewing { background: rgba(88,166,255,0.1); color: #58a6ff; }
|
|
451
|
+
.diff-viewer-header button {
|
|
452
|
+
background: none; border: none; color: #484f58; cursor: pointer;
|
|
453
|
+
font-size: 14px; padding: 2px 6px; border-radius: 3px;
|
|
454
|
+
}
|
|
455
|
+
.diff-viewer-header button:hover { color: #e6edf3; background: #161b22; }
|
|
456
|
+
/* Auto-close countdown bar */
|
|
457
|
+
.dv-countdown {
|
|
458
|
+
height: 2px; background: #3fb950; transition: width 2s linear;
|
|
459
|
+
flex-shrink: 0;
|
|
460
|
+
}
|
|
461
|
+
.dv-countdown.done { width: 0 !important; }
|
|
462
|
+
.diff-viewer-content {
|
|
463
|
+
flex: 1; overflow: auto; padding: 0; margin: 0; position: relative;
|
|
464
|
+
}
|
|
465
|
+
.dv-md-toggle { display: flex; align-items: center; }
|
|
466
|
+
.dv-md-toggle.active { color: #58a6ff !important; }
|
|
467
|
+
.dv-markdown {
|
|
468
|
+
padding: 16px 20px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
469
|
+
font-size: 13px; line-height: 1.6; color: #e6edf3;
|
|
470
|
+
}
|
|
471
|
+
.dv-markdown.hidden, .dv-image.hidden { display: none; }
|
|
472
|
+
.dv-image {
|
|
473
|
+
display: flex; align-items: center; justify-content: center; padding: 20px;
|
|
474
|
+
flex: 1; background: #0a0d12; overflow: auto;
|
|
475
|
+
}
|
|
476
|
+
.dv-image img {
|
|
477
|
+
max-width: 100%; max-height: 100%; object-fit: contain;
|
|
478
|
+
border-radius: 6px; border: 1px solid #21262d;
|
|
479
|
+
background: repeating-conic-gradient(#1c2129 0% 25%, #161b22 0% 50%) 0 0 / 16px 16px;
|
|
480
|
+
}
|
|
481
|
+
.dv-image .img-meta {
|
|
482
|
+
position: absolute; bottom: 8px; right: 12px; font-size: 10px; color: #484f58;
|
|
483
|
+
font-family: 'IBM Plex Mono', monospace;
|
|
484
|
+
}
|
|
485
|
+
.dv-markdown h1 { font-size: 1.6em; font-weight: 600; border-bottom: 1px solid #21262d; padding-bottom: 6px; margin: 16px 0 8px; color: #f0f6fc; }
|
|
486
|
+
.dv-markdown h2 { font-size: 1.3em; font-weight: 600; border-bottom: 1px solid #21262d; padding-bottom: 4px; margin: 14px 0 6px; color: #f0f6fc; }
|
|
487
|
+
.dv-markdown h3 { font-size: 1.1em; font-weight: 600; margin: 12px 0 4px; color: #f0f6fc; }
|
|
488
|
+
.dv-markdown h4, .dv-markdown h5, .dv-markdown h6 { font-size: 1em; font-weight: 600; margin: 10px 0 4px; color: #e6edf3; }
|
|
489
|
+
.dv-markdown p { margin: 0 0 10px; }
|
|
490
|
+
.dv-markdown ul, .dv-markdown ol { padding-left: 24px; margin: 0 0 10px; }
|
|
491
|
+
.dv-markdown li { margin: 2px 0; }
|
|
492
|
+
.dv-markdown code {
|
|
493
|
+
background: rgba(110,118,129,0.15); padding: 2px 5px; border-radius: 3px;
|
|
494
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace; font-size: 0.9em;
|
|
495
|
+
}
|
|
496
|
+
.dv-markdown pre {
|
|
497
|
+
background: #161b22; border: 1px solid #21262d; border-radius: 6px;
|
|
498
|
+
padding: 12px 16px; overflow-x: auto; margin: 0 0 12px;
|
|
499
|
+
}
|
|
500
|
+
.dv-markdown pre code { background: none; padding: 0; font-size: 12px; }
|
|
501
|
+
.dv-markdown blockquote {
|
|
502
|
+
border-left: 3px solid #30363d; padding: 4px 16px; margin: 0 0 10px; color: #8b949e;
|
|
503
|
+
}
|
|
504
|
+
.dv-markdown a { color: #58a6ff; text-decoration: none; }
|
|
505
|
+
.dv-markdown a:hover { text-decoration: underline; }
|
|
506
|
+
.dv-markdown hr { border: none; border-top: 1px solid #21262d; margin: 16px 0; }
|
|
507
|
+
.dv-markdown strong { color: #f0f6fc; }
|
|
508
|
+
.dv-markdown table { border-collapse: collapse; margin: 0 0 12px; width: 100%; }
|
|
509
|
+
.dv-markdown th, .dv-markdown td { border: 1px solid #21262d; padding: 6px 12px; text-align: left; }
|
|
510
|
+
.dv-markdown th { background: #161b22; font-weight: 600; }
|
|
511
|
+
.dv-markdown img { max-width: 100%; border-radius: 6px; }
|
|
512
|
+
.diff-viewer-content pre {
|
|
513
|
+
margin: 0; padding: 8px 0; font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
514
|
+
font-size: 11px; line-height: 1.55; color: #e6edf3; white-space: pre;
|
|
515
|
+
counter-reset: line;
|
|
516
|
+
}
|
|
517
|
+
.diff-viewer-content .dv-line {
|
|
518
|
+
display: block; padding: 0 10px 0 52px; position: relative; min-height: 1.55em;
|
|
519
|
+
}
|
|
520
|
+
.diff-viewer-content .dv-line::before {
|
|
521
|
+
content: counter(line); counter-increment: line;
|
|
522
|
+
position: absolute; left: 0; width: 36px; text-align: right; padding-right: 8px;
|
|
523
|
+
color: #484f58; font-size: 10px; user-select: none;
|
|
524
|
+
}
|
|
525
|
+
.diff-viewer-content .dv-line:hover { background: rgba(88,166,255,0.04); }
|
|
526
|
+
.diff-viewer-content .dv-line .dv-gutter {
|
|
527
|
+
position: absolute; left: 40px; top: 2px; bottom: 2px; width: 3px;
|
|
528
|
+
border-radius: 2px;
|
|
529
|
+
}
|
|
530
|
+
.dv-gutter.g-added { background: #3fb950; box-shadow: 0 0 6px rgba(63,185,80,0.5); }
|
|
531
|
+
.dv-gutter.g-modified { background: #d29922; box-shadow: 0 0 6px rgba(210,153,34,0.5); }
|
|
532
|
+
@keyframes dv-line-flash {
|
|
533
|
+
0% { background: rgba(63,185,80,0.3); box-shadow: inset 0 0 20px rgba(63,185,80,0.15); }
|
|
534
|
+
40% { background: rgba(63,185,80,0.12); box-shadow: inset 0 0 8px rgba(63,185,80,0.06); }
|
|
535
|
+
100% { background: transparent; box-shadow: none; }
|
|
536
|
+
}
|
|
537
|
+
.dv-line.line-changed { animation: dv-line-flash 2s ease-out forwards; border-left: 2px solid rgba(210,153,34,0.4); padding-left: 50px; }
|
|
538
|
+
.dv-line.line-added { animation: dv-line-flash 2s ease-out forwards; border-left: 2px solid rgba(63,185,80,0.4); padding-left: 50px; }
|
|
539
|
+
@keyframes dv-line-enter {
|
|
540
|
+
0% { opacity: 0; transform: translateX(6px); }
|
|
541
|
+
100% { opacity: 1; transform: translateX(0); }
|
|
542
|
+
}
|
|
543
|
+
.dv-line.line-enter { animation: dv-line-enter 0.15s ease-out forwards; }
|
|
544
|
+
|
|
545
|
+
/* FILES VIEW (disabled)
|
|
546
|
+
.files-view { display: flex; flex: 1; overflow: hidden; }
|
|
547
|
+
.files-view.hidden { display: none; }
|
|
548
|
+
.activity-bar {
|
|
549
|
+
width: 48px; min-width: 48px; background: #0a0d12; border-right: 1px solid #21262d;
|
|
550
|
+
display: flex; flex-direction: column; align-items: center; padding: 8px 0; gap: 4px;
|
|
551
|
+
}
|
|
552
|
+
.activity-btn {
|
|
553
|
+
width: 36px; height: 36px; border-radius: 6px; border: none; background: transparent;
|
|
554
|
+
color: #8b949e; cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
555
|
+
transition: all 0.15s; position: relative;
|
|
556
|
+
}
|
|
557
|
+
.activity-btn:hover { color: #e6edf3; background: #161b22; }
|
|
558
|
+
.activity-btn.active { color: #e6edf3; }
|
|
559
|
+
.activity-btn.active::before {
|
|
560
|
+
content: ''; position: absolute; left: 0; top: 6px; bottom: 6px;
|
|
561
|
+
width: 2px; background: #58a6ff; border-radius: 0 2px 2px 0;
|
|
562
|
+
}
|
|
563
|
+
.activity-btn svg { width: 20px; height: 20px; }
|
|
564
|
+
.file-sidebar {
|
|
565
|
+
width: 260px; min-width: 260px; background: #0e1117; border-right: 1px solid #21262d;
|
|
566
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
567
|
+
}
|
|
568
|
+
.file-sidebar-header {
|
|
569
|
+
padding: 10px 16px; font-size: 11px; color: #8b949e; font-weight: 600;
|
|
570
|
+
text-transform: uppercase; letter-spacing: 0.5px;
|
|
571
|
+
display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #21262d;
|
|
572
|
+
}
|
|
573
|
+
.file-sidebar-header button {
|
|
574
|
+
background: none; border: none; color: #8b949e; cursor: pointer;
|
|
575
|
+
font-size: 11px; padding: 2px 6px; border-radius: 3px; font-family: inherit;
|
|
576
|
+
}
|
|
577
|
+
.file-sidebar-header button:hover { color: #58a6ff; background: rgba(88,166,255,0.08); }
|
|
578
|
+
.file-tree { flex: 1; overflow-y: auto; padding: 4px 0; }
|
|
579
|
+
/* File tree — native <details>/<summary> based */
|
|
580
|
+
.ft-dir { border: none; margin: 0; padding: 0; }
|
|
581
|
+
.ft-dir > summary {
|
|
582
|
+
display: flex; align-items: center; gap: 4px; padding: 3px 8px; font-size: 13px;
|
|
583
|
+
cursor: pointer; color: #b1bac4; white-space: nowrap; overflow: hidden;
|
|
584
|
+
text-overflow: ellipsis; font-family: 'IBM Plex Mono', monospace;
|
|
585
|
+
list-style: none; transition: background 0.1s; user-select: none;
|
|
586
|
+
}
|
|
587
|
+
.ft-dir > summary::-webkit-details-marker { display: none; }
|
|
588
|
+
.ft-dir > summary::marker { display: none; content: ''; }
|
|
589
|
+
.ft-dir > summary:hover { background: #161b22; }
|
|
590
|
+
.ft-dir > .ft-children { margin: 0; padding: 0; }
|
|
591
|
+
.ft-item {
|
|
592
|
+
display: flex; align-items: center; gap: 4px; padding: 3px 8px; font-size: 13px;
|
|
593
|
+
cursor: pointer; color: #b1bac4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
594
|
+
transition: background 0.1s; font-family: 'IBM Plex Mono', monospace;
|
|
595
|
+
}
|
|
596
|
+
.ft-item:hover { background: #161b22; }
|
|
597
|
+
.ft-item.active-viewer { background: rgba(88,166,255,0.08); color: #e6edf3; }
|
|
598
|
+
.ft-icon { width: 16px; height: 16px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
|
|
599
|
+
.ft-chevron { width: 14px; height: 14px; flex-shrink: 0; color: #484f58; display: flex; align-items: center; justify-content: center; transition: transform 0.15s; }
|
|
600
|
+
.ft-dir[open] > summary .ft-chevron { transform: rotate(90deg); }
|
|
601
|
+
.ft-name { overflow: hidden; text-overflow: ellipsis; margin-left: 2px; }
|
|
602
|
+
.ft-icon.folder { color: #d29922; }
|
|
603
|
+
.ft-icon.ts { color: #58a6ff; }
|
|
604
|
+
.ft-icon.js { color: #f0db4f; }
|
|
605
|
+
.ft-icon.json { color: #d4883b; }
|
|
606
|
+
.ft-icon.css { color: #d291ff; }
|
|
607
|
+
.ft-icon.html { color: #f06529; }
|
|
608
|
+
.ft-icon.md { color: #8b949e; }
|
|
609
|
+
.ft-icon.yaml { color: #cb4a32; }
|
|
610
|
+
.ft-icon.py { color: #3572A5; }
|
|
611
|
+
.ft-icon.sh { color: #89e051; }
|
|
612
|
+
.ft-icon.default { color: #6e7681; }
|
|
613
|
+
.editor-area { flex: 1; display: flex; flex-direction: column; min-width: 0; background: #0d1117; }
|
|
614
|
+
.editor-tabs {
|
|
615
|
+
display: flex; background: #0a0d12; border-bottom: 1px solid #21262d; overflow-x: auto; min-height: 35px;
|
|
616
|
+
}
|
|
617
|
+
.editor-tabs:empty { display: none; }
|
|
618
|
+
.ed-tab {
|
|
619
|
+
display: flex; align-items: center; gap: 6px; padding: 0 14px; font-size: 12px;
|
|
620
|
+
font-family: 'IBM Plex Mono', monospace; cursor: pointer; border-right: 1px solid #21262d;
|
|
621
|
+
color: #8b949e; background: #0a0d12; flex-shrink: 0; transition: all 0.1s; height: 35px;
|
|
622
|
+
}
|
|
623
|
+
.ed-tab:hover { color: #b1bac4; }
|
|
624
|
+
.ed-tab.active { background: #0d1117; color: #e6edf3; border-top: 2px solid #58a6ff; }
|
|
625
|
+
.ed-tab:not(.active) { border-top: 2px solid transparent; }
|
|
626
|
+
.ed-tab .tab-icon { width: 14px; height: 14px; flex-shrink: 0; display: flex; align-items: center; }
|
|
627
|
+
.ed-tab .tab-modified { width: 6px; height: 6px; border-radius: 50%; background: #58a6ff; flex-shrink: 0; }
|
|
628
|
+
.ed-tab .tab-close {
|
|
629
|
+
width: 18px; height: 18px; border: none; background: transparent; color: #8b949e;
|
|
630
|
+
cursor: pointer; border-radius: 3px; display: flex; align-items: center; justify-content: center;
|
|
631
|
+
opacity: 0; transition: opacity 0.1s; font-size: 14px; margin-left: 4px;
|
|
632
|
+
}
|
|
633
|
+
.ed-tab:hover .tab-close { opacity: 1; }
|
|
634
|
+
.ed-tab .tab-close:hover { background: #21262d; color: #e6edf3; }
|
|
635
|
+
.editor-container { flex: 1; overflow: hidden; }
|
|
636
|
+
.editor-empty {
|
|
637
|
+
flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
638
|
+
color: #484f58; gap: 12px;
|
|
639
|
+
}
|
|
640
|
+
.editor-empty svg { width: 48px; height: 48px; opacity: 0.2; }
|
|
641
|
+
.editor-empty p { font-size: 13px; }
|
|
642
|
+
.editor-empty .shortcuts { display: flex; gap: 20px; font-size: 11px; color: #6e7681; margin-top: 4px; }
|
|
643
|
+
.editor-empty .shortcuts kbd {
|
|
644
|
+
background: #161b22; padding: 2px 6px; border-radius: 3px;
|
|
645
|
+
font-family: 'IBM Plex Mono', monospace; font-size: 10px; border: 1px solid #21262d; margin-right: 4px;
|
|
646
|
+
}
|
|
647
|
+
.status-bar {
|
|
648
|
+
height: 24px; min-height: 24px; background: #1a3a5c; border-top: 1px solid #21262d;
|
|
649
|
+
display: flex; align-items: center; padding: 0 12px; font-size: 11px; color: #8b949e; gap: 16px;
|
|
650
|
+
font-family: 'IBM Plex Mono', monospace;
|
|
651
|
+
}
|
|
652
|
+
.status-bar .sb-right { margin-left: auto; display: flex; gap: 16px; }
|
|
653
|
+
FILES VIEW (disabled) */
|
|
654
|
+
/* Scrollbar — dark theme */
|
|
655
|
+
* { scrollbar-width: thin; scrollbar-color: rgba(139,148,158,0.25) transparent; }
|
|
656
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
657
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
658
|
+
::-webkit-scrollbar-thumb { background: rgba(139,148,158,0.2); border-radius: 3px; }
|
|
659
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(139,148,158,0.35); }
|
|
660
|
+
::-webkit-scrollbar-corner { background: transparent; }
|
|
661
|
+
/* INTEGRATIONS VIEW */
|
|
662
|
+
.integrations-view { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
|
|
663
|
+
.integrations-view.hidden { display: none; }
|
|
664
|
+
.integrations-header {
|
|
665
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
666
|
+
padding: 16px 20px; border-bottom: 1px solid #21262d;
|
|
667
|
+
}
|
|
668
|
+
.integrations-header h2 { font-size: 15px; font-weight: 600; font-family: 'IBM Plex Mono', monospace; color: #e6edf3; }
|
|
669
|
+
.integrations-header button {
|
|
670
|
+
background: #161b22; border: 1px solid #21262d; border-radius: 6px; color: #8b949e;
|
|
671
|
+
font-family: 'IBM Plex Mono', monospace; font-size: 11px; padding: 6px 14px; cursor: pointer;
|
|
672
|
+
}
|
|
673
|
+
.integrations-header button:hover { border-color: #30363d; color: #b1bac4; }
|
|
674
|
+
.integrations-grid {
|
|
675
|
+
flex: 1; overflow-y: auto; padding: 20px;
|
|
676
|
+
display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px;
|
|
677
|
+
align-content: start;
|
|
678
|
+
}
|
|
679
|
+
.integrations-empty {
|
|
680
|
+
flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
681
|
+
color: #484f58; gap: 8px; font-size: 13px; font-family: 'IBM Plex Mono', monospace;
|
|
682
|
+
}
|
|
683
|
+
.toolkit-card {
|
|
684
|
+
background: #161b22; border: 1px solid #21262d; border-radius: 8px; padding: 16px;
|
|
685
|
+
display: flex; flex-direction: column; gap: 10px; transition: border-color 0.15s;
|
|
686
|
+
}
|
|
687
|
+
.toolkit-card:hover { border-color: #30363d; }
|
|
688
|
+
.toolkit-card .tk-top { display: flex; align-items: center; gap: 10px; }
|
|
689
|
+
.toolkit-card .tk-logo {
|
|
690
|
+
width: 36px; height: 36px; border-radius: 8px; object-fit: contain; background: #0d1117;
|
|
691
|
+
flex-shrink: 0;
|
|
692
|
+
}
|
|
693
|
+
.toolkit-card .tk-logo-placeholder {
|
|
694
|
+
width: 36px; height: 36px; border-radius: 8px; background: #21262d;
|
|
695
|
+
display: flex; align-items: center; justify-content: center; font-size: 16px; color: #484f58;
|
|
696
|
+
flex-shrink: 0;
|
|
697
|
+
}
|
|
698
|
+
.toolkit-card .tk-name { font-size: 13px; font-weight: 600; color: #e6edf3; }
|
|
699
|
+
.toolkit-card .tk-desc {
|
|
700
|
+
font-size: 11px; color: #8b949e; line-height: 1.5;
|
|
701
|
+
overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
|
702
|
+
}
|
|
703
|
+
.toolkit-card .tk-actions { margin-top: auto; }
|
|
704
|
+
.tk-btn {
|
|
705
|
+
width: 100%; padding: 8px; border-radius: 6px; font-family: 'IBM Plex Mono', monospace;
|
|
706
|
+
font-size: 11px; font-weight: 600; cursor: pointer; border: none; transition: all 0.15s;
|
|
707
|
+
}
|
|
708
|
+
.tk-btn.connect { background: #238636; color: #fff; }
|
|
709
|
+
.tk-btn.connect:hover { background: #2ea043; }
|
|
710
|
+
.tk-btn.connected { background: rgba(63,185,80,0.12); color: #3fb950; border: 1px solid #23612c; }
|
|
711
|
+
.tk-btn.connected:hover { background: rgba(248,81,73,0.12); color: #f85149; border-color: #da3633; }
|
|
712
|
+
|
|
713
|
+
/* Skills Marketplace view */
|
|
714
|
+
.skills-view.hidden { display: none !important; }
|
|
715
|
+
|
|
716
|
+
/* Communication view */
|
|
717
|
+
.comms-view { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
|
|
718
|
+
.comms-view.hidden { display: none; }
|
|
719
|
+
.comms-header {
|
|
720
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
721
|
+
padding: 16px 20px; border-bottom: 1px solid #21262d;
|
|
722
|
+
}
|
|
723
|
+
.comms-header h2 { font-size: 15px; font-weight: 600; font-family: 'IBM Plex Mono', monospace; color: #e6edf3; margin: 0; }
|
|
724
|
+
.comms-subtitle { font-size: 12px; color: #484f58; }
|
|
725
|
+
.comms-content {
|
|
726
|
+
flex: 1; overflow-y: auto; padding: 20px;
|
|
727
|
+
display: grid;
|
|
728
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
729
|
+
gap: 16px;
|
|
730
|
+
align-content: start;
|
|
731
|
+
}
|
|
732
|
+
.comms-card {
|
|
733
|
+
background: #161b22; border: 1px solid #21262d; border-radius: 12px; padding: 24px;
|
|
734
|
+
display: flex; flex-direction: column; gap: 16px;
|
|
735
|
+
transition: border-color 0.2s ease;
|
|
736
|
+
}
|
|
737
|
+
.comms-card:hover { border-color: #30363d; }
|
|
738
|
+
.comms-card.telegram { border-top: 2px solid #2AABEE; }
|
|
739
|
+
.comms-card.whatsapp { border-top: 2px solid #25D366; }
|
|
740
|
+
.comms-card.phone { border-top: 2px solid #f44336; }
|
|
741
|
+
.comms-card-top { display: flex; align-items: flex-start; gap: 14px; }
|
|
742
|
+
.comms-card-icon {
|
|
743
|
+
width: 48px; height: 48px; border-radius: 12px; background: #0d1117;
|
|
744
|
+
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
745
|
+
color: #58a6ff;
|
|
746
|
+
}
|
|
747
|
+
.comms-card-icon svg { width: 28px; height: 28px; }
|
|
748
|
+
.comms-card-icon.telegram { color: #2AABEE; background: rgba(42,171,238,0.08); }
|
|
749
|
+
.comms-card-icon.whatsapp { color: #25D366; background: rgba(37,211,102,0.08); }
|
|
750
|
+
.comms-card-icon.phone { color: #f44336; background: rgba(244,67,54,0.08); }
|
|
751
|
+
.phone-url-box {
|
|
752
|
+
display: flex; align-items: center; gap: 8px; padding: 10px 12px;
|
|
753
|
+
background: #0d1117; border: 1px solid #21262d; border-radius: 8px; font-size: 12px;
|
|
754
|
+
}
|
|
755
|
+
.phone-url-box code {
|
|
756
|
+
flex: 1; color: #e6edf3; font-family: 'IBM Plex Mono', monospace; word-break: break-all;
|
|
757
|
+
font-size: 11px;
|
|
758
|
+
}
|
|
759
|
+
.phone-url-box .copy-btn {
|
|
760
|
+
background: none; border: 1px solid #30363d; color: #8b949e; border-radius: 6px;
|
|
761
|
+
padding: 4px 10px; cursor: pointer; font-size: 11px; white-space: nowrap;
|
|
762
|
+
}
|
|
763
|
+
.phone-url-box .copy-btn:hover { color: #e6edf3; border-color: #58a6ff; }
|
|
764
|
+
.phone-url-box .copy-btn.copied { color: #3fb950; border-color: #3fb950; }
|
|
765
|
+
.conv-msg.whatsapp { border-left: 2px solid #25D366; padding-left: 8px; }
|
|
766
|
+
.conv-msg.whatsapp .label.wa { color: #25D366; }
|
|
767
|
+
.comms-status.scanning { background: rgba(210,153,34,0.12); color: #d29922; }
|
|
768
|
+
.wa-qr-container { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 10px; }
|
|
769
|
+
.wa-qr-container canvas { border-radius: 8px; background: #fff; padding: 8px; }
|
|
770
|
+
.wa-qr-hint { font-size: 10px; color: #8b949e; text-align: center; }
|
|
771
|
+
.wa-clear-auth { display: flex; align-items: center; gap: 6px; font-size: 11px; color: #8b949e; }
|
|
772
|
+
.wa-clear-auth input { accent-color: #f85149; }
|
|
773
|
+
.comms-card-info { display: flex; flex-direction: column; gap: 3px; flex: 1; }
|
|
774
|
+
.comms-card-name { font-size: 14px; font-weight: 600; color: #e6edf3; }
|
|
775
|
+
.comms-card-desc { font-size: 11px; color: #8b949e; line-height: 1.5; }
|
|
776
|
+
.comms-status {
|
|
777
|
+
font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 10px;
|
|
778
|
+
flex-shrink: 0; text-transform: uppercase; letter-spacing: 0.5px;
|
|
779
|
+
}
|
|
780
|
+
.comms-status.connected { background: rgba(63,185,80,0.12); color: #3fb950; }
|
|
781
|
+
.comms-status.disconnected { background: rgba(139,148,158,0.1); color: #484f58; }
|
|
782
|
+
.comms-status.error { background: rgba(248,81,73,0.12); color: #f85149; }
|
|
783
|
+
.comms-card-config { display: flex; flex-direction: column; gap: 12px; }
|
|
784
|
+
.comms-field { display: flex; flex-direction: column; gap: 5px; }
|
|
785
|
+
.comms-field label {
|
|
786
|
+
font-size: 11px; font-weight: 600; color: #8b949e; text-transform: uppercase;
|
|
787
|
+
letter-spacing: 0.3px;
|
|
788
|
+
}
|
|
789
|
+
.comms-field input {
|
|
790
|
+
background: #0d1117; border: 1px solid #21262d; border-radius: 6px; padding: 8px 12px;
|
|
791
|
+
color: #e6edf3; font-family: 'IBM Plex Mono', monospace; font-size: 12px;
|
|
792
|
+
outline: none; transition: border-color 0.15s;
|
|
793
|
+
}
|
|
794
|
+
.comms-field input:focus { border-color: #58a6ff; }
|
|
795
|
+
.comms-field input::placeholder { color: #30363d; }
|
|
796
|
+
.comms-hint { font-size: 10px; color: #484f58; }
|
|
797
|
+
.comms-hint a { color: #58a6ff; text-decoration: none; }
|
|
798
|
+
.comms-hint a:hover { text-decoration: underline; }
|
|
799
|
+
.comms-card-actions { display: flex; gap: 8px; }
|
|
800
|
+
.comms-card.is-connected .comms-card-config { display: none; }
|
|
801
|
+
.comms-bot-info {
|
|
802
|
+
display: flex; align-items: center; gap: 10px; padding: 10px 14px;
|
|
803
|
+
background: rgba(63,185,80,0.06); border: 1px solid #23612c; border-radius: 8px;
|
|
804
|
+
}
|
|
805
|
+
.comms-bot-info .bot-name { font-size: 13px; font-weight: 600; color: #3fb950; }
|
|
806
|
+
.comms-bot-info .bot-username { font-size: 11px; color: #8b949e; }
|
|
807
|
+
|
|
808
|
+
@keyframes fade-in { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }
|
|
809
|
+
@media (max-width: 700px) {
|
|
810
|
+
.chat-view { flex-direction: column; }
|
|
811
|
+
.panel-cam { width: 100%; min-width: 0; border-right: none; border-bottom: 1px solid #21262d; }
|
|
812
|
+
.camera-container { aspect-ratio: 16/9; }
|
|
813
|
+
.activity-bar { display: none; }
|
|
814
|
+
.file-sidebar { width: 100%; min-width: 0; max-height: 40vh; border-right: none; border-bottom: 1px solid #21262d; }
|
|
815
|
+
.files-panel { position: absolute; right: 0; top: 0; bottom: 0; z-index: 50; width: 280px; min-width: 280px; }
|
|
816
|
+
}
|
|
817
|
+
</style>
|
|
818
|
+
</head>
|
|
819
|
+
<body>
|
|
820
|
+
<div class="header">
|
|
821
|
+
<div class="header-left">
|
|
822
|
+
<div class="header-logo" id="headerLogo"></div>
|
|
823
|
+
<h1>Gitclaw: {{AGENT_NAME}}</h1>
|
|
824
|
+
<button class="chat-sidebar-toggle" id="chatSidebarToggle" onclick="toggleChatSidebar()" title="Toggle chat list">
|
|
825
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>
|
|
826
|
+
</button>
|
|
827
|
+
<div class="view-tabs">
|
|
828
|
+
<button class="view-tab active" id="tabChat" onclick="switchView('chat')">Chat</button>
|
|
829
|
+
<!-- <button class="view-tab" id="tabFiles" onclick="switchView('files')">Files</button> -->
|
|
830
|
+
<button class="view-tab" id="tabSkills" onclick="switchView('skills')">Skills</button>
|
|
831
|
+
<button class="view-tab" id="tabIntegrations" onclick="switchView('integrations')">Integrations</button>
|
|
832
|
+
<button class="view-tab" id="tabComms" onclick="switchView('comms')">Communication</button>
|
|
833
|
+
</div>
|
|
834
|
+
</div>
|
|
835
|
+
<div class="header-right">
|
|
836
|
+
<button class="audit-toggle active recording" id="auditToggle" onclick="toggleAuditMode()" title="File System: watch agent file changes live">
|
|
837
|
+
<span class="audit-dot"></span>
|
|
838
|
+
<span>File System</span>
|
|
839
|
+
</button>
|
|
840
|
+
<span class="status-dot" id="statusDot"></span>
|
|
841
|
+
<span class="status-text" id="statusText">Disconnected</span>
|
|
842
|
+
</div>
|
|
843
|
+
</div>
|
|
844
|
+
<div class="main" style="position:relative;">
|
|
845
|
+
<button class="chat-sidebar-edge-tab visible" id="chatEdgeTab" onclick="toggleChatSidebar()" title="Show chats">
|
|
846
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
|
|
847
|
+
</button>
|
|
848
|
+
<div class="chat-sidebar collapsed" id="chatSidebar">
|
|
849
|
+
<div class="chat-sidebar-header">
|
|
850
|
+
<button class="chat-sidebar-collapse" onclick="toggleChatSidebar()" title="Collapse chats">
|
|
851
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
|
|
852
|
+
</button>
|
|
853
|
+
<span>Chats</span>
|
|
854
|
+
<button class="new-chat-btn" onclick="newChat()">+ New</button>
|
|
855
|
+
</div>
|
|
856
|
+
<div class="chat-list" id="chatList"></div>
|
|
857
|
+
<div class="branch-indicator" id="branchIndicator">
|
|
858
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
|
|
859
|
+
<span class="branch-name" id="branchName">main</span>
|
|
860
|
+
</div>
|
|
861
|
+
</div>
|
|
862
|
+
<div class="chat-view" id="chatView">
|
|
863
|
+
<div class="panel-cam">
|
|
864
|
+
<div class="camera-container">
|
|
865
|
+
<div class="camera-off" id="cameraOff">Camera off</div>
|
|
866
|
+
<video id="cameraVideo" autoplay playsinline muted style="display:none;"></video>
|
|
867
|
+
<canvas id="cameraCanvas"></canvas>
|
|
868
|
+
</div>
|
|
869
|
+
<div class="controls">
|
|
870
|
+
<button class="ctrl-btn" id="cameraBtn" onclick="toggleCamera()">
|
|
871
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.934a.5.5 0 0 0-.777-.416L16 11"/><rect x="2" y="6" width="14" height="12" rx="2"/></svg>
|
|
872
|
+
Camera
|
|
873
|
+
</button>
|
|
874
|
+
<button class="ctrl-btn" id="screenBtn" onclick="toggleScreen()">
|
|
875
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>
|
|
876
|
+
Screen
|
|
877
|
+
</button>
|
|
878
|
+
<button class="ctrl-btn" id="micBtn" onclick="toggleMic()">
|
|
879
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg>
|
|
880
|
+
Mic
|
|
881
|
+
</button>
|
|
882
|
+
</div>
|
|
883
|
+
<div class="agent-vitals" id="agentVitals">
|
|
884
|
+
<div class="vitals-header">
|
|
885
|
+
<span class="vitals-title">Agent Vitals</span>
|
|
886
|
+
<span class="vitals-status-dot" id="vitalsDot"></span>
|
|
887
|
+
</div>
|
|
888
|
+
<div class="vitals-grid">
|
|
889
|
+
<div class="vital-cell cpu">
|
|
890
|
+
<span class="vital-label">CPU</span>
|
|
891
|
+
<span class="vital-value" id="vitalCpu">0<span class="vital-unit">%</span></span>
|
|
892
|
+
<div class="vital-bar"><div class="vital-bar-fill" id="vitalCpuBar" style="width:0%"></div></div>
|
|
893
|
+
</div>
|
|
894
|
+
<div class="vital-cell mem">
|
|
895
|
+
<span class="vital-label">Memory</span>
|
|
896
|
+
<span class="vital-value" id="vitalMem">0<span class="vital-unit">MB</span></span>
|
|
897
|
+
<div class="vital-bar"><div class="vital-bar-fill" id="vitalMemBar" style="width:0%"></div></div>
|
|
898
|
+
</div>
|
|
899
|
+
<div class="vital-cell tokens">
|
|
900
|
+
<span class="vital-label">Tokens</span>
|
|
901
|
+
<span class="vital-value" id="vitalTokens">0<span class="vital-unit">tok</span></span>
|
|
902
|
+
<div class="vital-bar"><div class="vital-bar-fill" id="vitalTokensBar" style="width:0%"></div></div>
|
|
903
|
+
</div>
|
|
904
|
+
<div class="vital-cell uptime">
|
|
905
|
+
<span class="vital-label">Uptime</span>
|
|
906
|
+
<span class="vital-value" id="vitalUptime">00:00</span>
|
|
907
|
+
</div>
|
|
908
|
+
<div class="vital-cell wide">
|
|
909
|
+
<div class="vitals-wave">
|
|
910
|
+
<span class="vitals-wave-label">Pulse</span>
|
|
911
|
+
<canvas id="vitalsWaveCanvas"></canvas>
|
|
912
|
+
</div>
|
|
913
|
+
</div>
|
|
914
|
+
</div>
|
|
915
|
+
</div>
|
|
916
|
+
</div>
|
|
917
|
+
<div class="panel-right">
|
|
918
|
+
<div class="conversation" id="conversation"></div>
|
|
919
|
+
<div class="file-preview-bar" id="filePreviewBar"></div>
|
|
920
|
+
<div class="input-bar">
|
|
921
|
+
<button class="attach-btn" onclick="document.getElementById('fileInput').click()" title="Attach files">
|
|
922
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
|
923
|
+
</button>
|
|
924
|
+
<input type="file" id="fileInput" multiple style="display:none" onchange="handleFileSelect(this.files)" />
|
|
925
|
+
<input type="text" id="textInput" placeholder="Type a message or drop files..." autocomplete="off" />
|
|
926
|
+
<button onclick="sendText()">Send</button>
|
|
927
|
+
</div>
|
|
928
|
+
<div class="drop-overlay" id="dropOverlay">Drop files here</div>
|
|
929
|
+
</div>
|
|
930
|
+
<div class="files-panel" id="filesPanel">
|
|
931
|
+
<div class="resize-handle-x" id="fpResizeX"></div>
|
|
932
|
+
<div class="files-panel-header">
|
|
933
|
+
<span class="fp-title">Files <span class="fp-count hidden" id="fpCount">0</span></span>
|
|
934
|
+
<button onclick="toggleAuditMode()">×</button>
|
|
935
|
+
</div>
|
|
936
|
+
<div class="file-tree" id="explorerTree"></div>
|
|
937
|
+
<div class="resize-handle-y" id="dvResizeY"></div>
|
|
938
|
+
<div class="diff-viewer" id="diffViewer">
|
|
939
|
+
<div class="diff-viewer-header">
|
|
940
|
+
<div class="dv-left">
|
|
941
|
+
<span class="dv-path" id="dvPath"></span>
|
|
942
|
+
<span class="dv-status viewing" id="dvStatus">VIEWING</span>
|
|
943
|
+
</div>
|
|
944
|
+
<button class="dv-md-toggle hidden" id="dvMdToggle" onclick="toggleMdView()" title="Toggle markdown preview">
|
|
945
|
+
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h20v18H2z"/><path d="M7 15V9l2.5 3L12 9v6"/><path d="M17 9v6l-2-3"/></svg>
|
|
946
|
+
</button>
|
|
947
|
+
<button onclick="closeDiffViewer()" title="Close">×</button>
|
|
948
|
+
</div>
|
|
949
|
+
<div class="dv-countdown" id="dvCountdown" style="width:100%"></div>
|
|
950
|
+
<div class="diff-viewer-content">
|
|
951
|
+
<pre id="dvPre"></pre>
|
|
952
|
+
<div class="dv-markdown hidden" id="dvMarkdown"></div>
|
|
953
|
+
<div class="dv-image hidden" id="dvImage"></div>
|
|
954
|
+
</div>
|
|
955
|
+
</div>
|
|
956
|
+
</div>
|
|
957
|
+
</div>
|
|
958
|
+
<!-- FILES VIEW (disabled)
|
|
959
|
+
<div class="files-view hidden" id="filesView">
|
|
960
|
+
<div class="activity-bar">
|
|
961
|
+
<button class="activity-btn active" title="Explorer">
|
|
962
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>
|
|
963
|
+
</button>
|
|
964
|
+
<button class="activity-btn" title="Search">
|
|
965
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
966
|
+
</button>
|
|
967
|
+
</div>
|
|
968
|
+
<div class="file-sidebar">
|
|
969
|
+
<div class="file-sidebar-header"><span>Explorer</span><button onclick="loadFileTree()">Refresh</button></div>
|
|
970
|
+
<div class="file-tree" id="fileTree"><div style="padding:16px;color:#484f58;font-size:12px;">Loading...</div></div>
|
|
971
|
+
</div>
|
|
972
|
+
<div class="editor-area">
|
|
973
|
+
<div class="editor-tabs" id="editorTabs"></div>
|
|
974
|
+
<div class="editor-container" id="editorContainer">
|
|
975
|
+
<div class="editor-empty" id="editorEmpty">
|
|
976
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
|
977
|
+
<p>Select a file to start editing</p>
|
|
978
|
+
<div class="shortcuts"><span><kbd>Cmd+S</kbd> Save</span><span><kbd>Cmd+W</kbd> Close tab</span></div>
|
|
979
|
+
</div>
|
|
980
|
+
</div>
|
|
981
|
+
<div class="status-bar">
|
|
982
|
+
<div id="sbBranch"></div>
|
|
983
|
+
<div class="sb-right"><div id="sbLang"></div><div>UTF-8</div></div>
|
|
984
|
+
</div>
|
|
985
|
+
</div>
|
|
986
|
+
</div>
|
|
987
|
+
-->
|
|
988
|
+
<div class="integrations-view hidden" id="integrationsView">
|
|
989
|
+
<div class="integrations-header">
|
|
990
|
+
<h2>Integrations</h2>
|
|
991
|
+
<button onclick="loadToolkits()">Refresh</button>
|
|
992
|
+
</div>
|
|
993
|
+
<div class="integrations-grid" id="integrationsGrid">
|
|
994
|
+
<div class="integrations-empty" id="integrationsEmpty">Loading...</div>
|
|
995
|
+
</div>
|
|
996
|
+
</div>
|
|
997
|
+
<div class="comms-view hidden" id="commsView">
|
|
998
|
+
<div class="comms-header">
|
|
999
|
+
<h2>Communication</h2>
|
|
1000
|
+
<span class="comms-subtitle">Connect messaging channels so your agent can send and receive messages</span>
|
|
1001
|
+
</div>
|
|
1002
|
+
<div class="comms-content">
|
|
1003
|
+
<div class="comms-card telegram" id="telegramCard">
|
|
1004
|
+
<div class="comms-card-top">
|
|
1005
|
+
<div class="comms-card-icon">
|
|
1006
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69.01-.03.01-.14-.07-.2-.08-.06-.19-.04-.28-.02-.12.03-2.02 1.28-5.69 3.77-.54.37-1.03.55-1.47.54-.48-.01-1.41-.27-2.1-.5-.85-.28-1.52-.43-1.46-.91.03-.25.38-.51 1.05-.78 4.12-1.79 6.87-2.97 8.26-3.54 3.93-1.62 4.75-1.9 5.28-1.91.12 0 .37.03.54.17.14.12.18.28.2.47-.01.06.01.24 0 .37z"/></svg>
|
|
1007
|
+
</div>
|
|
1008
|
+
<div class="comms-card-info">
|
|
1009
|
+
<span class="comms-card-name">Telegram</span>
|
|
1010
|
+
<span class="comms-card-desc">Connect a Telegram bot to chat with your agent from anywhere</span>
|
|
1011
|
+
</div>
|
|
1012
|
+
<span class="comms-status" id="tgStatus"></span>
|
|
1013
|
+
</div>
|
|
1014
|
+
<div class="comms-card-config" id="tgConfig">
|
|
1015
|
+
<div class="comms-field">
|
|
1016
|
+
<label>Bot Token</label>
|
|
1017
|
+
<input type="password" id="tgToken" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v..." spellcheck="false" autocomplete="off" />
|
|
1018
|
+
<span class="comms-hint">Get one from <a href="https://t.me/BotFather" target="_blank">@BotFather</a> on Telegram</span>
|
|
1019
|
+
</div>
|
|
1020
|
+
<div class="comms-field">
|
|
1021
|
+
<label>Allowed Users</label>
|
|
1022
|
+
<input type="text" id="tgAllowedUsers" placeholder="@username1, @username2" spellcheck="false" autocomplete="off" />
|
|
1023
|
+
<span class="comms-hint">Comma-separated usernames. Use <code>*</code> to allow everyone. Empty = block all.</span>
|
|
1024
|
+
</div>
|
|
1025
|
+
</div>
|
|
1026
|
+
<div class="comms-card-security" id="tgSecurity" style="display:none;">
|
|
1027
|
+
<div class="comms-field">
|
|
1028
|
+
<label>Allowed Users</label>
|
|
1029
|
+
<div style="display:flex;gap:8px;align-items:center;">
|
|
1030
|
+
<input type="text" id="tgAllowedUsersLive" placeholder="@username1, @username2" spellcheck="false" autocomplete="off" style="flex:1;" />
|
|
1031
|
+
<button class="tk-btn connect" onclick="saveTgAllowedUsers()" style="padding:6px 14px;font-size:12px;">Save</button>
|
|
1032
|
+
</div>
|
|
1033
|
+
<span class="comms-hint">Comma-separated usernames. Use <code>*</code> to allow everyone. Empty = block all.</span>
|
|
1034
|
+
</div>
|
|
1035
|
+
</div>
|
|
1036
|
+
<div class="comms-card-actions">
|
|
1037
|
+
<button class="tk-btn connect" id="tgConnectBtn" onclick="toggleTelegram()">Connect</button>
|
|
1038
|
+
</div>
|
|
1039
|
+
</div>
|
|
1040
|
+
|
|
1041
|
+
<!-- WhatsApp Card -->
|
|
1042
|
+
<div class="comms-card whatsapp" id="whatsappCard">
|
|
1043
|
+
<div class="comms-card-top">
|
|
1044
|
+
<div class="comms-card-icon whatsapp">
|
|
1045
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg>
|
|
1046
|
+
</div>
|
|
1047
|
+
<div class="comms-card-info">
|
|
1048
|
+
<span class="comms-card-name">WhatsApp</span>
|
|
1049
|
+
<span class="comms-card-desc">Send messages to yourself on WhatsApp and get agent responses</span>
|
|
1050
|
+
</div>
|
|
1051
|
+
<span class="comms-status" id="waStatus"></span>
|
|
1052
|
+
</div>
|
|
1053
|
+
<div class="comms-card-config" id="waConfig">
|
|
1054
|
+
<div class="wa-qr-container" id="waQrContainer" style="display:none;">
|
|
1055
|
+
<canvas id="waQrCanvas" width="256" height="256"></canvas>
|
|
1056
|
+
<span class="wa-qr-hint">Scan with WhatsApp → Settings → Linked Devices</span>
|
|
1057
|
+
</div>
|
|
1058
|
+
</div>
|
|
1059
|
+
<div class="comms-card-actions">
|
|
1060
|
+
<label class="wa-clear-auth" id="waClearLabel" style="display:none;">
|
|
1061
|
+
<input type="checkbox" id="waClearAuth" /> Clear session on disconnect
|
|
1062
|
+
</label>
|
|
1063
|
+
<button class="tk-btn connect" id="waConnectBtn" onclick="toggleWhatsApp()">Connect</button>
|
|
1064
|
+
</div>
|
|
1065
|
+
</div>
|
|
1066
|
+
|
|
1067
|
+
<!-- Phone Number / Twilio Card -->
|
|
1068
|
+
<div class="comms-card phone" id="phoneCard">
|
|
1069
|
+
<div class="comms-card-top">
|
|
1070
|
+
<div class="comms-card-icon phone">
|
|
1071
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"/></svg>
|
|
1072
|
+
</div>
|
|
1073
|
+
<div class="comms-card-info">
|
|
1074
|
+
<span class="comms-card-name">Phone Number</span>
|
|
1075
|
+
<span class="comms-card-desc">Receive SMS via Twilio webhook — set this URL in your Twilio phone number config</span>
|
|
1076
|
+
</div>
|
|
1077
|
+
</div>
|
|
1078
|
+
<div class="comms-card-config">
|
|
1079
|
+
<div class="phone-url-box">
|
|
1080
|
+
<code id="phoneWebhookUrl"></code>
|
|
1081
|
+
<button class="copy-btn" onclick="copyWebhookUrl(this)">Copy</button>
|
|
1082
|
+
</div>
|
|
1083
|
+
<span style="font-size:10px; color:#484f58; margin-top:4px; display:block;">
|
|
1084
|
+
Paste this URL in Twilio → Phone Numbers → your number → Messaging → "A message comes in" webhook
|
|
1085
|
+
</span>
|
|
1086
|
+
</div>
|
|
1087
|
+
</div>
|
|
1088
|
+
</div>
|
|
1089
|
+
</div>
|
|
1090
|
+
<div class="skills-view hidden" id="skillsView" style="display:flex;flex-direction:column;flex:1;overflow:hidden;">
|
|
1091
|
+
<iframe id="skillsFrame" src="" style="flex:1;border:none;background:#0d1117;width:100%;"></iframe>
|
|
1092
|
+
</div>
|
|
1093
|
+
</div>
|
|
1094
|
+
<script src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"></script>
|
|
1095
|
+
<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
|
|
1096
|
+
<!-- <script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script> -->
|
|
1097
|
+
<script>
|
|
1098
|
+
var hasComposio={{HAS_COMPOSIO}};
|
|
1099
|
+
if(!hasComposio){document.getElementById('tabIntegrations').style.display='none';}
|
|
1100
|
+
let currentView='chat',filesLoaded=false,integrationsLoaded=false,commsLoaded=false,skillsLoaded=false;
|
|
1101
|
+
function switchView(v){
|
|
1102
|
+
currentView=v;
|
|
1103
|
+
document.getElementById('chatView').classList.toggle('hidden',v!=='chat');
|
|
1104
|
+
document.getElementById('integrationsView').classList.toggle('hidden',v!=='integrations');
|
|
1105
|
+
document.getElementById('commsView').classList.toggle('hidden',v!=='comms');
|
|
1106
|
+
document.getElementById('skillsView').classList.toggle('hidden',v!=='skills');
|
|
1107
|
+
document.getElementById('tabChat').classList.toggle('active',v==='chat');
|
|
1108
|
+
document.getElementById('tabIntegrations').classList.toggle('active',v==='integrations');
|
|
1109
|
+
document.getElementById('tabComms').classList.toggle('active',v==='comms');
|
|
1110
|
+
document.getElementById('tabSkills').classList.toggle('active',v==='skills');
|
|
1111
|
+
if(v==='integrations'&&!integrationsLoaded){loadToolkits();integrationsLoaded=true;}
|
|
1112
|
+
if(v==='comms'&&!commsLoaded){loadTelegramStatus();loadWhatsAppStatus();loadPhoneWebhookUrl();commsLoaded=true;}
|
|
1113
|
+
if(v==='skills'&&!skillsLoaded){document.getElementById('skillsFrame').src='/api/skills-mp/proxy?path=/';skillsLoaded=true;}
|
|
1114
|
+
}
|
|
1115
|
+
// Skills MP install handler
|
|
1116
|
+
window.addEventListener('message',function(e){
|
|
1117
|
+
if(!e.data||e.data.type!=='install_skill')return;
|
|
1118
|
+
var source=e.data.source;
|
|
1119
|
+
if(!source)return;
|
|
1120
|
+
fetch('/api/skills-mp/install',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source:source})})
|
|
1121
|
+
.then(function(r){return r.json();})
|
|
1122
|
+
.then(function(d){
|
|
1123
|
+
if(d.ok){
|
|
1124
|
+
alert('Installed');
|
|
1125
|
+
// Notify the iframe that install succeeded
|
|
1126
|
+
var frame=document.getElementById('skillsFrame');
|
|
1127
|
+
if(frame&&frame.contentWindow)frame.contentWindow.postMessage({type:'install_success',source:source},'*');
|
|
1128
|
+
} else {
|
|
1129
|
+
alert('Install failed: '+(d.error||'Unknown error'));
|
|
1130
|
+
}
|
|
1131
|
+
})
|
|
1132
|
+
.catch(function(err){alert('Install failed: '+err.message);});
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
/* FILES/MONACO DISABLED
|
|
1136
|
+
let monacoEditor=null,monacoReady=false;
|
|
1137
|
+
require.config({paths:{vs:'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs'}});
|
|
1138
|
+
require(['vs/editor/editor.main'],function(){monacoReady=true;monaco.editor.defineTheme('gitclaw-dark',{base:'vs-dark',inherit:true,rules:[],colors:{'editor.background':'#0d1117','editor.lineHighlightBackground':'#161b2280','editorLineNumber.foreground':'#484f58','editorLineNumber.activeForeground':'#8b949e','editor.selectionBackground':'#58a6ff30','editorCursor.foreground':'#58a6ff','editorWidget.background':'#161b22','editorWidget.border':'#21262d','minimap.background':'#0d1117'}});});
|
|
1139
|
+
|
|
1140
|
+
function getLanguage(f){var e=f.split('.').pop().toLowerCase(),m={ts:'typescript',tsx:'typescript',js:'javascript',jsx:'javascript',json:'json',html:'html',css:'css',scss:'scss',less:'less',md:'markdown',py:'python',sh:'shell',bash:'shell',yaml:'yaml',yml:'yaml',xml:'xml',sql:'sql',rs:'rust',go:'go',java:'java',c:'c',cpp:'cpp',h:'c',rb:'ruby',php:'php',swift:'swift',kt:'kotlin',toml:'ini',env:'ini',gitignore:'ini'};return m[e]||'plaintext';}
|
|
1141
|
+
|
|
1142
|
+
function ensureMonacoEditor(){if(monacoEditor||!monacoReady)return;var c=document.getElementById('editorContainer');document.getElementById('editorEmpty').style.display='none';var d=document.createElement('div');d.id='monacoMount';d.style.cssText='width:100%;height:100%;';c.appendChild(d);monacoEditor=monaco.editor.create(d,{value:'',language:'plaintext',theme:'gitclaw-dark',fontSize:13,fontFamily:"'JetBrains Mono','IBM Plex Mono','Fira Code',monospace",fontLigatures:true,minimap:{enabled:true,scale:1},lineNumbers:'on',renderLineHighlight:'all',scrollBeyondLastLine:false,padding:{top:8},bracketPairColorization:{enabled:true},smoothScrolling:true,cursorBlinking:'smooth',cursorSmoothCaretAnimation:'on',wordWrap:'on',tabSize:2,automaticLayout:true});monacoEditor.onDidChangeModelContent(function(){if(activeTabPath&&openTabs[activeTabPath]){openTabs[activeTabPath].modified=true;renderTabs();}});}
|
|
1143
|
+
|
|
1144
|
+
var ICONS={chevronRight:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9,18 15,12 9,6"/></svg>',chevronDown:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6,9 12,15 18,9"/></svg>',folder:'<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>',folderOpen:'<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M5 19h14a2 2 0 001.84-1.22L23 12H5.24a2 2 0 00-1.84 1.22L1 19h2a2 2 0 002-2V7a2 2 0 012-2h4l2 2h6a2 2 0 012 2v1"/></svg>',file:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/></svg>',fileCode:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/><path d="M10 12l-2 2 2 2"/><path d="M14 12l2 2-2 2"/></svg>'};
|
|
1145
|
+
|
|
1146
|
+
function getFileIconClass(n){var e=n.split('.').pop().toLowerCase();if(['ts','tsx'].includes(e))return'ts';if(['js','jsx','mjs'].includes(e))return'js';if(e==='json')return'json';if(['css','scss','less'].includes(e))return'css';if(['html','htm'].includes(e))return'html';if(['md','txt','rst'].includes(e))return'md';if(['yaml','yml'].includes(e))return'yaml';if(e==='py')return'py';if(['sh','bash','zsh'].includes(e))return'sh';return'default';}
|
|
1147
|
+
function getFileIconSvg(n){var c=getFileIconClass(n);if(['ts','js','css','html','py','sh'].includes(c))return ICONS.fileCode;return ICONS.file;}
|
|
1148
|
+
|
|
1149
|
+
var activeTabPath=null;
|
|
1150
|
+
async function loadFileTree(){var t=document.getElementById('fileTree');t.innerHTML='<div style="padding:16px;color:#484f58;font-size:12px;">Loading...</div>';try{var r=await fetch('/api/files?path=.');var d=await r.json();t.innerHTML='';renderTree(d.entries,t,0);}catch(e){t.innerHTML='<div style="padding:16px;color:#f85149;font-size:12px;">Failed to load files</div>';}}
|
|
1151
|
+
|
|
1152
|
+
function renderTree(entries,parent,depth){for(var i=0;i<entries.length;i++){var entry=entries[i];if(entry.type==='directory'){var group=document.createElement('div');group.className='ft-group';var item=document.createElement('div');item.className='ft-item dir';item.style.paddingLeft=(depth*12+8)+'px';item.innerHTML='<span class="ft-chevron">'+ICONS.chevronDown+'</span><span class="ft-icon folder">'+ICONS.folderOpen+'</span><span class="ft-name">'+escHtml(entry.name)+'</span>';item.onclick=(function(g,it){return function(e){e.stopPropagation();var c=g.classList.toggle('collapsed');it.querySelector('.ft-chevron').innerHTML=c?ICONS.chevronRight:ICONS.chevronDown;it.querySelector('.ft-icon').innerHTML=c?ICONS.folder:ICONS.folderOpen;};})(group,item);var ch=document.createElement('div');ch.className='ft-children';if(entry.children)renderTree(entry.children,ch,depth+1);group.appendChild(item);group.appendChild(ch);parent.appendChild(group);}else{var fi=document.createElement('div');fi.className='ft-item'+(activeTabPath===entry.path?' active':'');fi.style.paddingLeft=(depth*12+22)+'px';fi.innerHTML='<span class="ft-icon '+getFileIconClass(entry.name)+'">'+getFileIconSvg(entry.name)+'</span><span class="ft-name">'+escHtml(entry.name)+'</span>';fi.onclick=(function(p,n){return function(){openFile(p,n);};})(entry.path,entry.name);fi.dataset.path=entry.path;parent.appendChild(fi);}}}
|
|
1153
|
+
|
|
1154
|
+
var openTabs={};
|
|
1155
|
+
function renderTabs(){var t=document.getElementById('editorTabs');t.innerHTML='';for(var p in openTabs){var tab=openTabs[p];var d=document.createElement('div');d.className='ed-tab'+(p===activeTabPath?' active':'');d.innerHTML='<span class="tab-icon ft-icon '+getFileIconClass(tab.name)+'">'+getFileIconSvg(tab.name)+'</span><span>'+escHtml(tab.name)+'</span>'+(tab.modified?'<span class="tab-modified"></span>':'')+'<button class="tab-close">×</button>';(function(path){d.querySelector('.tab-close').onclick=function(e){e.stopPropagation();closeTab(path);};d.onclick=function(){switchTab(path);};})(p);t.appendChild(d);}}
|
|
1156
|
+
|
|
1157
|
+
function switchTab(p){if(!openTabs[p])return;if(activeTabPath&&openTabs[activeTabPath]&&monacoEditor)openTabs[activeTabPath].content=monacoEditor.getValue();activeTabPath=p;var tab=openTabs[p];if(monacoEditor){monaco.editor.setModelLanguage(monacoEditor.getModel(),tab.language);monacoEditor.setValue(tab.content);}document.getElementById('sbLang').textContent=tab.language;renderTabs();document.querySelectorAll('.ft-item').forEach(function(el){el.classList.toggle('active',el.dataset.path===activeTabPath);});}
|
|
1158
|
+
|
|
1159
|
+
function closeTab(p){delete openTabs[p];var rem=Object.keys(openTabs);if(p===activeTabPath){if(rem.length>0)switchTab(rem[rem.length-1]);else{activeTabPath=null;if(monacoEditor)monacoEditor.setValue('');document.getElementById('editorEmpty').style.display='';var m=document.getElementById('monacoMount');if(m)m.style.display='none';document.getElementById('sbLang').textContent='';}}renderTabs();document.querySelectorAll('.ft-item').forEach(function(el){el.classList.toggle('active',el.dataset.path===activeTabPath);});}
|
|
1160
|
+
|
|
1161
|
+
async function openFile(fp,fn){if(openTabs[fp]){switchTab(fp);return;}ensureMonacoEditor();var m=document.getElementById('monacoMount');if(m)m.style.display='';document.getElementById('editorEmpty').style.display='none';var lang=getLanguage(fn);openTabs[fp]={name:fn,content:'Loading...',modified:false,language:lang};activeTabPath=fp;renderTabs();try{var r=await fetch('/api/file?path='+encodeURIComponent(fp));var d=await r.json();openTabs[fp].content=d.error?'// Error: '+d.error:d.content;}catch(e){openTabs[fp].content='// Failed to load file';}openTabs[fp].modified=false;if(monacoEditor&&activeTabPath===fp){monaco.editor.setModelLanguage(monacoEditor.getModel(),lang);monacoEditor.setValue(openTabs[fp].content);}document.getElementById('sbLang').textContent=lang;renderTabs();document.querySelectorAll('.ft-item').forEach(function(el){el.classList.toggle('active',el.dataset.path===activeTabPath);});}
|
|
1162
|
+
|
|
1163
|
+
async function saveCurrentFile(){if(!activeTabPath||!openTabs[activeTabPath])return;if(monacoEditor)openTabs[activeTabPath].content=monacoEditor.getValue();try{var r=await fetch('/api/file',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:activeTabPath,content:openTabs[activeTabPath].content})});var d=await r.json();if(d.ok){openTabs[activeTabPath].modified=false;renderTabs();}}catch(e){}}
|
|
1164
|
+
|
|
1165
|
+
document.addEventListener('keydown',function(e){if(currentView!=='files')return;if((e.ctrlKey||e.metaKey)&&e.key==='s'){e.preventDefault();saveCurrentFile();}if((e.ctrlKey||e.metaKey)&&e.key==='w'){e.preventDefault();if(activeTabPath)closeTab(activeTabPath);}});
|
|
1166
|
+
FILES/MONACO DISABLED */
|
|
1167
|
+
|
|
1168
|
+
// CHAT / VOICE
|
|
1169
|
+
var ws=null,audioCtx=null,micStream=null,micProcessor=null,micActive=false,cameraStream=null,cameraActive=false,cameraInterval=null,screenStream=null,screenActive=false,screenInterval=null;
|
|
1170
|
+
var statusDot=document.getElementById('statusDot'),statusText=document.getElementById('statusText'),micBtn=document.getElementById('micBtn'),cameraBtn=document.getElementById('cameraBtn'),screenBtn=document.getElementById('screenBtn'),cameraVideo=document.getElementById('cameraVideo'),cameraOff=document.getElementById('cameraOff'),cameraCanvas=document.getElementById('cameraCanvas'),conv=document.getElementById('conversation'),textInput=document.getElementById('textInput');
|
|
1171
|
+
|
|
1172
|
+
function setStatus(t,s){statusText.textContent=t;statusDot.className='status-dot '+(s||'');}
|
|
1173
|
+
function appendConv(h,c){var d=document.createElement('div');d.className='conv-msg '+(c||'');d.innerHTML=h;conv.appendChild(d);conv.scrollTop=conv.scrollHeight;}
|
|
1174
|
+
function connectWS(){if(ws)return;setStatus('Connecting...','');ws=new WebSocket('ws://'+window.location.host);ws.onopen=function(){setStatus('Connected','connected');if(auditMode)loadExplorerTree();};ws.onmessage=function(e){try{handleServerMessage(JSON.parse(e.data));}catch(x){}};ws.onerror=function(){setStatus('Connection error','error');};ws.onclose=function(){ws=null;setStatus('Disconnected','');stopMic();stopCamera();stopScreen();};}
|
|
1175
|
+
function sendMsg(m){if(ws&&ws.readyState===WebSocket.OPEN)ws.send(JSON.stringify(m));}
|
|
1176
|
+
|
|
1177
|
+
// Agent Vitals
|
|
1178
|
+
var vitalsStart=Date.now(), vitalsTokenCount=0, vitalsMaxTokens=200000;
|
|
1179
|
+
var waveData=[], waveCanvas=document.getElementById('vitalsWaveCanvas'), waveCtx=waveCanvas?waveCanvas.getContext('2d'):null;
|
|
1180
|
+
|
|
1181
|
+
function updateUptime(){
|
|
1182
|
+
var s=Math.floor((Date.now()-vitalsStart)/1000);
|
|
1183
|
+
var h=Math.floor(s/3600), m=Math.floor((s%3600)/60), sec=s%60;
|
|
1184
|
+
var el=document.getElementById('vitalUptime');
|
|
1185
|
+
if(el) el.innerHTML=(h>0?(h+':'):'')+String(m).padStart(2,'0')+':'+String(sec).padStart(2,'0');
|
|
1186
|
+
}
|
|
1187
|
+
setInterval(updateUptime,1000);
|
|
1188
|
+
|
|
1189
|
+
function updateVitalTokens(count){
|
|
1190
|
+
vitalsTokenCount=count;
|
|
1191
|
+
var el=document.getElementById('vitalTokens');
|
|
1192
|
+
var bar=document.getElementById('vitalTokensBar');
|
|
1193
|
+
if(el){
|
|
1194
|
+
var display=count>=1000?(Math.round(count/100)/10)+'k':count;
|
|
1195
|
+
el.innerHTML=display+'<span class="vital-unit">tok</span>';
|
|
1196
|
+
}
|
|
1197
|
+
if(bar) bar.style.width=Math.min(100,Math.round(count/vitalsMaxTokens*100))+'%';
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function pollSystemVitals(){
|
|
1201
|
+
fetch('/api/vitals').then(function(r){return r.json();}).then(function(d){
|
|
1202
|
+
var cpuEl=document.getElementById('vitalCpu'), cpuBar=document.getElementById('vitalCpuBar');
|
|
1203
|
+
var memEl=document.getElementById('vitalMem'), memBar=document.getElementById('vitalMemBar');
|
|
1204
|
+
if(cpuEl&&d.cpu!==undefined){cpuEl.innerHTML=Math.round(d.cpu)+'<span class="vital-unit">%</span>';if(cpuBar)cpuBar.style.width=Math.round(d.cpu)+'%';}
|
|
1205
|
+
if(memEl&&d.mem!==undefined){
|
|
1206
|
+
var mb=Math.round(d.mem);
|
|
1207
|
+
var display=mb>=1024?(Math.round(mb/102.4)/10)+'GB':mb+'MB';
|
|
1208
|
+
var unit=mb>=1024?'':'MB';
|
|
1209
|
+
memEl.innerHTML=display.replace(/[A-Z]+$/,'')+'<span class="vital-unit">'+display.replace(/^[\d.]+/,'')+'</span>';
|
|
1210
|
+
if(memBar)memBar.style.width=Math.min(100,Math.round(mb/4096*100))+'%';
|
|
1211
|
+
}
|
|
1212
|
+
if(d.tokens!==undefined) updateVitalTokens(d.tokens);
|
|
1213
|
+
// Push to wave
|
|
1214
|
+
waveData.push(d.cpu||0);
|
|
1215
|
+
if(waveData.length>60) waveData.shift();
|
|
1216
|
+
}).catch(function(){});
|
|
1217
|
+
}
|
|
1218
|
+
setInterval(pollSystemVitals,2000);
|
|
1219
|
+
pollSystemVitals();
|
|
1220
|
+
|
|
1221
|
+
function drawWave(){
|
|
1222
|
+
if(!waveCanvas||!waveCtx) return;
|
|
1223
|
+
var w=waveCanvas.width=waveCanvas.offsetWidth*2, h=waveCanvas.height=waveCanvas.offsetHeight*2;
|
|
1224
|
+
waveCtx.clearRect(0,0,w,h);
|
|
1225
|
+
if(waveData.length<2){requestAnimationFrame(drawWave);return;}
|
|
1226
|
+
waveCtx.strokeStyle='#f85149'; waveCtx.lineWidth=1.5; waveCtx.shadowColor='#f85149'; waveCtx.shadowBlur=4;
|
|
1227
|
+
waveCtx.beginPath();
|
|
1228
|
+
var step=w/(waveData.length-1);
|
|
1229
|
+
for(var i=0;i<waveData.length;i++){
|
|
1230
|
+
var val=waveData[i]/100;
|
|
1231
|
+
var y=h-val*(h-4)-2;
|
|
1232
|
+
if(i===0)waveCtx.moveTo(0,y);else{
|
|
1233
|
+
var px=(i-1)*step,py=h-(waveData[i-1]/100)*(h-4)-2;
|
|
1234
|
+
var cx=(px+i*step)/2;
|
|
1235
|
+
waveCtx.bezierCurveTo(cx,py,cx,y,i*step,y);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
waveCtx.stroke();
|
|
1239
|
+
// Glow line
|
|
1240
|
+
waveCtx.shadowBlur=0; waveCtx.strokeStyle='rgba(248,81,73,0.15)'; waveCtx.lineWidth=6;
|
|
1241
|
+
waveCtx.beginPath();
|
|
1242
|
+
for(var i=0;i<waveData.length;i++){
|
|
1243
|
+
var val=waveData[i]/100;var y=h-val*(h-4)-2;
|
|
1244
|
+
if(i===0)waveCtx.moveTo(0,y);else{
|
|
1245
|
+
var px=(i-1)*step,py=h-(waveData[i-1]/100)*(h-4)-2;
|
|
1246
|
+
var cx=(px+i*step)/2;
|
|
1247
|
+
waveCtx.bezierCurveTo(cx,py,cx,y,i*step,y);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
waveCtx.stroke();
|
|
1251
|
+
requestAnimationFrame(drawWave);
|
|
1252
|
+
}
|
|
1253
|
+
requestAnimationFrame(drawWave);
|
|
1254
|
+
|
|
1255
|
+
var assistantText='',thinkingEl=null,toolActivityEl=null,toolCount=0,isReplay=false,toolVerb='';
|
|
1256
|
+
var toolVerbs=['susurrating','skulking','slithering','shuffling','skimming','scudding','swishing','sparkling','twinkling','glowing','shimmering','murmuring','lilting','drifting','billowing','meandering','hovering','skirling','thrumming','wending','gadding','loitering','brooding','looming','haunting','seething','flickering','glimmering','whispering','rustling','eddying','trembling','quivering','wavering','gliding','cascading','rippling','tumbling','stirring','wandering'];
|
|
1257
|
+
var spriteMap=[
|
|
1258
|
+
[0,0,1,1,1,1,1,1,0,0],[0,1,2,2,2,2,2,2,1,0],[1,2,2,2,2,2,2,2,2,1],
|
|
1259
|
+
[1,2,1,2,2,2,2,1,2,1],[1,2,1,2,2,2,2,1,2,1],[1,2,2,2,2,2,2,2,2,1],
|
|
1260
|
+
[1,2,2,2,2,2,2,2,2,1],[0,1,2,2,2,2,2,2,1,0],[1,2,2,1,2,2,1,2,2,1],[0,1,1,0,1,1,0,1,1,0]
|
|
1261
|
+
];
|
|
1262
|
+
function buildSprite(){
|
|
1263
|
+
var s=document.createElement('div');s.className='tool-sprite';
|
|
1264
|
+
spriteMap.flat().forEach(function(v){var p=document.createElement('div');p.className='px '+(v===1?'px-o':v===2?'px-f':'px-e');s.appendChild(p);});
|
|
1265
|
+
return s;
|
|
1266
|
+
}
|
|
1267
|
+
(function(){var el=document.getElementById('headerLogo');if(el)spriteMap.flat().forEach(function(v){var p=document.createElement('div');p.className='px '+(v===1?'px-o':v===2?'px-f':'px-e');el.appendChild(p);});})();
|
|
1268
|
+
function getToolActivity(){
|
|
1269
|
+
if(!toolActivityEl){
|
|
1270
|
+
toolActivityEl=document.createElement('div');
|
|
1271
|
+
toolActivityEl.className='conv-msg tool-activity';
|
|
1272
|
+
toolVerb=toolVerbs[Math.floor(Math.random()*toolVerbs.length)];
|
|
1273
|
+
var sprite=buildSprite();
|
|
1274
|
+
var verb=document.createElement('span');verb.className='tool-verb';verb.textContent='< '+toolVerb+'... >';
|
|
1275
|
+
var body=document.createElement('div');body.className='tool-activity-body';
|
|
1276
|
+
var sum=document.createElement('div');sum.className='tool-activity-summary';sum.style.display='none';
|
|
1277
|
+
toolActivityEl.appendChild(sprite);
|
|
1278
|
+
toolActivityEl.appendChild(verb);
|
|
1279
|
+
toolActivityEl.appendChild(body);
|
|
1280
|
+
toolActivityEl.appendChild(sum);
|
|
1281
|
+
conv.appendChild(toolActivityEl);
|
|
1282
|
+
toolCount=0;
|
|
1283
|
+
}
|
|
1284
|
+
return toolActivityEl;
|
|
1285
|
+
}
|
|
1286
|
+
function collapseToolActivity(){
|
|
1287
|
+
if(toolActivityEl&&toolCount>0){
|
|
1288
|
+
var el=toolActivityEl;
|
|
1289
|
+
var body=el.querySelector('.tool-activity-body');
|
|
1290
|
+
var sprite=el.querySelector('.tool-sprite');
|
|
1291
|
+
var verb=el.querySelector('.tool-verb');
|
|
1292
|
+
var sum=el.querySelector('.tool-activity-summary');
|
|
1293
|
+
var tc=toolCount;
|
|
1294
|
+
body.style.display='none';
|
|
1295
|
+
if(sprite)sprite.style.display='none';
|
|
1296
|
+
if(verb)verb.style.display='none';
|
|
1297
|
+
sum.style.display='';
|
|
1298
|
+
sum.innerHTML='<span class="tool-summary-toggle">▸ Used '+tc+' tool'+(tc!==1?'s':'')+'</span>';
|
|
1299
|
+
if(!isReplay) sum.style.animation='toolSummaryIn 0.2s ease-out';
|
|
1300
|
+
el.style.height='auto';
|
|
1301
|
+
sum.onclick=function(){
|
|
1302
|
+
var isHidden=body.style.display==='none';
|
|
1303
|
+
body.style.display=isHidden?'':'none';
|
|
1304
|
+
el.classList.toggle('expanded',isHidden);
|
|
1305
|
+
sum.querySelector('.tool-summary-toggle').textContent=(isHidden?'▾':'▸')+' Used '+tc+' tool'+(tc!==1?'s':'');
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
toolActivityEl=null;
|
|
1309
|
+
toolCount=0;
|
|
1310
|
+
toolVerb='';
|
|
1311
|
+
}
|
|
1312
|
+
function handleServerMessage(m){switch(m.type){
|
|
1313
|
+
case'audio_delta':playAudioDelta(m.audio);break;
|
|
1314
|
+
case'interrupt':flushAudio();break;
|
|
1315
|
+
case'transcript':
|
|
1316
|
+
if(m.role==='user'&&!m.partial){
|
|
1317
|
+
var isTg=m.text&&m.text.startsWith('[Telegram]');
|
|
1318
|
+
var isWa=m.text&&m.text.startsWith('[WhatsApp]');
|
|
1319
|
+
var label=isTg?'<span class="label tg">Telegram: </span>':isWa?'<span class="label wa">WhatsApp: </span>':'<span class="label">You: </span>';
|
|
1320
|
+
var cleanText=isTg?m.text.replace(/^\[Telegram\]\s*/,''):isWa?m.text.replace(/^\[WhatsApp\]:\s*/,''):m.text;
|
|
1321
|
+
appendConv(label+escHtml(cleanText),isTg?'user telegram':isWa?'user whatsapp':'user');
|
|
1322
|
+
}
|
|
1323
|
+
else if(m.role==='assistant'){if(m.partial)assistantText+=m.text;else{collapseToolActivity();if(assistantText){appendConv('<span class="label">{{AGENT_NAME}}: </span>'+escHtml(assistantText),'assistant');assistantText='';}else appendConv('<span class="label">{{AGENT_NAME}}: </span>'+escHtml(m.text),'assistant');}}
|
|
1324
|
+
break;
|
|
1325
|
+
case'agent_working':
|
|
1326
|
+
var awVerb=toolVerbs[Math.floor(Math.random()*toolVerbs.length)];
|
|
1327
|
+
var awEl=document.createElement('div');awEl.className='conv-msg agent-working';
|
|
1328
|
+
var awSpinner=document.createElement('div');awSpinner.className='agent-working-spinner';awEl.appendChild(awSpinner);
|
|
1329
|
+
var awText=document.createElement('span');awText.className='agent-working-text';
|
|
1330
|
+
awText.innerHTML='<span class="agent-working-name">gitclaw</span> <span class="agent-working-verb">< '+escHtml(awVerb)+'... ></span> <span class="agent-working-sep">:</span> <span class="agent-working-query">'+escHtml(m.query)+'</span>';
|
|
1331
|
+
awEl.appendChild(awText);
|
|
1332
|
+
conv.appendChild(awEl);conv.scrollTop=conv.scrollHeight;
|
|
1333
|
+
setStatus('Agent working...','connected');break;
|
|
1334
|
+
case'agent_done':
|
|
1335
|
+
thinkingEl=null;
|
|
1336
|
+
collapseToolActivity();
|
|
1337
|
+
conv.querySelectorAll('.conv-msg.agent-working').forEach(function(el){el.remove();});
|
|
1338
|
+
setStatus('Connected','connected');
|
|
1339
|
+
if(filesLoaded)loadFileTree();
|
|
1340
|
+
refreshFileTree();break;
|
|
1341
|
+
case'memory_saving':
|
|
1342
|
+
if(m.status==='start'){
|
|
1343
|
+
var msEl=document.createElement('div');msEl.className='conv-msg memory-saving';msEl.id='memory-saving-indicator';
|
|
1344
|
+
var msSpinner=document.createElement('div');msSpinner.className='memory-saving-spinner';msEl.appendChild(msSpinner);
|
|
1345
|
+
var msText=document.createElement('span');msText.className='memory-saving-text';msText.textContent='< remembering... >';msEl.appendChild(msText);
|
|
1346
|
+
if(m.text){var msDetail=document.createElement('span');msDetail.className='memory-saving-detail';msDetail.textContent=m.text;msEl.appendChild(msDetail);}
|
|
1347
|
+
conv.appendChild(msEl);conv.scrollTop=conv.scrollHeight;
|
|
1348
|
+
} else {
|
|
1349
|
+
var existing=document.getElementById('memory-saving-indicator');
|
|
1350
|
+
if(existing)existing.remove();
|
|
1351
|
+
}
|
|
1352
|
+
break;
|
|
1353
|
+
case'tool_call':
|
|
1354
|
+
thinkingEl=null;
|
|
1355
|
+
toolCount++;
|
|
1356
|
+
if(isReplay){
|
|
1357
|
+
var ta=getToolActivity();
|
|
1358
|
+
var cur=ta.querySelector('.tool-activity-body');
|
|
1359
|
+
cur.innerHTML='<span class="tool-call">'+friendlyToolCall(m.toolName,m.args)+'</span>';
|
|
1360
|
+
} else {
|
|
1361
|
+
var ta=getToolActivity();
|
|
1362
|
+
var cur=ta.querySelector('.tool-activity-body');
|
|
1363
|
+
cur.innerHTML='<span class="tool-call">'+friendlyToolCall(m.toolName,m.args)+'</span>';
|
|
1364
|
+
cur.style.display='';
|
|
1365
|
+
conv.scrollTop=conv.scrollHeight;
|
|
1366
|
+
}
|
|
1367
|
+
// In audit mode, track which file the agent is about to write
|
|
1368
|
+
if(auditMode){
|
|
1369
|
+
var writePath=detectFileWritePath(m.toolName,m.args);
|
|
1370
|
+
if(writePath)lastToolWritePath=writePath;
|
|
1371
|
+
}
|
|
1372
|
+
break;
|
|
1373
|
+
case'tool_result':
|
|
1374
|
+
var ta2=getToolActivity();
|
|
1375
|
+
var cur2=ta2.querySelector('.tool-activity-body');
|
|
1376
|
+
if(m.isError){
|
|
1377
|
+
cur2.innerHTML='<span class="tool-result thinking-fallback"><span class="label">thinking...</span></span>';
|
|
1378
|
+
} else {
|
|
1379
|
+
cur2.innerHTML='<span class="tool-result">'+friendlyToolResult(m.toolName,m.content,false)+'</span>';
|
|
1380
|
+
}
|
|
1381
|
+
if(!isReplay)conv.scrollTop=conv.scrollHeight;
|
|
1382
|
+
// In audit mode, auto-open the file that was just written
|
|
1383
|
+
if(auditMode&&lastToolWritePath&&!m.isError){
|
|
1384
|
+
var autoPath=lastToolWritePath;
|
|
1385
|
+
lastToolWritePath=null;
|
|
1386
|
+
auditAutoOpenFile(autoPath);
|
|
1387
|
+
}
|
|
1388
|
+
refreshFileTree();break;
|
|
1389
|
+
case'agent_thinking':
|
|
1390
|
+
if(!thinkingEl){thinkingEl=document.createElement('div');thinkingEl.className='conv-msg thinking';thinkingEl.innerHTML='<span class="label">Thinking: </span><span class="thinking-text"></span>';conv.appendChild(thinkingEl);}
|
|
1391
|
+
thinkingEl.querySelector('.thinking-text').textContent+=m.text;
|
|
1392
|
+
conv.scrollTop=conv.scrollHeight;
|
|
1393
|
+
break;
|
|
1394
|
+
case'files_changed':refreshFileTree();break;
|
|
1395
|
+
case'error':appendConv('Error: '+escHtml(m.message),'system');break;
|
|
1396
|
+
}}
|
|
1397
|
+
function escHtml(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML;}
|
|
1398
|
+
|
|
1399
|
+
// Parse composio tool names like "composio_googlecalendar_GOOGLECALENDAR_EVENTS_LIST" into friendly text
|
|
1400
|
+
function parseComposioName(name){
|
|
1401
|
+
var raw=name.replace(/^composio_/,'');
|
|
1402
|
+
// Split on first underscore to get service, then action
|
|
1403
|
+
var parts=raw.split('_');
|
|
1404
|
+
var service=parts[0].toLowerCase();
|
|
1405
|
+
// The action is everything after the service prefix (which repeats)
|
|
1406
|
+
// e.g. "googlecalendar_GOOGLECALENDAR_EVENTS_LIST" → action = "EVENTS_LIST"
|
|
1407
|
+
var actionRaw=raw.replace(new RegExp('^[^_]+_'+service+'_?','i'),'');
|
|
1408
|
+
if(!actionRaw) actionRaw=parts.slice(1).join('_');
|
|
1409
|
+
var action=actionRaw.toLowerCase().replace(/_/g,' ');
|
|
1410
|
+
// Friendly service names
|
|
1411
|
+
var services={google:'Google',googlecalendar:'Google Calendar',gmail:'Gmail',
|
|
1412
|
+
github:'GitHub',slack:'Slack',notion:'Notion',drive:'Google Drive',
|
|
1413
|
+
googledrive:'Google Drive',sheets:'Google Sheets',googlesheets:'Google Sheets',
|
|
1414
|
+
outlook:'Outlook',discord:'Discord',trello:'Trello',linear:'Linear',
|
|
1415
|
+
jira:'Jira',asana:'Asana',hubspot:'HubSpot',salesforce:'Salesforce',
|
|
1416
|
+
spotify:'Spotify',twitter:'Twitter',youtube:'YouTube'};
|
|
1417
|
+
var friendlyService=services[service]||service.charAt(0).toUpperCase()+service.slice(1);
|
|
1418
|
+
// Friendly action verbs
|
|
1419
|
+
var actionMap={
|
|
1420
|
+
'events list':'checking events','find event':'finding event','create event':'creating event',
|
|
1421
|
+
'delete event':'deleting event','update event':'updating event','quick add':'adding event',
|
|
1422
|
+
'send email':'sending email','create email draft':'drafting email',
|
|
1423
|
+
'list emails':'checking emails','get email':'reading email','fetch emails':'checking emails',
|
|
1424
|
+
'list messages':'checking messages','send message':'sending message',
|
|
1425
|
+
'get message':'reading message','search messages':'searching messages',
|
|
1426
|
+
'list labels':'checking labels','get profile':'checking profile',
|
|
1427
|
+
'list repos':'checking repos','create issue':'creating issue',
|
|
1428
|
+
'list issues':'checking issues','get repo':'checking repo',
|
|
1429
|
+
'create pr':'creating pull request','list prs':'checking pull requests',
|
|
1430
|
+
'search contacts':'searching contacts','list contacts':'checking contacts',
|
|
1431
|
+
'find contact':'finding contact','get contact':'looking up contact',
|
|
1432
|
+
};
|
|
1433
|
+
var friendlyAction=actionMap[action]||action;
|
|
1434
|
+
return {service:friendlyService,action:friendlyAction};
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// Summarize composio args into a short human string
|
|
1438
|
+
function summarizeArgs(args){
|
|
1439
|
+
var a=args||{};
|
|
1440
|
+
var parts=[];
|
|
1441
|
+
// Calendar
|
|
1442
|
+
if(a.calendarId&&a.calendarId!=='primary') parts.push(a.calendarId);
|
|
1443
|
+
if(a.summary) parts.push('"'+a.summary+'"');
|
|
1444
|
+
if(a.query||a.q) parts.push('"'+(a.query||a.q)+'"');
|
|
1445
|
+
// Email
|
|
1446
|
+
if(a.to||a.recipient) parts.push('to '+(a.to||a.recipient));
|
|
1447
|
+
if(a.subject) parts.push('"'+a.subject+'"');
|
|
1448
|
+
// General
|
|
1449
|
+
if(a.name&&!a.phone) parts.push(a.name);
|
|
1450
|
+
if(a.message&&!a.to) parts.push('"'+a.message.slice(0,60)+'"');
|
|
1451
|
+
if(a.owner&&a.repo) parts.push(a.owner+'/'+a.repo);
|
|
1452
|
+
if(a.channel) parts.push('#'+a.channel);
|
|
1453
|
+
return parts.length?parts.join(', '):'';
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// Parse composio JSON result into something readable
|
|
1457
|
+
function summarizeResult(name,content){
|
|
1458
|
+
try{
|
|
1459
|
+
var d=JSON.parse(content);
|
|
1460
|
+
var data=d.data||d;
|
|
1461
|
+
// Calendar events
|
|
1462
|
+
if(data.items&&Array.isArray(data.items)){
|
|
1463
|
+
if(data.items.length===0) return 'No events found';
|
|
1464
|
+
var evts=data.items.slice(0,5).map(function(e){
|
|
1465
|
+
var when='';
|
|
1466
|
+
if(e.start){
|
|
1467
|
+
var dt=e.start.dateTime||e.start.date||'';
|
|
1468
|
+
if(dt){try{var dd=new Date(dt);when=' ('+dd.toLocaleDateString('en-US',{weekday:'short',month:'short',day:'numeric'})+', '+dd.toLocaleTimeString('en-US',{hour:'numeric',minute:'2-digit'})+')';}catch(x){}}
|
|
1469
|
+
}
|
|
1470
|
+
return (e.summary||'Untitled')+when;
|
|
1471
|
+
});
|
|
1472
|
+
var more=data.items.length>5?' and '+(data.items.length-5)+' more':'';
|
|
1473
|
+
return 'Found '+data.items.length+' event'+(data.items.length!==1?'s':'')+':\n'+evts.join('\n')+more;
|
|
1474
|
+
}
|
|
1475
|
+
// Email list
|
|
1476
|
+
if(data.messages&&Array.isArray(data.messages)){
|
|
1477
|
+
if(data.messages.length===0) return 'No messages found';
|
|
1478
|
+
return 'Found '+data.messages.length+' message'+(data.messages.length!==1?'s':'');
|
|
1479
|
+
}
|
|
1480
|
+
// Single email/event
|
|
1481
|
+
if(data.summary&&data.start) return 'Event: '+data.summary;
|
|
1482
|
+
if(data.subject) return (data.from?'From '+data.from+': ':'')+data.subject;
|
|
1483
|
+
// Contact
|
|
1484
|
+
if(data.emailAddress||data.email) return (data.displayName||data.name||'Contact')+' — '+(data.emailAddress||data.email);
|
|
1485
|
+
if(Array.isArray(data)&&data.length>0&&data[0].emailAddress) return data.map(function(c){return (c.displayName||c.name||'?')+' <'+(c.emailAddress||c.email)+'>';}).slice(0,5).join('\n');
|
|
1486
|
+
// GitHub
|
|
1487
|
+
if(data.full_name&&data.html_url) return data.full_name;
|
|
1488
|
+
if(data.title&&data.number) return '#'+data.number+' '+data.title;
|
|
1489
|
+
// Generic success
|
|
1490
|
+
if(d.successful===true){
|
|
1491
|
+
if(data.id||data.htmlLink) return 'Done';
|
|
1492
|
+
if(typeof data==='string') return data.slice(0,200);
|
|
1493
|
+
}
|
|
1494
|
+
if(d.successful===false) return 'Failed: '+(d.error||'unknown error');
|
|
1495
|
+
}catch(e){}
|
|
1496
|
+
return null; // couldn't parse, use fallback
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function friendlyToolCall(name,args){
|
|
1500
|
+
var a=args||{};
|
|
1501
|
+
switch(name){
|
|
1502
|
+
case'send_whatsapp_message':
|
|
1503
|
+
return '<span class="label">Sending WhatsApp to '+escHtml(a.to||'?')+':</span> '+escHtml(a.message||'');
|
|
1504
|
+
case'save_whatsapp_contact':
|
|
1505
|
+
return '<span class="label">Saving contact:</span> '+escHtml(a.name||'?')+' ('+escHtml(a.phone||'?')+')';
|
|
1506
|
+
case'list_whatsapp_contacts':
|
|
1507
|
+
return '<span class="label">Looking up contacts...</span>';
|
|
1508
|
+
case'create_trigger':
|
|
1509
|
+
return '<span class="label">Creating trigger:</span> when '+escHtml(a.from||'anyone')+' says "'+escHtml(a.pattern||'')+'" → reply "'+escHtml(a.reply||'')+'"';
|
|
1510
|
+
case'list_triggers':
|
|
1511
|
+
return '<span class="label">Checking triggers...</span>';
|
|
1512
|
+
case'delete_trigger':
|
|
1513
|
+
return '<span class="label">Deleting trigger</span> '+escHtml(a.id||'');
|
|
1514
|
+
case'toggle_trigger':
|
|
1515
|
+
return '<span class="label">'+(a.enabled?'Enabling':'Disabling')+' trigger</span> '+escHtml(a.id||'');
|
|
1516
|
+
default:
|
|
1517
|
+
if(name.startsWith('composio_')){
|
|
1518
|
+
var p=parseComposioName(name);
|
|
1519
|
+
var detail=summarizeArgs(a);
|
|
1520
|
+
return '<span class="label">'+escHtml(p.service)+':</span> '+escHtml(p.action)+(detail?' — '+escHtml(detail):'');
|
|
1521
|
+
}
|
|
1522
|
+
// Detect skill usage from file reads/globs targeting skills/ directory
|
|
1523
|
+
var skillPath=a.file_path||a.path||a.command||'';
|
|
1524
|
+
var skillMatch=skillPath.match(/skills\/([^\/]+)/);
|
|
1525
|
+
if(skillMatch){
|
|
1526
|
+
var skillName=skillMatch[1];
|
|
1527
|
+
return '<span class="label skill-label">Using skill:</span> <span class="skill-name">'+escHtml('skills/'+skillName)+'</span>';
|
|
1528
|
+
}
|
|
1529
|
+
var argsStr=JSON.stringify(a);
|
|
1530
|
+
var preview=argsStr.length>120?argsStr.slice(0,120)+'...':argsStr;
|
|
1531
|
+
return '<span class="label">'+escHtml(name)+'</span><span class="tool-args">'+escHtml(preview)+'</span>';
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
function friendlyToolResult(name,content,isError){
|
|
1536
|
+
if(isError) return '<span class="label" style="color:#f85149">Failed: </span>'+escHtml(content.slice(0,200));
|
|
1537
|
+
switch(name){
|
|
1538
|
+
case'send_whatsapp_message':
|
|
1539
|
+
case'save_whatsapp_contact':
|
|
1540
|
+
return '<span class="label" style="color:#3fb950">✓</span> '+escHtml(content);
|
|
1541
|
+
case'list_whatsapp_contacts':
|
|
1542
|
+
if(content.startsWith('No saved')) return '<span class="label">No contacts saved yet</span>';
|
|
1543
|
+
return '<span class="label">Contacts:</span><div class="tool-content">'+escHtml(content)+'</div>';
|
|
1544
|
+
case'create_trigger':
|
|
1545
|
+
return '<span class="label" style="color:#3fb950">✓ Trigger set up</span>';
|
|
1546
|
+
case'list_triggers':
|
|
1547
|
+
if(content.startsWith('No triggers')) return '<span class="label">No triggers set up</span>';
|
|
1548
|
+
return '<span class="label">Triggers:</span><div class="tool-content">'+escHtml(content)+'</div>';
|
|
1549
|
+
case'delete_trigger':
|
|
1550
|
+
case'toggle_trigger':
|
|
1551
|
+
return '<span class="label" style="color:#3fb950">✓</span> '+escHtml(content);
|
|
1552
|
+
default:
|
|
1553
|
+
if(name.startsWith('composio_')){
|
|
1554
|
+
var friendly=summarizeResult(name,content);
|
|
1555
|
+
if(friendly) return '<span class="label" style="color:#3fb950">✓</span> '+escHtml(friendly).replace(/\n/g,'<br>');
|
|
1556
|
+
// Fallback: just show success/truncated
|
|
1557
|
+
var preview=content.length>200?content.slice(0,200)+'...':content;
|
|
1558
|
+
return '<span class="label" style="color:#3fb950">✓</span> Done';
|
|
1559
|
+
}
|
|
1560
|
+
var preview=content.length>300?content.slice(0,300)+'...':content;
|
|
1561
|
+
return '<span class="label">'+escHtml(name)+':</span><div class="tool-content">'+escHtml(preview)+'</div>';
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
var audioQueue=[],isPlaying=false,currentSource=null,nextPlayTime=0;
|
|
1566
|
+
function flushAudio(){if(currentSource){try{currentSource.stop();}catch(e){}}currentSource=null;audioQueue=[];nextPlayTime=0;isPlaying=false;}
|
|
1567
|
+
function playAudioDelta(b){if(!audioCtx)audioCtx=new(window.AudioContext||window.webkitAudioContext)({sampleRate:24000});var bin=atob(b),bytes=new Uint8Array(bin.length);for(var i=0;i<bin.length;i++)bytes[i]=bin.charCodeAt(i);var i16=new Int16Array(bytes.buffer),f32=new Float32Array(i16.length);for(var i=0;i<i16.length;i++)f32[i]=i16[i]/32768;var buf=audioCtx.createBuffer(1,f32.length,24000);buf.getChannelData(0).set(f32);audioQueue.push(buf);if(!isPlaying)playNext();}
|
|
1568
|
+
function playNext(){if(audioQueue.length===0){isPlaying=false;return;}isPlaying=true;var buf=audioQueue.shift(),src=audioCtx.createBufferSource();src.buffer=buf;src.connect(audioCtx.destination);var now=audioCtx.currentTime,st=Math.max(now,nextPlayTime);src.start(st);nextPlayTime=st+buf.duration;src.onended=function(){if(audioQueue.length>0)playNext();else isPlaying=false;};currentSource=src;}
|
|
1569
|
+
|
|
1570
|
+
async function toggleMic(){if(micActive)stopMic();else await startMic();}
|
|
1571
|
+
async function startMic(){connectWS();try{micStream=await navigator.mediaDevices.getUserMedia({audio:true});}catch(e){appendConv('Microphone access denied','system');return;}if(!audioCtx)audioCtx=new(window.AudioContext||window.webkitAudioContext)({sampleRate:24000});var src=audioCtx.createMediaStreamSource(micStream);micProcessor=audioCtx.createScriptProcessor(4096,1,1);src.connect(micProcessor);micProcessor.connect(audioCtx.destination);micProcessor.onaudioprocess=function(e){if(!micActive||!ws||ws.readyState!==WebSocket.OPEN)return;var inp=e.inputBuffer.getChannelData(0),i16=new Int16Array(inp.length);for(var i=0;i<inp.length;i++)i16[i]=Math.max(-32768,Math.min(32767,Math.floor(inp[i]*32768)));sendMsg({type:'audio',audio:btoa(String.fromCharCode.apply(null,new Uint8Array(i16.buffer)))});};micActive=true;micBtn.classList.add('active');}
|
|
1572
|
+
function stopMic(){micActive=false;micBtn.classList.remove('active');if(micProcessor){micProcessor.disconnect();micProcessor=null;}if(micStream){micStream.getTracks().forEach(function(t){t.stop();});micStream=null;}}
|
|
1573
|
+
|
|
1574
|
+
async function toggleCamera(){if(cameraActive)stopCamera();else await startCamera();}
|
|
1575
|
+
async function startCamera(){connectWS();try{cameraStream=await navigator.mediaDevices.getUserMedia({video:{width:640,height:480}});}catch(e){appendConv('Camera access denied','system');return;}cameraVideo.srcObject=cameraStream;cameraVideo.style.display='block';cameraOff.style.display='none';cameraCanvas.width=640;cameraCanvas.height=480;var ctx=cameraCanvas.getContext('2d');cameraInterval=setInterval(function(){if(!cameraActive||!ws||ws.readyState!==WebSocket.OPEN)return;ctx.drawImage(cameraVideo,0,0,640,480);var u=cameraCanvas.toDataURL('image/jpeg',0.7);sendMsg({type:'video_frame',frame:u.split(',')[1],mimeType:'image/jpeg'});},1000);cameraActive=true;cameraBtn.classList.add('active');}
|
|
1576
|
+
function stopCamera(){cameraActive=false;cameraBtn.classList.remove('active');if(cameraInterval){clearInterval(cameraInterval);cameraInterval=null;}if(cameraStream){cameraStream.getTracks().forEach(function(t){t.stop();});cameraStream=null;}cameraVideo.style.display='none';cameraOff.style.display='';}
|
|
1577
|
+
|
|
1578
|
+
async function toggleScreen(){if(screenActive)stopScreen();else await startScreen();}
|
|
1579
|
+
async function startScreen(){connectWS();try{screenStream=await navigator.mediaDevices.getDisplayMedia({video:{cursor:'always'}});}catch(e){appendConv('Screen sharing cancelled','system');return;}var video=document.createElement('video');video.srcObject=screenStream;video.muted=true;video.playsInline=true;video.play();var canvas=document.createElement('canvas');var ctx=canvas.getContext('2d');screenStream.getVideoTracks()[0].onended=function(){stopScreen();};screenInterval=setInterval(function(){if(!screenActive||!ws||ws.readyState!==WebSocket.OPEN)return;var vw=video.videoWidth||1920,vh=video.videoHeight||1080;var scale=Math.min(960/vw,540/vh,1);canvas.width=Math.round(vw*scale);canvas.height=Math.round(vh*scale);ctx.drawImage(video,0,0,canvas.width,canvas.height);var u=canvas.toDataURL('image/jpeg',0.6);sendMsg({type:'video_frame',frame:u.split(',')[1],mimeType:'image/jpeg',source:'screen'});},2000);screenActive=true;screenBtn.classList.add('active');cameraVideo.srcObject=screenStream;cameraVideo.style.display='block';cameraOff.style.display='none';}
|
|
1580
|
+
function stopScreen(){screenActive=false;screenBtn.classList.remove('active');if(screenInterval){clearInterval(screenInterval);screenInterval=null;}if(screenStream){screenStream.getTracks().forEach(function(t){t.stop();});screenStream=null;}if(!cameraActive){cameraVideo.style.display='none';cameraOff.style.display='';cameraVideo.srcObject=cameraStream;}else{cameraVideo.srcObject=cameraStream;}}
|
|
1581
|
+
|
|
1582
|
+
// FILE ATTACHMENTS
|
|
1583
|
+
var pendingFiles=[];
|
|
1584
|
+
var MAX_FILE_SIZE=10*1024*1024; // 10MB
|
|
1585
|
+
|
|
1586
|
+
function handleFileSelect(fileList){
|
|
1587
|
+
for(var i=0;i<fileList.length;i++){
|
|
1588
|
+
var f=fileList[i];
|
|
1589
|
+
if(f.size>MAX_FILE_SIZE){appendConv('File too large (max 10MB): '+escHtml(f.name),'system');continue;}
|
|
1590
|
+
pendingFiles.push(f);
|
|
1591
|
+
}
|
|
1592
|
+
renderFilePreview();
|
|
1593
|
+
document.getElementById('fileInput').value='';
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function renderFilePreview(){
|
|
1597
|
+
var bar=document.getElementById('filePreviewBar');
|
|
1598
|
+
bar.innerHTML='';
|
|
1599
|
+
if(pendingFiles.length===0){bar.classList.remove('active');return;}
|
|
1600
|
+
bar.classList.add('active');
|
|
1601
|
+
pendingFiles.forEach(function(f,idx){
|
|
1602
|
+
var chip=document.createElement('div');
|
|
1603
|
+
chip.className='file-chip';
|
|
1604
|
+
var preview='';
|
|
1605
|
+
if(f.type.startsWith('image/')&&f._dataUrl){
|
|
1606
|
+
preview='<img src="'+f._dataUrl+'" alt="preview" />';
|
|
1607
|
+
}
|
|
1608
|
+
chip.innerHTML=preview+'<span>'+escHtml(f.name)+'</span><button class="remove-file" onclick="removeFile('+idx+')">×</button>';
|
|
1609
|
+
bar.appendChild(chip);
|
|
1610
|
+
});
|
|
1611
|
+
// Generate image previews
|
|
1612
|
+
pendingFiles.forEach(function(f,idx){
|
|
1613
|
+
if(f.type.startsWith('image/')&&!f._dataUrl){
|
|
1614
|
+
var reader=new FileReader();
|
|
1615
|
+
reader.onload=function(e){f._dataUrl=e.target.result;renderFilePreview();};
|
|
1616
|
+
reader.readAsDataURL(f);
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
function removeFile(idx){pendingFiles.splice(idx,1);renderFilePreview();}
|
|
1622
|
+
|
|
1623
|
+
function readFileAsBase64(file){
|
|
1624
|
+
return new Promise(function(resolve){
|
|
1625
|
+
var reader=new FileReader();
|
|
1626
|
+
reader.onload=function(e){
|
|
1627
|
+
var b64=e.target.result.split(',')[1]||'';
|
|
1628
|
+
resolve(b64);
|
|
1629
|
+
};
|
|
1630
|
+
reader.readAsDataURL(file);
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
async function sendText(){
|
|
1635
|
+
var t=textInput.value.trim();
|
|
1636
|
+
var hasFiles=pendingFiles.length>0;
|
|
1637
|
+
if(!t&&!hasFiles)return;
|
|
1638
|
+
connectWS();
|
|
1639
|
+
|
|
1640
|
+
if(hasFiles){
|
|
1641
|
+
var filesToSend=pendingFiles.slice();
|
|
1642
|
+
pendingFiles=[];
|
|
1643
|
+
renderFilePreview();
|
|
1644
|
+
|
|
1645
|
+
for(var i=0;i<filesToSend.length;i++){
|
|
1646
|
+
var f=filesToSend[i];
|
|
1647
|
+
var b64=await readFileAsBase64(f);
|
|
1648
|
+
var displayText=t||(filesToSend.length===1?'':'');
|
|
1649
|
+
// Show in conversation
|
|
1650
|
+
if(f.type.startsWith('image/')&&f._dataUrl){
|
|
1651
|
+
appendConv('<span class="label">You: </span>'+(t?escHtml(t)+' ':'')+'<br><img src="'+f._dataUrl+'" style="max-width:200px;max-height:150px;border-radius:4px;margin-top:4px;" />','user');
|
|
1652
|
+
} else {
|
|
1653
|
+
appendConv('<span class="label">You: </span>'+(t?escHtml(t)+' ':'')+'[Attached: '+escHtml(f.name)+']','user');
|
|
1654
|
+
}
|
|
1655
|
+
sendMsg({type:'file',name:f.name,mimeType:f.type||'application/octet-stream',data:b64,text:i===0?t:''});
|
|
1656
|
+
t=''; // Only send text with first file
|
|
1657
|
+
}
|
|
1658
|
+
} else {
|
|
1659
|
+
appendConv('<span class="label">You: </span>'+escHtml(t),'user');
|
|
1660
|
+
sendMsg({type:'text',text:t});
|
|
1661
|
+
}
|
|
1662
|
+
textInput.value='';
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
textInput.addEventListener('keydown',function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendText();}});
|
|
1666
|
+
|
|
1667
|
+
// DRAG AND DROP
|
|
1668
|
+
var panelRight=document.querySelector('.panel-right');
|
|
1669
|
+
var dropOverlay=document.getElementById('dropOverlay');
|
|
1670
|
+
var dragCounter=0;
|
|
1671
|
+
|
|
1672
|
+
panelRight.addEventListener('dragenter',function(e){e.preventDefault();dragCounter++;dropOverlay.classList.add('active');});
|
|
1673
|
+
panelRight.addEventListener('dragleave',function(e){e.preventDefault();dragCounter--;if(dragCounter<=0){dragCounter=0;dropOverlay.classList.remove('active');}});
|
|
1674
|
+
panelRight.addEventListener('dragover',function(e){e.preventDefault();});
|
|
1675
|
+
panelRight.addEventListener('drop',function(e){
|
|
1676
|
+
e.preventDefault();dragCounter=0;dropOverlay.classList.remove('active');
|
|
1677
|
+
if(e.dataTransfer.files.length>0)handleFileSelect(e.dataTransfer.files);
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
// CHAT SIDEBAR TOGGLE
|
|
1681
|
+
function toggleChatSidebar(){
|
|
1682
|
+
var sb=document.getElementById('chatSidebar');
|
|
1683
|
+
var tab=document.getElementById('chatEdgeTab');
|
|
1684
|
+
var isCollapsed=sb.classList.toggle('collapsed');
|
|
1685
|
+
tab.classList.toggle('visible',isCollapsed);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
// RESIZE HANDLES
|
|
1689
|
+
(function(){
|
|
1690
|
+
// Files panel horizontal resize
|
|
1691
|
+
var fpHandle=document.getElementById('fpResizeX');
|
|
1692
|
+
var fp=document.getElementById('filesPanel');
|
|
1693
|
+
fpHandle.addEventListener('mousedown',function(e){
|
|
1694
|
+
e.preventDefault();
|
|
1695
|
+
fpHandle.classList.add('active');
|
|
1696
|
+
var startX=e.clientX, startW=fp.offsetWidth;
|
|
1697
|
+
function onMove(e){
|
|
1698
|
+
var w=startW-(e.clientX-startX);
|
|
1699
|
+
if(w<200)w=200;
|
|
1700
|
+
if(w>window.innerWidth*0.6)w=window.innerWidth*0.6;
|
|
1701
|
+
fp.style.width=w+'px';
|
|
1702
|
+
fp.style.transition='none';
|
|
1703
|
+
}
|
|
1704
|
+
function onUp(){
|
|
1705
|
+
fpHandle.classList.remove('active');
|
|
1706
|
+
fp.style.transition='';
|
|
1707
|
+
document.removeEventListener('mousemove',onMove);
|
|
1708
|
+
document.removeEventListener('mouseup',onUp);
|
|
1709
|
+
}
|
|
1710
|
+
document.addEventListener('mousemove',onMove);
|
|
1711
|
+
document.addEventListener('mouseup',onUp);
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
// Diff viewer vertical resize
|
|
1715
|
+
var dvHandle=document.getElementById('dvResizeY');
|
|
1716
|
+
var dv=document.getElementById('diffViewer');
|
|
1717
|
+
dvHandle.addEventListener('mousedown',function(e){
|
|
1718
|
+
e.preventDefault();
|
|
1719
|
+
dvHandle.classList.add('active');
|
|
1720
|
+
var startY=e.clientY, startH=dv.offsetHeight;
|
|
1721
|
+
function onMove(e){
|
|
1722
|
+
var h=startH-(e.clientY-startY);
|
|
1723
|
+
if(h<80)h=80;
|
|
1724
|
+
var maxH=fp.offsetHeight-120;
|
|
1725
|
+
if(h>maxH)h=maxH;
|
|
1726
|
+
dv.style.height=h+'px';
|
|
1727
|
+
dv.style.transition='none';
|
|
1728
|
+
}
|
|
1729
|
+
function onUp(){
|
|
1730
|
+
dvHandle.classList.remove('active');
|
|
1731
|
+
dv.style.transition='';
|
|
1732
|
+
document.removeEventListener('mousemove',onMove);
|
|
1733
|
+
document.removeEventListener('mouseup',onUp);
|
|
1734
|
+
}
|
|
1735
|
+
document.addEventListener('mousemove',onMove);
|
|
1736
|
+
document.addEventListener('mouseup',onUp);
|
|
1737
|
+
});
|
|
1738
|
+
})();
|
|
1739
|
+
|
|
1740
|
+
// CHAT BRANCHES
|
|
1741
|
+
var currentBranch='',chatBranches=[];
|
|
1742
|
+
|
|
1743
|
+
async function loadChatBranches(){
|
|
1744
|
+
try{
|
|
1745
|
+
var r=await fetch('/api/chat/list');
|
|
1746
|
+
var d=await r.json();
|
|
1747
|
+
if(d.error)return;
|
|
1748
|
+
chatBranches=d.chats||[];
|
|
1749
|
+
currentBranch=d.current||'';
|
|
1750
|
+
renderChatList();
|
|
1751
|
+
document.getElementById('branchName').textContent=currentBranch||'main';
|
|
1752
|
+
}catch(e){}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function renderChatList(){
|
|
1756
|
+
var list=document.getElementById('chatList');
|
|
1757
|
+
list.innerHTML='';
|
|
1758
|
+
chatBranches.forEach(function(chat){
|
|
1759
|
+
var item=document.createElement('div');
|
|
1760
|
+
item.className='chat-item'+(chat.branch===currentBranch?' active':'');
|
|
1761
|
+
var icon='<svg class="chat-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
|
|
1762
|
+
var name=chat.name||chat.branch.replace('chat/','');
|
|
1763
|
+
var time=chat.time?'<span class="chat-time">'+escHtml(chat.time)+'</span>':'';
|
|
1764
|
+
var del='<button class="chat-delete" onclick="event.stopPropagation();deleteChat(\''+escHtml(chat.branch)+'\')" title="Delete">×</button>';
|
|
1765
|
+
item.innerHTML=icon+'<span class="chat-name">'+escHtml(name)+'</span>'+time+del;
|
|
1766
|
+
(function(branch){item.onclick=function(){switchChat(branch);};})(chat.branch);
|
|
1767
|
+
list.appendChild(item);
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
async function newChat(){
|
|
1772
|
+
try{
|
|
1773
|
+
var r=await fetch('/api/chat/new',{method:'POST'});
|
|
1774
|
+
var d=await r.json();
|
|
1775
|
+
if(d.error){appendConv('Error: '+escHtml(d.error),'system');return;}
|
|
1776
|
+
currentBranch=d.branch;
|
|
1777
|
+
document.getElementById('branchName').textContent=d.branch;
|
|
1778
|
+
chatBranches.unshift({branch:d.branch,name:d.branch.replace('chat/',''),time:'just now'});
|
|
1779
|
+
renderChatList();
|
|
1780
|
+
// Clear conversation
|
|
1781
|
+
conv.innerHTML='';
|
|
1782
|
+
assistantText='';thinkingEl=null;
|
|
1783
|
+
// Reconnect WS for fresh session
|
|
1784
|
+
if(ws){ws.close();ws=null;}
|
|
1785
|
+
setTimeout(function(){connectWS();loadChatBranches();},300);
|
|
1786
|
+
}catch(e){appendConv('Failed to create new chat','system');}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
async function restoreChatHistory(branch){
|
|
1790
|
+
try{
|
|
1791
|
+
var r=await fetch('/api/chat/history?branch='+encodeURIComponent(branch));
|
|
1792
|
+
var d=await r.json();
|
|
1793
|
+
isReplay=true;
|
|
1794
|
+
if(d.messages)d.messages.forEach(function(m){handleServerMessage(m);});
|
|
1795
|
+
collapseToolActivity();
|
|
1796
|
+
isReplay=false;
|
|
1797
|
+
}catch(e){}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
async function switchChat(branch){
|
|
1801
|
+
if(branch===currentBranch)return;
|
|
1802
|
+
try{
|
|
1803
|
+
var r=await fetch('/api/chat/switch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({branch:branch})});
|
|
1804
|
+
var d=await r.json();
|
|
1805
|
+
if(d.error){appendConv('Error: '+escHtml(d.error),'system');return;}
|
|
1806
|
+
currentBranch=branch;
|
|
1807
|
+
document.getElementById('branchName').textContent=branch;
|
|
1808
|
+
renderChatList();
|
|
1809
|
+
conv.innerHTML='';
|
|
1810
|
+
assistantText='';thinkingEl=null;
|
|
1811
|
+
await restoreChatHistory(branch);
|
|
1812
|
+
if(ws){ws.close();ws=null;}
|
|
1813
|
+
setTimeout(function(){connectWS();loadChatBranches();},300);
|
|
1814
|
+
}catch(e){appendConv('Failed to switch chat','system');}
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
async function deleteChat(branch){
|
|
1818
|
+
if(branch===currentBranch){appendConv('Cannot delete the active chat','system');return;}
|
|
1819
|
+
try{
|
|
1820
|
+
await fetch('/api/chat/delete',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({branch:branch})});
|
|
1821
|
+
loadChatBranches();
|
|
1822
|
+
}catch(e){}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// Load chat list on startup and restore history for current branch
|
|
1826
|
+
loadChatBranches().then(function(){
|
|
1827
|
+
if(currentBranch)restoreChatHistory(currentBranch);
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
// INTEGRATIONS
|
|
1831
|
+
var toolkitConnections={};
|
|
1832
|
+
function showGridMessage(msg){
|
|
1833
|
+
var grid=document.getElementById('integrationsGrid');
|
|
1834
|
+
grid.innerHTML='<div class="integrations-empty">'+escHtml(msg)+'</div>';
|
|
1835
|
+
}
|
|
1836
|
+
async function loadToolkits(){
|
|
1837
|
+
var grid=document.getElementById('integrationsGrid');
|
|
1838
|
+
showGridMessage('Loading...');
|
|
1839
|
+
try{
|
|
1840
|
+
var tr=await fetch('/api/composio/toolkits');
|
|
1841
|
+
if(tr.status===501){showGridMessage('Composio not configured. Set COMPOSIO_API_KEY to enable integrations.');return;}
|
|
1842
|
+
var toolkits=await tr.json();
|
|
1843
|
+
if(toolkits.error){showGridMessage(toolkits.error);return;}
|
|
1844
|
+
// Also fetch connections for status
|
|
1845
|
+
var cr=await fetch('/api/composio/connections');
|
|
1846
|
+
var conns=cr.ok?await cr.json():[];
|
|
1847
|
+
toolkitConnections={};
|
|
1848
|
+
if(Array.isArray(conns))conns.forEach(function(c){toolkitConnections[c.toolkitSlug]=c.id;});
|
|
1849
|
+
if(!Array.isArray(toolkits)||toolkits.length===0){showGridMessage('No toolkits available.');return;}
|
|
1850
|
+
grid.innerHTML='';
|
|
1851
|
+
toolkits.forEach(function(tk){
|
|
1852
|
+
var isConn=tk.connected||!!toolkitConnections[tk.slug];
|
|
1853
|
+
var connId=toolkitConnections[tk.slug]||'';
|
|
1854
|
+
var card=document.createElement('div');card.className='toolkit-card';
|
|
1855
|
+
var logoHtml=tk.logo?'<img class="tk-logo" src="'+escHtml(tk.logo)+'" alt="" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><div class="tk-logo-placeholder" style="display:none">'+escHtml(tk.name.charAt(0))+'</div>':'<div class="tk-logo-placeholder">'+escHtml(tk.name.charAt(0))+'</div>';
|
|
1856
|
+
var btnClass=isConn?'tk-btn connected':'tk-btn connect';
|
|
1857
|
+
var btnLabel=isConn?'Connected':'Connect';
|
|
1858
|
+
card.innerHTML='<div class="tk-top">'+logoHtml+'<span class="tk-name">'+escHtml(tk.name)+'</span></div><div class="tk-desc">'+escHtml(tk.description)+'</div><div class="tk-actions"><button class="'+btnClass+'" data-slug="'+escHtml(tk.slug)+'" data-conn="'+escHtml(connId)+'" onclick="toggleToolkit(this)">'+btnLabel+'</button></div>';
|
|
1859
|
+
grid.appendChild(card);
|
|
1860
|
+
});
|
|
1861
|
+
}catch(e){showGridMessage('Failed to load integrations.');}
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
async function toggleToolkit(btn){
|
|
1865
|
+
var slug=btn.dataset.slug;var connId=btn.dataset.conn;
|
|
1866
|
+
if(connId){
|
|
1867
|
+
// Disconnect
|
|
1868
|
+
btn.textContent='Disconnecting...';btn.disabled=true;
|
|
1869
|
+
try{await fetch('/api/composio/connections/'+encodeURIComponent(connId),{method:'DELETE'});
|
|
1870
|
+
}catch(e){}
|
|
1871
|
+
loadToolkits();
|
|
1872
|
+
}else{
|
|
1873
|
+
// Connect via OAuth
|
|
1874
|
+
btn.textContent='Connecting...';btn.disabled=true;
|
|
1875
|
+
try{
|
|
1876
|
+
var r=await fetch('/api/composio/connect',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({toolkit:slug,redirectUrl:window.location.origin+'/api/composio/callback'})});
|
|
1877
|
+
var d=await r.json();
|
|
1878
|
+
if(d.redirectUrl){window.open(d.redirectUrl,'composio_auth','width=600,height=700');}
|
|
1879
|
+
else{loadToolkits();}
|
|
1880
|
+
}catch(e){btn.textContent='Connect';btn.disabled=false;}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// Listen for OAuth completion from popup
|
|
1885
|
+
window.addEventListener('message',function(e){
|
|
1886
|
+
if(e.data&&e.data.type==='composio_auth_complete'){loadToolkits();}
|
|
1887
|
+
});
|
|
1888
|
+
// Also refresh when window regains focus (in case popup completed)
|
|
1889
|
+
window.addEventListener('focus',function(){if(currentView==='integrations'&&integrationsLoaded)loadToolkits();});
|
|
1890
|
+
|
|
1891
|
+
// ── TELEGRAM / COMMUNICATION ────────────────────────────────────────
|
|
1892
|
+
async function loadTelegramStatus(){
|
|
1893
|
+
var card=document.getElementById('telegramCard');
|
|
1894
|
+
var statusEl=document.getElementById('tgStatus');
|
|
1895
|
+
var btn=document.getElementById('tgConnectBtn');
|
|
1896
|
+
var tokenInput=document.getElementById('tgToken');
|
|
1897
|
+
var configDiv=document.getElementById('tgConfig');
|
|
1898
|
+
try{
|
|
1899
|
+
var r=await fetch('/api/telegram/status');
|
|
1900
|
+
var d=await r.json();
|
|
1901
|
+
var allowedUsersInput=document.getElementById('tgAllowedUsers');
|
|
1902
|
+
var securityDiv=document.getElementById('tgSecurity');
|
|
1903
|
+
var liveInput=document.getElementById('tgAllowedUsersLive');
|
|
1904
|
+
var usersStr=(d.allowedUsers||[]).map(function(u){return '@'+u;}).join(', ');
|
|
1905
|
+
if(d.connected){
|
|
1906
|
+
card.classList.add('is-connected');
|
|
1907
|
+
statusEl.className='comms-status connected';
|
|
1908
|
+
statusEl.textContent='Connected';
|
|
1909
|
+
btn.className='tk-btn connected';
|
|
1910
|
+
btn.textContent='Disconnect';
|
|
1911
|
+
var existing=card.querySelector('.comms-bot-info');
|
|
1912
|
+
if(existing)existing.remove();
|
|
1913
|
+
if(d.botName){
|
|
1914
|
+
var info=document.createElement('div');
|
|
1915
|
+
info.className='comms-bot-info';
|
|
1916
|
+
info.innerHTML='<span class="bot-name">'+escHtml(d.botName)+'</span><span class="bot-username">@'+escHtml(d.botUsername||'')+'</span>';
|
|
1917
|
+
configDiv.parentNode.insertBefore(info,configDiv);
|
|
1918
|
+
}
|
|
1919
|
+
// Show live security editor when connected
|
|
1920
|
+
securityDiv.style.display='';
|
|
1921
|
+
liveInput.value=usersStr;
|
|
1922
|
+
}else{
|
|
1923
|
+
card.classList.remove('is-connected');
|
|
1924
|
+
statusEl.className='comms-status disconnected';
|
|
1925
|
+
statusEl.textContent='Not connected';
|
|
1926
|
+
btn.className='tk-btn connect';
|
|
1927
|
+
btn.textContent='Connect';
|
|
1928
|
+
var existing2=card.querySelector('.comms-bot-info');
|
|
1929
|
+
if(existing2)existing2.remove();
|
|
1930
|
+
if(d.hasToken)tokenInput.placeholder='Token saved (enter new to change)';
|
|
1931
|
+
securityDiv.style.display='none';
|
|
1932
|
+
if(usersStr)allowedUsersInput.value=usersStr;
|
|
1933
|
+
}
|
|
1934
|
+
}catch(e){
|
|
1935
|
+
statusEl.className='comms-status error';
|
|
1936
|
+
statusEl.textContent='Error';
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
async function toggleTelegram(){
|
|
1941
|
+
var card=document.getElementById('telegramCard');
|
|
1942
|
+
var btn=document.getElementById('tgConnectBtn');
|
|
1943
|
+
var tokenInput=document.getElementById('tgToken');
|
|
1944
|
+
|
|
1945
|
+
if(card.classList.contains('is-connected')){
|
|
1946
|
+
btn.disabled=true;btn.textContent='Disconnecting...';
|
|
1947
|
+
try{
|
|
1948
|
+
await fetch('/api/telegram/disconnect',{method:'POST'});
|
|
1949
|
+
}catch(e){}
|
|
1950
|
+
btn.disabled=false;
|
|
1951
|
+
loadTelegramStatus();
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
var token=tokenInput.value.trim();
|
|
1956
|
+
var allowedUsers=document.getElementById('tgAllowedUsers').value.trim();
|
|
1957
|
+
if(!token&&!tokenInput.placeholder.includes('saved')){
|
|
1958
|
+
tokenInput.style.borderColor='#f85149';
|
|
1959
|
+
tokenInput.focus();
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
btn.disabled=true;btn.textContent='Connecting...';
|
|
1963
|
+
tokenInput.style.borderColor='';
|
|
1964
|
+
try{
|
|
1965
|
+
var r=await fetch('/api/telegram/connect',{
|
|
1966
|
+
method:'POST',
|
|
1967
|
+
headers:{'Content-Type':'application/json'},
|
|
1968
|
+
body:JSON.stringify({token:token||undefined,allowedUsers:allowedUsers||undefined})
|
|
1969
|
+
});
|
|
1970
|
+
var d=await r.json();
|
|
1971
|
+
if(d.error){
|
|
1972
|
+
document.getElementById('tgStatus').className='comms-status error';
|
|
1973
|
+
document.getElementById('tgStatus').textContent=d.error;
|
|
1974
|
+
btn.disabled=false;btn.textContent='Connect';
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
}catch(e){
|
|
1978
|
+
btn.disabled=false;btn.textContent='Connect';
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
btn.disabled=false;
|
|
1982
|
+
tokenInput.value='';
|
|
1983
|
+
loadTelegramStatus();
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
async function saveTgAllowedUsers(){
|
|
1987
|
+
var input=document.getElementById('tgAllowedUsersLive');
|
|
1988
|
+
var users=input.value.trim();
|
|
1989
|
+
try{
|
|
1990
|
+
var r=await fetch('/api/telegram/allowed-users',{
|
|
1991
|
+
method:'POST',
|
|
1992
|
+
headers:{'Content-Type':'application/json'},
|
|
1993
|
+
body:JSON.stringify({users:users})
|
|
1994
|
+
});
|
|
1995
|
+
var d=await r.json();
|
|
1996
|
+
if(d.ok){
|
|
1997
|
+
input.style.borderColor='#3fb950';
|
|
1998
|
+
setTimeout(function(){input.style.borderColor='';},1500);
|
|
1999
|
+
}
|
|
2000
|
+
}catch(e){}
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// ── WhatsApp ────────────────────────────────────────────────────────
|
|
2004
|
+
var waQrPollTimer=null;
|
|
2005
|
+
|
|
2006
|
+
function renderQR(canvas,text){
|
|
2007
|
+
var qr=qrcode(0,'L');
|
|
2008
|
+
qr.addData(text);
|
|
2009
|
+
qr.make();
|
|
2010
|
+
var count=qr.getModuleCount();
|
|
2011
|
+
var ctx=canvas.getContext('2d');
|
|
2012
|
+
var size=canvas.width;
|
|
2013
|
+
ctx.fillStyle='#fff';ctx.fillRect(0,0,size,size);
|
|
2014
|
+
var cellSize=Math.floor(size/count);
|
|
2015
|
+
var offset=Math.floor((size-cellSize*count)/2);
|
|
2016
|
+
ctx.fillStyle='#000';
|
|
2017
|
+
for(var r=0;r<count;r++)for(var c=0;c<count;c++){
|
|
2018
|
+
if(qr.isDark(r,c))ctx.fillRect(offset+c*cellSize,offset+r*cellSize,cellSize,cellSize);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
async function loadWhatsAppStatus(){
|
|
2023
|
+
var card=document.getElementById('whatsappCard');
|
|
2024
|
+
var statusEl=document.getElementById('waStatus');
|
|
2025
|
+
var btn=document.getElementById('waConnectBtn');
|
|
2026
|
+
var qrContainer=document.getElementById('waQrContainer');
|
|
2027
|
+
var clearLabel=document.getElementById('waClearLabel');
|
|
2028
|
+
try{
|
|
2029
|
+
var r=await fetch('/api/whatsapp/status');
|
|
2030
|
+
var d=await r.json();
|
|
2031
|
+
if(d.connected){
|
|
2032
|
+
card.classList.add('is-connected');
|
|
2033
|
+
statusEl.className='comms-status connected';
|
|
2034
|
+
statusEl.textContent='Connected';
|
|
2035
|
+
btn.className='tk-btn connected';
|
|
2036
|
+
btn.textContent='Disconnect';
|
|
2037
|
+
clearLabel.style.display='';
|
|
2038
|
+
qrContainer.style.display='none';
|
|
2039
|
+
if(waQrPollTimer){clearInterval(waQrPollTimer);waQrPollTimer=null;}
|
|
2040
|
+
// Show phone number
|
|
2041
|
+
var existing=card.querySelector('.comms-bot-info');
|
|
2042
|
+
if(existing)existing.remove();
|
|
2043
|
+
if(d.phoneNumber){
|
|
2044
|
+
var info=document.createElement('div');
|
|
2045
|
+
info.className='comms-bot-info';
|
|
2046
|
+
info.innerHTML='<span class="bot-name">'+escHtml(d.phoneNumber)+'</span><span class="bot-username">WhatsApp linked</span>';
|
|
2047
|
+
document.getElementById('waConfig').parentNode.insertBefore(info,document.getElementById('waConfig'));
|
|
2048
|
+
}
|
|
2049
|
+
}else if(d.qrCode){
|
|
2050
|
+
card.classList.remove('is-connected');
|
|
2051
|
+
statusEl.className='comms-status scanning';
|
|
2052
|
+
statusEl.textContent='Scanning';
|
|
2053
|
+
btn.className='tk-btn connect';
|
|
2054
|
+
btn.textContent='Cancel';
|
|
2055
|
+
clearLabel.style.display='none';
|
|
2056
|
+
qrContainer.style.display='';
|
|
2057
|
+
renderQR(document.getElementById('waQrCanvas'),d.qrCode);
|
|
2058
|
+
var existing2=card.querySelector('.comms-bot-info');
|
|
2059
|
+
if(existing2)existing2.remove();
|
|
2060
|
+
}else{
|
|
2061
|
+
card.classList.remove('is-connected');
|
|
2062
|
+
statusEl.className='comms-status disconnected';
|
|
2063
|
+
statusEl.textContent='Not connected';
|
|
2064
|
+
btn.className='tk-btn connect';
|
|
2065
|
+
btn.textContent='Connect';
|
|
2066
|
+
clearLabel.style.display='none';
|
|
2067
|
+
qrContainer.style.display='none';
|
|
2068
|
+
var existing3=card.querySelector('.comms-bot-info');
|
|
2069
|
+
if(existing3)existing3.remove();
|
|
2070
|
+
}
|
|
2071
|
+
}catch(e){
|
|
2072
|
+
statusEl.className='comms-status error';
|
|
2073
|
+
statusEl.textContent='Error';
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
async function toggleWhatsApp(){
|
|
2078
|
+
var card=document.getElementById('whatsappCard');
|
|
2079
|
+
var btn=document.getElementById('waConnectBtn');
|
|
2080
|
+
|
|
2081
|
+
if(card.classList.contains('is-connected')){
|
|
2082
|
+
// Disconnect
|
|
2083
|
+
btn.disabled=true;btn.textContent='Disconnecting...';
|
|
2084
|
+
var clearAuth=document.getElementById('waClearAuth').checked;
|
|
2085
|
+
try{
|
|
2086
|
+
await fetch('/api/whatsapp/disconnect',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({clearAuth:clearAuth})});
|
|
2087
|
+
}catch(e){}
|
|
2088
|
+
btn.disabled=false;
|
|
2089
|
+
document.getElementById('waClearAuth').checked=false;
|
|
2090
|
+
if(waQrPollTimer){clearInterval(waQrPollTimer);waQrPollTimer=null;}
|
|
2091
|
+
loadWhatsAppStatus();
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// If currently scanning (cancel)
|
|
2096
|
+
if(document.getElementById('waStatus').textContent==='Scanning'){
|
|
2097
|
+
btn.disabled=true;btn.textContent='Canceling...';
|
|
2098
|
+
try{await fetch('/api/whatsapp/disconnect',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})});}catch(e){}
|
|
2099
|
+
btn.disabled=false;
|
|
2100
|
+
if(waQrPollTimer){clearInterval(waQrPollTimer);waQrPollTimer=null;}
|
|
2101
|
+
loadWhatsAppStatus();
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// Connect
|
|
2106
|
+
btn.disabled=true;btn.textContent='Connecting...';
|
|
2107
|
+
try{
|
|
2108
|
+
var r=await fetch('/api/whatsapp/connect',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})});
|
|
2109
|
+
var d=await r.json();
|
|
2110
|
+
if(d.error){
|
|
2111
|
+
document.getElementById('waStatus').className='comms-status error';
|
|
2112
|
+
document.getElementById('waStatus').textContent=d.error;
|
|
2113
|
+
btn.disabled=false;btn.textContent='Connect';
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
}catch(e){btn.disabled=false;btn.textContent='Connect';return;}
|
|
2117
|
+
btn.disabled=false;
|
|
2118
|
+
// Poll for QR code / connection
|
|
2119
|
+
waQrPollTimer=setInterval(function(){loadWhatsAppStatus();},2000);
|
|
2120
|
+
setTimeout(function(){loadWhatsAppStatus();},500);
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// ── PHONE / TWILIO ──────────────────────────────────────────────────
|
|
2124
|
+
function loadPhoneWebhookUrl(){
|
|
2125
|
+
var el=document.getElementById('phoneWebhookUrl');
|
|
2126
|
+
if(!el) return;
|
|
2127
|
+
var base=window.location.origin;
|
|
2128
|
+
el.textContent=base+'/api/phone/webhook';
|
|
2129
|
+
}
|
|
2130
|
+
function copyWebhookUrl(btn){
|
|
2131
|
+
var url=document.getElementById('phoneWebhookUrl').textContent;
|
|
2132
|
+
navigator.clipboard.writeText(url).then(function(){
|
|
2133
|
+
btn.textContent='Copied!';
|
|
2134
|
+
btn.classList.add('copied');
|
|
2135
|
+
setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
// ── AUDIT MODE & FILE EXPLORER ──────────────────────────────────────
|
|
2140
|
+
var ICONS={chevronRight:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9,18 15,12 9,6"/></svg>',chevronDown:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6,9 12,15 18,9"/></svg>',folder:'<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>',folderOpen:'<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M5 19h14a2 2 0 001.84-1.22L23 12H5.24a2 2 0 00-1.84 1.22L1 19h2a2 2 0 002-2V7a2 2 0 012-2h4l2 2h6a2 2 0 012 2v1"/></svg>',file:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/></svg>',fileCode:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/><path d="M10 12l-2 2 2 2"/><path d="M14 12l2 2-2 2"/></svg>'};
|
|
2141
|
+
|
|
2142
|
+
function getFileIconClass(n){var e=n.split('.').pop().toLowerCase();if(['ts','tsx'].includes(e))return'ts';if(['js','jsx','mjs'].includes(e))return'js';if(e==='json')return'json';if(['css','scss','less'].includes(e))return'css';if(['html','htm'].includes(e))return'html';if(['md','txt','rst'].includes(e))return'md';if(['yaml','yml'].includes(e))return'yaml';if(e==='py')return'py';if(['sh','bash','zsh'].includes(e))return'sh';return'default';}
|
|
2143
|
+
function getFileIconSvg(n){var c=getFileIconClass(n);if(['ts','js','css','html','py','sh'].includes(c))return ICONS.fileCode;return ICONS.file;}
|
|
2144
|
+
|
|
2145
|
+
var auditMode=true;
|
|
2146
|
+
var auditBaselineMtimes=null; // snapshot taken when audit mode turns on — never triggers EDITED
|
|
2147
|
+
var fileTreeMtimes={};
|
|
2148
|
+
var diffOpenPath=null;
|
|
2149
|
+
var diffOpenContent='';
|
|
2150
|
+
var refreshDebounceTimer=null;
|
|
2151
|
+
var lastToolWritePath=null;
|
|
2152
|
+
var auditEditedPaths=new Set(); // files confirmed edited since audit started
|
|
2153
|
+
var fileContentCache={}; // path -> content for diff
|
|
2154
|
+
var autoCloseTimer=null;
|
|
2155
|
+
|
|
2156
|
+
function toggleAuditMode(){
|
|
2157
|
+
auditMode=!auditMode;
|
|
2158
|
+
var toggle=document.getElementById('auditToggle');
|
|
2159
|
+
var panel=document.getElementById('filesPanel');
|
|
2160
|
+
toggle.classList.toggle('active',auditMode);
|
|
2161
|
+
panel.classList.toggle('collapsed',!auditMode);
|
|
2162
|
+
if(auditMode){
|
|
2163
|
+
toggle.classList.add('recording');
|
|
2164
|
+
auditEditedPaths.clear();
|
|
2165
|
+
auditBaselineMtimes=null; // will be set on first tree load
|
|
2166
|
+
fileTreeMtimes={};
|
|
2167
|
+
fileContentCache={};
|
|
2168
|
+
closeDiffViewer();
|
|
2169
|
+
loadExplorerTree();
|
|
2170
|
+
}else{
|
|
2171
|
+
toggle.classList.remove('recording');
|
|
2172
|
+
closeDiffViewer();
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
// Detect file paths from tool_call args for auto-open
|
|
2177
|
+
function detectFileWritePath(toolName,args){
|
|
2178
|
+
if(!args)return null;
|
|
2179
|
+
var tn=toolName.toLowerCase();
|
|
2180
|
+
var isWrite=tn==='write'||tn==='edit'||tn==='write_file'||tn==='edit_file'
|
|
2181
|
+
||tn==='create_file'||tn==='save_file'||tn==='writefile'||tn==='editfile'
|
|
2182
|
+
||tn.indexOf('write')!==-1||tn.indexOf('edit')!==-1;
|
|
2183
|
+
if(!isWrite)return null;
|
|
2184
|
+
var path=args.file_path||args.path||args.filepath||args.filename||args.file||null;
|
|
2185
|
+
if(!path)return null;
|
|
2186
|
+
if(typeof path==='string'&&path.startsWith('/')){
|
|
2187
|
+
var parts=path.split('/');
|
|
2188
|
+
for(var i=parts.length-1;i>=0;i--){
|
|
2189
|
+
if(parts[i]==='memory'||parts[i]==='skills'||parts[i]==='src'||parts[i]==='workspace'){
|
|
2190
|
+
return parts.slice(i).join('/');
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
return parts[parts.length-1];
|
|
2194
|
+
}
|
|
2195
|
+
return path;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// Called after a successful write tool — auto-open inline diff
|
|
2199
|
+
function auditAutoOpenFile(path){
|
|
2200
|
+
auditEditedPaths.add(path);
|
|
2201
|
+
setTimeout(function(){
|
|
2202
|
+
loadExplorerTree();
|
|
2203
|
+
openDiffViewer(path,true,2500);
|
|
2204
|
+
},400);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
function collectMtimes(entries,out){
|
|
2208
|
+
for(var i=0;i<entries.length;i++){
|
|
2209
|
+
var e=entries[i];
|
|
2210
|
+
if(e.type==='file'&&e.mtime)out[e.path]=e.mtime;
|
|
2211
|
+
if(e.children)collectMtimes(e.children,out);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
// Compare mtimes, but only if we have a previous baseline
|
|
2216
|
+
function findChangedSinceLastRefresh(oldMtimes,newMtimes){
|
|
2217
|
+
var changed=[];
|
|
2218
|
+
// If oldMtimes is empty, this is baseline — nothing is "changed"
|
|
2219
|
+
if(Object.keys(oldMtimes).length===0)return changed;
|
|
2220
|
+
for(var p in newMtimes){
|
|
2221
|
+
if(!oldMtimes[p]||newMtimes[p]>oldMtimes[p])changed.push(p);
|
|
2222
|
+
}
|
|
2223
|
+
return changed;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
function updateChangeCount(){
|
|
2227
|
+
var countEl=document.getElementById('fpCount');
|
|
2228
|
+
var n=auditEditedPaths.size;
|
|
2229
|
+
countEl.textContent=n;
|
|
2230
|
+
countEl.classList.toggle('hidden',n===0);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
async function loadExplorerTree(){
|
|
2234
|
+
var t=document.getElementById('explorerTree');
|
|
2235
|
+
try{
|
|
2236
|
+
var r=await fetch('/api/files?path=.');
|
|
2237
|
+
var d=await r.json();
|
|
2238
|
+
var newMtimes={};
|
|
2239
|
+
collectMtimes(d.entries,newMtimes);
|
|
2240
|
+
|
|
2241
|
+
// First load sets the baseline — nothing should be marked changed
|
|
2242
|
+
var justChanged=findChangedSinceLastRefresh(fileTreeMtimes,newMtimes);
|
|
2243
|
+
|
|
2244
|
+
// Identify truly new files (not in previous baseline at all)
|
|
2245
|
+
var justNew=[];
|
|
2246
|
+
if(Object.keys(fileTreeMtimes).length>0){
|
|
2247
|
+
for(var j=0;j<justChanged.length;j++){
|
|
2248
|
+
if(!fileTreeMtimes[justChanged[j]])justNew.push(justChanged[j]);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// Only add to auditEditedPaths if these are real changes (not baseline)
|
|
2253
|
+
justChanged.forEach(function(p){auditEditedPaths.add(p);});
|
|
2254
|
+
fileTreeMtimes=newMtimes;
|
|
2255
|
+
updateChangeCount();
|
|
2256
|
+
t.innerHTML='';
|
|
2257
|
+
renderExplorerTree(d.entries,t,0,justChanged,justNew);
|
|
2258
|
+
|
|
2259
|
+
// Auto-expand parents of just-changed files
|
|
2260
|
+
if(justChanged.length>0){
|
|
2261
|
+
justChanged.forEach(function(cp){
|
|
2262
|
+
var parts=cp.split('/');
|
|
2263
|
+
for(var i=1;i<parts.length;i++){
|
|
2264
|
+
var parentPath=parts.slice(0,i).join('/');
|
|
2265
|
+
t.querySelectorAll('.ft-dir').forEach(function(det){
|
|
2266
|
+
if(det.dataset.path===parentPath)det.open=true;
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
// Scroll the first changed item into view in the tree
|
|
2271
|
+
var firstItem=t.querySelector('.ft-item.changed,.ft-item.new-file,summary.changed,summary.new-file');
|
|
2272
|
+
if(firstItem)firstItem.scrollIntoView({behavior:'smooth',block:'nearest'});
|
|
2273
|
+
|
|
2274
|
+
// Auto-open the changed file in the diff viewer
|
|
2275
|
+
if(diffOpenPath&&justChanged.indexOf(diffOpenPath)!==-1){
|
|
2276
|
+
// Currently-open file was updated — refresh it and scroll to diff
|
|
2277
|
+
openDiffViewer(diffOpenPath,true);
|
|
2278
|
+
}else{
|
|
2279
|
+
// Open the first changed file automatically
|
|
2280
|
+
openDiffViewer(justChanged[0],true);
|
|
2281
|
+
}
|
|
2282
|
+
}else if(diffOpenPath&&justChanged&&justChanged.indexOf(diffOpenPath)!==-1){
|
|
2283
|
+
// No new changes detected but the open file was in the changed set
|
|
2284
|
+
openDiffViewer(diffOpenPath,true);
|
|
2285
|
+
}
|
|
2286
|
+
}catch(e){
|
|
2287
|
+
t.innerHTML='<div style="padding:16px;color:#f85149;font-size:12px;">Failed to load</div>';
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
function renderExplorerTree(entries,parent,depth,justChanged,justNew){
|
|
2292
|
+
for(var i=0;i<entries.length;i++){
|
|
2293
|
+
var entry=entries[i];
|
|
2294
|
+
if(entry.type==='directory'){
|
|
2295
|
+
var dirHasChanges=justChanged&&justChanged.some(function(p){return p.startsWith(entry.path+'/');});
|
|
2296
|
+
var dirHasNew=justNew&&justNew.some(function(p){return p.startsWith(entry.path+'/');});
|
|
2297
|
+
var autoExpand=entry.name==='memory'||dirHasChanges||dirHasNew;
|
|
2298
|
+
var det=document.createElement('details');
|
|
2299
|
+
det.className='ft-dir';
|
|
2300
|
+
det.dataset.path=entry.path;
|
|
2301
|
+
if(autoExpand)det.open=true;
|
|
2302
|
+
var sum=document.createElement('summary');
|
|
2303
|
+
if(dirHasNew)sum.classList.add('new-file');
|
|
2304
|
+
else if(dirHasChanges)sum.classList.add('changed');
|
|
2305
|
+
sum.style.paddingLeft=(depth*12+8)+'px';
|
|
2306
|
+
var dirBadge='';
|
|
2307
|
+
if(dirHasNew)dirBadge='<span class="ft-badge new pop">NEW</span>';
|
|
2308
|
+
else if(dirHasChanges)dirBadge='<span class="ft-badge edited pop">EDITED</span>';
|
|
2309
|
+
sum.innerHTML='<span class="ft-chevron">'+ICONS.chevronRight+'</span><span class="ft-icon folder">'+ICONS.folder+'</span><span class="ft-name">'+escHtml(entry.name)+'</span>'+dirBadge;
|
|
2310
|
+
det.appendChild(sum);
|
|
2311
|
+
var ch=document.createElement('div');
|
|
2312
|
+
ch.className='ft-children';
|
|
2313
|
+
if(entry.children)renderExplorerTree(entry.children,ch,depth+1,justChanged,justNew);
|
|
2314
|
+
det.appendChild(ch);
|
|
2315
|
+
parent.appendChild(det);
|
|
2316
|
+
}else{
|
|
2317
|
+
var fi=document.createElement('div');
|
|
2318
|
+
fi.className='ft-item';
|
|
2319
|
+
var isJustChanged=justChanged&&justChanged.indexOf(entry.path)!==-1;
|
|
2320
|
+
var isJustNew=justNew&&justNew.indexOf(entry.path)!==-1;
|
|
2321
|
+
var wasEdited=auditEditedPaths.has(entry.path);
|
|
2322
|
+
if(isJustNew)fi.classList.add('new-file');
|
|
2323
|
+
else if(isJustChanged)fi.classList.add('changed');
|
|
2324
|
+
if(diffOpenPath===entry.path)fi.classList.add('active-viewer');
|
|
2325
|
+
fi.style.paddingLeft=(depth*12+22)+'px';
|
|
2326
|
+
var badgeHtml='';
|
|
2327
|
+
var gutterHtml='';
|
|
2328
|
+
if(isJustNew){
|
|
2329
|
+
badgeHtml='<span class="ft-badge new pop">NEW</span>';
|
|
2330
|
+
gutterHtml='<span class="ft-gutter edited"></span>';
|
|
2331
|
+
}else if(isJustChanged){
|
|
2332
|
+
badgeHtml='<span class="ft-badge edited pop">EDITED</span>';
|
|
2333
|
+
gutterHtml='<span class="ft-gutter edited"></span>';
|
|
2334
|
+
}else if(wasEdited){
|
|
2335
|
+
badgeHtml='<span class="ft-badge edited" style="opacity:0.45">EDITED</span>';
|
|
2336
|
+
gutterHtml='<span class="ft-gutter edited" style="opacity:0.3"></span>';
|
|
2337
|
+
}
|
|
2338
|
+
fi.innerHTML=gutterHtml+'<span class="ft-icon '+getFileIconClass(entry.name)+'">'+getFileIconSvg(entry.name)+'</span><span class="ft-name">'+escHtml(entry.name)+'</span>'+badgeHtml;
|
|
2339
|
+
fi.onclick=(function(p){return function(){openDiffViewer(p,false);};})(entry.path);
|
|
2340
|
+
fi.dataset.path=entry.path;
|
|
2341
|
+
parent.appendChild(fi);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// ── Inline diff viewer (inside files panel) ──────────────────────────
|
|
2347
|
+
async function openDiffViewer(path,isAutoEdit,autoCloseMs){
|
|
2348
|
+
if(autoCloseTimer){clearTimeout(autoCloseTimer);autoCloseTimer=null;}
|
|
2349
|
+
var viewer=document.getElementById('diffViewer');
|
|
2350
|
+
var pathEl=document.getElementById('dvPath');
|
|
2351
|
+
var statusEl=document.getElementById('dvStatus');
|
|
2352
|
+
var countdown=document.getElementById('dvCountdown');
|
|
2353
|
+
var pre=document.getElementById('dvPre');
|
|
2354
|
+
|
|
2355
|
+
// Highlight active file in tree
|
|
2356
|
+
document.querySelectorAll('#explorerTree .ft-item').forEach(function(el){
|
|
2357
|
+
el.classList.toggle('active-viewer',el.dataset.path===path);
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
var isReopen=(diffOpenPath===path&&viewer.classList.contains('open'));
|
|
2361
|
+
diffOpenPath=path;
|
|
2362
|
+
pathEl.textContent=path.split('/').pop();
|
|
2363
|
+
pathEl.title=path;
|
|
2364
|
+
|
|
2365
|
+
// Detect file type
|
|
2366
|
+
var ext=path.split('.').pop().toLowerCase();
|
|
2367
|
+
var isMd=ext==='md';
|
|
2368
|
+
var isImage=/^(png|jpg|jpeg|gif|webp|svg|bmp|ico)$/.test(ext);
|
|
2369
|
+
var mdToggle=document.getElementById('dvMdToggle');
|
|
2370
|
+
var mdDiv=document.getElementById('dvMarkdown');
|
|
2371
|
+
var imgDiv=document.getElementById('dvImage');
|
|
2372
|
+
|
|
2373
|
+
// Reset all content panels
|
|
2374
|
+
mdToggle.classList.toggle('hidden',!isMd);
|
|
2375
|
+
imgDiv.classList.add('hidden');
|
|
2376
|
+
imgDiv.innerHTML='';
|
|
2377
|
+
if(isMd){
|
|
2378
|
+
dvMdActive=true;
|
|
2379
|
+
mdToggle.classList.add('active');
|
|
2380
|
+
pre.style.display='none';
|
|
2381
|
+
mdDiv.classList.remove('hidden');
|
|
2382
|
+
}else{
|
|
2383
|
+
dvMdActive=false;
|
|
2384
|
+
mdToggle.classList.remove('active');
|
|
2385
|
+
pre.style.display='';
|
|
2386
|
+
mdDiv.classList.add('hidden');
|
|
2387
|
+
}
|
|
2388
|
+
if(isImage){
|
|
2389
|
+
pre.style.display='none';
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
if(isAutoEdit){
|
|
2393
|
+
statusEl.className='dv-status edited';
|
|
2394
|
+
statusEl.textContent='EDITED';
|
|
2395
|
+
}else{
|
|
2396
|
+
statusEl.className='dv-status viewing';
|
|
2397
|
+
statusEl.textContent='VIEWING';
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// Open the viewer panel
|
|
2401
|
+
viewer.classList.add('open');
|
|
2402
|
+
// Reset countdown
|
|
2403
|
+
countdown.style.transition='none';
|
|
2404
|
+
countdown.style.width='100%';
|
|
2405
|
+
countdown.classList.remove('done');
|
|
2406
|
+
|
|
2407
|
+
if(!isReopen){
|
|
2408
|
+
pre.innerHTML='<span class="dv-line" style="color:#484f58;">Loading...</span>';
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
// Image files — render directly, skip text parsing
|
|
2412
|
+
if(isImage){
|
|
2413
|
+
pre.innerHTML='';
|
|
2414
|
+
imgDiv.classList.remove('hidden');
|
|
2415
|
+
var img=document.createElement('img');
|
|
2416
|
+
img.src='/api/file/raw?path='+encodeURIComponent(path)+'&t='+Date.now();
|
|
2417
|
+
img.alt=path.split('/').pop();
|
|
2418
|
+
img.onload=function(){
|
|
2419
|
+
var meta=document.createElement('span');
|
|
2420
|
+
meta.className='img-meta';
|
|
2421
|
+
meta.textContent=img.naturalWidth+'×'+img.naturalHeight;
|
|
2422
|
+
imgDiv.appendChild(meta);
|
|
2423
|
+
};
|
|
2424
|
+
imgDiv.appendChild(img);
|
|
2425
|
+
// No auto-close for images
|
|
2426
|
+
countdown.style.width='0';
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
try{
|
|
2431
|
+
var r=await fetch('/api/file?path='+encodeURIComponent(path));
|
|
2432
|
+
var d=await r.json();
|
|
2433
|
+
if(d.error){pre.innerHTML='<span class="dv-line" style="color:#f85149;">'+escHtml(d.error)+'</span>';return;}
|
|
2434
|
+
|
|
2435
|
+
var oldContent=fileContentCache[path]||'';
|
|
2436
|
+
var newContent=d.content||'';
|
|
2437
|
+
var isNewFile=!fileContentCache.hasOwnProperty(path);
|
|
2438
|
+
fileContentCache[path]=newContent;
|
|
2439
|
+
diffOpenContent=newContent;
|
|
2440
|
+
|
|
2441
|
+
var oldLines=oldContent?oldContent.split('\n'):[];
|
|
2442
|
+
var newLines=newContent.split('\n');
|
|
2443
|
+
var hasDiff=(oldContent.length>0&&oldContent!==newContent)||isNewFile;
|
|
2444
|
+
|
|
2445
|
+
pre.innerHTML='';
|
|
2446
|
+
var maxStagger=Math.min(newLines.length,200);
|
|
2447
|
+
for(var i=0;i<newLines.length;i++){
|
|
2448
|
+
var span=document.createElement('span');
|
|
2449
|
+
span.className='dv-line';
|
|
2450
|
+
span.textContent=newLines[i];
|
|
2451
|
+
|
|
2452
|
+
var isChangedLine=hasDiff&&!isNewFile&&(i>=oldLines.length||newLines[i]!==oldLines[i]);
|
|
2453
|
+
var isAddedLine=hasDiff&&(isNewFile||i>=oldLines.length);
|
|
2454
|
+
|
|
2455
|
+
if(isAddedLine){
|
|
2456
|
+
span.classList.add('line-added');
|
|
2457
|
+
var gm=document.createElement('span');
|
|
2458
|
+
gm.className='dv-gutter g-added';
|
|
2459
|
+
span.appendChild(gm);
|
|
2460
|
+
}else if(isChangedLine){
|
|
2461
|
+
span.classList.add('line-changed');
|
|
2462
|
+
var gm2=document.createElement('span');
|
|
2463
|
+
gm2.className='dv-gutter g-modified';
|
|
2464
|
+
span.appendChild(gm2);
|
|
2465
|
+
}
|
|
2466
|
+
// Stagger entry for first open (no previous content)
|
|
2467
|
+
if(!hasDiff&&!isReopen&&i<maxStagger){
|
|
2468
|
+
span.classList.add('line-enter');
|
|
2469
|
+
span.style.animationDelay=(i*6)+'ms';
|
|
2470
|
+
span.style.opacity='0';
|
|
2471
|
+
}
|
|
2472
|
+
pre.appendChild(span);
|
|
2473
|
+
pre.appendChild(document.createTextNode('\n'));
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
// Render markdown preview if .md file
|
|
2477
|
+
if(isMd&&typeof marked!=='undefined'&&newContent){
|
|
2478
|
+
mdDiv.innerHTML=marked.parse(newContent);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
// Scroll to first changed line
|
|
2482
|
+
if(hasDiff){
|
|
2483
|
+
var firstChanged=pre.querySelector('.line-changed,.line-added');
|
|
2484
|
+
if(firstChanged){
|
|
2485
|
+
setTimeout(function(){
|
|
2486
|
+
firstChanged.scrollIntoView({behavior:'smooth',block:'center'});
|
|
2487
|
+
},80);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
// Auto-close countdown for auto-opened edits
|
|
2492
|
+
if(isAutoEdit){
|
|
2493
|
+
var closeDelay=autoCloseMs||5000;
|
|
2494
|
+
var barDuration=(closeDelay-500)/1000; // bar animation slightly shorter
|
|
2495
|
+
// Kick off the countdown bar after a brief paint delay
|
|
2496
|
+
requestAnimationFrame(function(){
|
|
2497
|
+
requestAnimationFrame(function(){
|
|
2498
|
+
countdown.style.transition='width '+barDuration+'s linear';
|
|
2499
|
+
countdown.classList.add('done');
|
|
2500
|
+
});
|
|
2501
|
+
});
|
|
2502
|
+
autoCloseTimer=setTimeout(function(){
|
|
2503
|
+
autoCloseTimer=null;
|
|
2504
|
+
// Only auto-close if still showing this same auto-edit
|
|
2505
|
+
if(diffOpenPath===path)closeDiffViewer();
|
|
2506
|
+
},closeDelay);
|
|
2507
|
+
}else{
|
|
2508
|
+
// Manual open — hide the countdown bar
|
|
2509
|
+
countdown.style.width='0';
|
|
2510
|
+
}
|
|
2511
|
+
}catch(e){
|
|
2512
|
+
pre.innerHTML='<span class="dv-line" style="color:#f85149;">Failed to load</span>';
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
var dvMdActive=false;
|
|
2517
|
+
function toggleMdView(){
|
|
2518
|
+
var pre=document.getElementById('dvPre');
|
|
2519
|
+
var md=document.getElementById('dvMarkdown');
|
|
2520
|
+
var btn=document.getElementById('dvMdToggle');
|
|
2521
|
+
dvMdActive=!dvMdActive;
|
|
2522
|
+
btn.classList.toggle('active',dvMdActive);
|
|
2523
|
+
if(dvMdActive){
|
|
2524
|
+
pre.style.display='none';
|
|
2525
|
+
md.classList.remove('hidden');
|
|
2526
|
+
if(typeof marked!=='undefined'&&diffOpenContent){
|
|
2527
|
+
md.innerHTML=marked.parse(diffOpenContent);
|
|
2528
|
+
}
|
|
2529
|
+
}else{
|
|
2530
|
+
pre.style.display='';
|
|
2531
|
+
md.classList.add('hidden');
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
function closeDiffViewer(){
|
|
2536
|
+
if(autoCloseTimer){clearTimeout(autoCloseTimer);autoCloseTimer=null;}
|
|
2537
|
+
document.getElementById('diffViewer').classList.remove('open');
|
|
2538
|
+
document.querySelectorAll('#explorerTree .ft-item.active-viewer').forEach(function(el){
|
|
2539
|
+
el.classList.remove('active-viewer');
|
|
2540
|
+
});
|
|
2541
|
+
diffOpenPath=null;
|
|
2542
|
+
diffOpenContent='';
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
function refreshFileTree(){
|
|
2546
|
+
if(!auditMode)return;
|
|
2547
|
+
if(refreshDebounceTimer)clearTimeout(refreshDebounceTimer);
|
|
2548
|
+
refreshDebounceTimer=setTimeout(function(){loadExplorerTree();},500);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
document.addEventListener('keydown',function(e){if(e.key==='Escape'&&diffOpenPath)closeDiffViewer();});
|
|
2552
|
+
|
|
2553
|
+
connectWS();
|
|
2554
|
+
</script>
|
|
2555
|
+
</body>
|
|
2556
|
+
</html>
|