kiro-mobile-bridge 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -21
- package/package.json +1 -1
- package/src/public/index.html +1162 -1623
- package/src/routes/api.js +358 -0
- package/src/server.js +253 -2575
- package/src/services/cdp.js +156 -0
- package/src/services/click.js +282 -0
- package/src/services/message.js +206 -0
- package/src/services/snapshot.js +331 -0
- package/src/utils/hash.js +22 -0
- package/src/utils/network.js +20 -0
package/src/public/index.html
CHANGED
|
@@ -9,234 +9,213 @@
|
|
|
9
9
|
<title>Kiro Mobile Bridge</title>
|
|
10
10
|
<link rel="stylesheet" href="https://unpkg.com/@vscode/codicons@0.0.35/dist/codicon.css">
|
|
11
11
|
<style>
|
|
12
|
+
/* =============================================================================
|
|
13
|
+
Base Styles
|
|
14
|
+
============================================================================= */
|
|
12
15
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
13
|
-
html, body {
|
|
16
|
+
html, body {
|
|
17
|
+
height: 100%;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
background: #1e1e1e;
|
|
20
|
+
font-family: "Segoe WPC", "Segoe UI", -apple-system, BlinkMacSystemFont, system-ui, Ubuntu, sans-serif;
|
|
21
|
+
font-size: 13px;
|
|
22
|
+
color: #fff;
|
|
23
|
+
}
|
|
14
24
|
.app { display: flex; flex-direction: column; height: 100%; height: 100dvh; }
|
|
15
25
|
|
|
16
|
-
/*
|
|
17
|
-
|
|
26
|
+
/* =============================================================================
|
|
27
|
+
Navigation Bar
|
|
28
|
+
============================================================================= */
|
|
29
|
+
.nav-bar {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
background: #1e1e1e;
|
|
33
|
+
border-bottom: 1px solid #3c3c3c;
|
|
34
|
+
padding: 0 8px;
|
|
35
|
+
flex-shrink: 0;
|
|
36
|
+
}
|
|
18
37
|
.status { display: flex; align-items: center; gap: 6px; font-size: 11px; padding: 10px 8px; }
|
|
19
38
|
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: #666; }
|
|
20
39
|
.status-dot.connected { background: #4caf50; }
|
|
21
40
|
.status-dot.disconnected { background: #f44336; }
|
|
22
|
-
.status-text { color: #888; display: none; }
|
|
23
41
|
.nav-tabs { display: flex; align-items: center; flex: 1; overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
24
42
|
.nav-tabs::-webkit-scrollbar { display: none; }
|
|
25
|
-
.nav-tab {
|
|
26
|
-
|
|
43
|
+
.nav-tab {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: 6px;
|
|
47
|
+
padding: 10px 16px;
|
|
48
|
+
color: #888;
|
|
49
|
+
font-size: 12px;
|
|
50
|
+
font-weight: 500;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
border-bottom: 2px solid transparent;
|
|
53
|
+
transition: all 0.2s ease;
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
user-select: none;
|
|
56
|
+
}
|
|
27
57
|
.nav-tab:hover { color: #ccc; background: rgba(255,255,255,0.05); }
|
|
28
58
|
.nav-tab.active { color: #fff; border-bottom-color: #7138cc; }
|
|
29
59
|
.nav-tab .codicon { font-size: 14px; }
|
|
30
60
|
|
|
31
|
-
/*
|
|
61
|
+
/* =============================================================================
|
|
62
|
+
Panel Container
|
|
63
|
+
============================================================================= */
|
|
32
64
|
.panel-container { flex: 1; overflow: hidden; position: relative; }
|
|
33
65
|
.panel { position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden; display: none; }
|
|
34
66
|
.panel.active { display: flex; flex-direction: column; }
|
|
35
67
|
|
|
36
|
-
/*
|
|
68
|
+
/* =============================================================================
|
|
69
|
+
Chat Panel
|
|
70
|
+
============================================================================= */
|
|
37
71
|
.chat-container { flex: 1; overflow: hidden; position: relative; }
|
|
38
72
|
.chat-content { width: 100%; height: 100%; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; }
|
|
39
73
|
#chatContent, #chatContent * { font-family: inherit; }
|
|
40
74
|
#chatContent code, #chatContent pre { font-family: Menlo, Monaco, "Courier New", monospace !important; }
|
|
41
75
|
#chatContent .codicon { font-family: codicon !important; font-size: 16px; line-height: 1; }
|
|
42
76
|
|
|
43
|
-
/*
|
|
77
|
+
/* =============================================================================
|
|
78
|
+
Editor Panel
|
|
79
|
+
============================================================================= */
|
|
44
80
|
.editor-container { flex: 1; overflow: hidden; background: #1e1e1e; display: flex; flex-direction: column; position: relative; }
|
|
45
81
|
.editor-header { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: #252526; border-bottom: 1px solid #3c3c3c; flex-shrink: 0; user-select: none; }
|
|
46
|
-
.editor-filename { font-size: 13px; color: #fff; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
82
|
+
.editor-filename { font-size: 13px; color: #fff; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 200px; }
|
|
47
83
|
.editor-search-btn { color: #888; font-size: 16px; cursor: pointer; padding: 4px; border-radius: 4px; }
|
|
48
84
|
.editor-search-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
|
|
49
|
-
.editor-explorer-btn { display: flex; align-items: center; gap: 6px; color: #
|
|
50
|
-
.editor-explorer-btn:hover { background:
|
|
51
|
-
.editor-explorer-text { font-size: 11px; font-weight: 600; color: #
|
|
85
|
+
.editor-explorer-btn { display: flex; align-items: center; gap: 6px; color: #a78bfa; font-size: 14px; cursor: pointer; padding: 4px 10px; border-radius: 6px; background: rgba(113, 56, 204, 0.15); border: 1px solid rgba(113, 56, 204, 0.3); }
|
|
86
|
+
.editor-explorer-btn:hover { background: rgba(113, 56, 204, 0.25); border-color: rgba(113, 56, 204, 0.5); }
|
|
87
|
+
.editor-explorer-text { font-size: 11px; font-weight: 600; color: #a78bfa; letter-spacing: 0.5px; }
|
|
88
|
+
.editor-explorer-indicator { font-size: 10px; color: #888; margin-left: 2px; transition: transform 0.2s ease; }
|
|
52
89
|
.editor-search-bar { display: none; align-items: center; gap: 8px; padding: 6px 12px; background: #252526; border-bottom: 1px solid #3c3c3c; }
|
|
53
90
|
.editor-search-bar.open { display: flex; }
|
|
54
91
|
.editor-search-bar input { flex: 1; padding: 6px 10px; background: #1e1e1e; border: 1px solid #3c3c3c; border-radius: 4px; color: #fff; font-size: 13px; outline: none; }
|
|
55
92
|
.editor-search-bar input:focus { border-color: #007acc; }
|
|
56
93
|
.editor-search-count { font-size: 12px; color: #888; min-width: 50px; text-align: center; }
|
|
57
|
-
.editor-search-nav { color: #888; font-size: 16px; cursor: pointer; padding: 4px; border-radius: 4px; }
|
|
58
|
-
.editor-search-nav:hover { color: #fff; background: rgba(255,255,255,0.1); }
|
|
59
|
-
.editor-search-close { color: #888; font-size: 16px; cursor: pointer; padding: 4px; border-radius: 4px; }
|
|
60
|
-
.editor-search-close:hover { color: #fff; background: rgba(255,255,255,0.1); }
|
|
94
|
+
.editor-search-nav, .editor-search-close { color: #888; font-size: 16px; cursor: pointer; padding: 4px; border-radius: 4px; }
|
|
95
|
+
.editor-search-nav:hover, .editor-search-close:hover { color: #fff; background: rgba(255,255,255,0.1); }
|
|
61
96
|
.editor-content { flex: 1; overflow: auto; -webkit-overflow-scrolling: touch; background: #1e1e1e; }
|
|
62
97
|
.editor-code { margin: 0; padding: 8px 0; font-family: Consolas, 'Courier New', monospace; font-size: 12px; line-height: 1.5; color: #d4d4d4; background: #1e1e1e; }
|
|
63
98
|
.editor-line { display: flex; min-height: 18px; }
|
|
64
99
|
.editor-line:hover { background: rgba(255,255,255,0.04); }
|
|
65
|
-
.editor-line-num { width: 50px; min-width: 50px; text-align: right; padding-right: 16px; color: #858585; user-select: none; flex-shrink: 0; font-size: 12px;
|
|
66
|
-
.editor-line-code { flex: 1; padding-right: 12px; white-space: pre; overflow-x: auto; tab-size: 2;
|
|
100
|
+
.editor-line-num { width: 50px; min-width: 50px; text-align: right; padding-right: 16px; color: #858585; user-select: none; flex-shrink: 0; font-size: 12px; }
|
|
101
|
+
.editor-line-code { flex: 1; padding-right: 12px; white-space: pre; overflow-x: auto; tab-size: 2; }
|
|
67
102
|
.search-highlight { background: #ffd500; color: #000; border-radius: 2px; padding: 0 1px; }
|
|
68
103
|
.search-highlight.current { background: #ff6b00; color: #fff; outline: 2px solid #ff6b00; }
|
|
69
104
|
|
|
70
|
-
/*
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
.
|
|
75
|
-
.
|
|
76
|
-
.
|
|
105
|
+
/* =============================================================================
|
|
106
|
+
Tasks Panel
|
|
107
|
+
============================================================================= */
|
|
108
|
+
.tasks-container { flex: 1; overflow: hidden; background: #0d0d0d; display: flex; flex-direction: column; }
|
|
109
|
+
.tasks-header { display: flex; align-items: center; gap: 10px; padding: 12px 16px; background: #0d0d0d; flex-shrink: 0; }
|
|
110
|
+
.tasks-dropdown { flex: 1; padding: 10px 14px; background: #1a1a1a; border: none; border-radius: 10px; color: #fff; font-size: 14px; font-weight: 500; cursor: pointer; outline: none; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23666' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; padding-right: 36px; }
|
|
111
|
+
.tasks-dropdown option { background: #1a1a1a; color: #fff; }
|
|
112
|
+
.tasks-content { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 0 16px 24px; }
|
|
113
|
+
.tasks-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #444; text-align: center; padding: 32px; }
|
|
114
|
+
.tasks-empty .codicon { font-size: 40px; margin-bottom: 16px; }
|
|
115
|
+
.task-md { font-size: 14px; }
|
|
116
|
+
|
|
117
|
+
/* Task Groups */
|
|
118
|
+
.task-group { background: #141414; border-radius: 16px; margin-bottom: 6px; overflow: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
119
|
+
.task-group.expanded { background: #181818; box-shadow: 0 0 0 1px rgba(60, 60, 60, 0.5), 0 8px 32px rgba(0,0,0,0.4); }
|
|
120
|
+
.task-group.expanded.completed { box-shadow: 0 0 0 1px rgba(113, 56, 204, 0.4), 0 8px 32px rgba(0,0,0,0.4); }
|
|
121
|
+
.task-group-header { display: flex; align-items: center; gap: 12px; padding: 14px 16px; cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; }
|
|
122
|
+
.task-group-indicator { width: 24px; height: 24px; min-width: 24px; border-radius: 50%; background: #2a2a2a; display: flex; align-items: center; justify-content: center; }
|
|
123
|
+
.task-group-indicator svg { display: none; width: 14px; height: 14px; color: #fff; }
|
|
124
|
+
.task-group.completed .task-group-indicator { background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%); }
|
|
125
|
+
.task-group.completed .task-group-indicator svg { display: block; }
|
|
126
|
+
.task-group-number { color: #666; font-size: 14px; font-weight: 700; width: 24px; text-align: center; }
|
|
127
|
+
.task-group.expanded .task-group-number { color: #ccc; }
|
|
128
|
+
.task-group.completed .task-group-number { color: #a855f7; }
|
|
129
|
+
.task-group-title { font-size: 14px; font-weight: 500; color: #e5e5e5; flex: 1; line-height: 1.4; }
|
|
130
|
+
.task-group.completed .task-group-title { color: #888; }
|
|
131
|
+
.task-group-meta { display: flex; align-items: center; gap: 8px; }
|
|
132
|
+
.task-group-count { font-size: 12px; font-weight: 600; color: #444; padding: 4px 10px; background: #1f1f1f; border-radius: 16px; }
|
|
133
|
+
.task-group.completed .task-group-count { color: #a855f7; background: rgba(124, 58, 237, 0.15); }
|
|
134
|
+
.task-group-arrow { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; color: #444; transition: transform 0.3s ease; }
|
|
135
|
+
.task-group-arrow svg { width: 12px; height: 12px; }
|
|
136
|
+
.task-group.expanded .task-group-arrow { transform: rotate(90deg); color: #888; }
|
|
137
|
+
.task-group-body { max-height: 0; overflow: hidden; transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
138
|
+
.task-group.expanded .task-group-body { max-height: 2000px; }
|
|
139
|
+
.task-group-body-inner { padding: 0 16px 12px 52px; }
|
|
140
|
+
|
|
141
|
+
/* Task Items */
|
|
142
|
+
.task-item { display: flex; align-items: flex-start; gap: 10px; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.05); }
|
|
143
|
+
.task-item:last-child { border-bottom: none; }
|
|
144
|
+
.task-checkbox { width: 18px; height: 18px; min-width: 18px; border: 2px solid #444; border-radius: 5px; display: flex; align-items: center; justify-content: center; background: transparent; }
|
|
145
|
+
.task-checkbox.checked { background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%); border-color: transparent; }
|
|
146
|
+
.task-checkbox.checked svg { display: block; }
|
|
147
|
+
.task-checkbox svg { display: none; width: 10px; height: 10px; color: #fff; }
|
|
148
|
+
.task-text { flex: 1; font-size: 13px; line-height: 1.4; color: #999; padding-top: 1px; }
|
|
149
|
+
.task-text.completed { color: #555; }
|
|
150
|
+
.task-item.indent-2 { padding-left: 20px; }
|
|
151
|
+
.task-item.indent-3 { padding-left: 40px; }
|
|
152
|
+
|
|
153
|
+
/* =============================================================================
|
|
154
|
+
File Tree Dropdown (under Explorer button)
|
|
155
|
+
============================================================================= */
|
|
156
|
+
.file-tree-dropdown {
|
|
157
|
+
position: absolute;
|
|
158
|
+
top: 100%;
|
|
159
|
+
left: 0;
|
|
160
|
+
width: 280px;
|
|
161
|
+
max-height: 400px;
|
|
162
|
+
background: #1e1e1e;
|
|
163
|
+
border: 1px solid #3c3c3c;
|
|
164
|
+
border-radius: 6px;
|
|
165
|
+
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
|
166
|
+
display: none;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
z-index: 100;
|
|
169
|
+
margin-top: 4px;
|
|
170
|
+
}
|
|
171
|
+
.file-tree-dropdown.open { display: flex; }
|
|
77
172
|
.file-tree { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 4px 0; }
|
|
78
|
-
.file-tree-folder-header { display: flex; align-items: center; gap: 6px; padding:
|
|
79
|
-
.file-tree-folder-header:hover { background: rgba(255,255,255,0.
|
|
173
|
+
.file-tree-folder-header { display: flex; align-items: center; gap: 6px; padding: 6px 10px; cursor: pointer; user-select: none; }
|
|
174
|
+
.file-tree-folder-header:hover { background: rgba(255,255,255,0.08); }
|
|
80
175
|
.file-tree-folder-icon { color: #888; font-size: 14px; transition: transform 0.15s; }
|
|
81
176
|
.file-tree-folder-icon.expanded { transform: rotate(90deg); }
|
|
82
177
|
.file-tree-folder-name { font-size: 13px; color: #c5c5c5; }
|
|
83
178
|
.file-tree-folder-contents { padding-left: 16px; display: none; }
|
|
84
179
|
.file-tree-folder-contents.expanded { display: block; }
|
|
85
|
-
.file-tree-file { display: flex; align-items: center; gap: 6px; padding:
|
|
86
|
-
.file-tree-file:hover { background: rgba(255,255,255,0.
|
|
180
|
+
.file-tree-file { display: flex; align-items: center; gap: 6px; padding: 6px 10px 6px 26px; cursor: pointer; }
|
|
181
|
+
.file-tree-file:hover { background: rgba(255,255,255,0.08); }
|
|
87
182
|
.file-tree-file:active { background: #094771; }
|
|
88
183
|
.file-tree-file-icon { color: #888; font-size: 14px; }
|
|
89
184
|
.file-tree-file-name { font-size: 13px; color: #c5c5c5; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
90
|
-
.file-tree-empty { padding: 20px; text-align: center; color: #666; font-size: 13px; }
|
|
91
|
-
.file-tree-loading {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
.
|
|
101
|
-
.
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.token-attr-name { color: #9cdcfe; }
|
|
108
|
-
.token-attr-value { color: #ce9178; }
|
|
109
|
-
/* Kiro theme token classes */
|
|
110
|
-
.tok-kw { color: #c586c0; } /* Keywords - purple/pink */
|
|
111
|
-
.tok-str { color: #ce9178; } /* Strings - orange */
|
|
112
|
-
.tok-num { color: #b5cea8; } /* Numbers - light green */
|
|
113
|
-
.tok-cmt { color: #6a9955; font-style: italic; } /* Comments - green italic */
|
|
114
|
-
.tok-fn { color: #dcdcaa; } /* Functions - yellow */
|
|
115
|
-
.tok-type { color: #4ec9b0; } /* Types - teal */
|
|
116
|
-
/* Syntax Highlighting */
|
|
117
|
-
.token-keyword { color: #569cd6; font-weight: 500; }
|
|
118
|
-
.token-string { color: #ce9178; }
|
|
119
|
-
.token-number { color: #b5cea8; }
|
|
120
|
-
.token-comment { color: #6a9955; font-style: italic; }
|
|
121
|
-
.token-function { color: #dcdcaa; }
|
|
122
|
-
.token-type { color: #4ec9b0; }
|
|
123
|
-
.token-operator { color: #d4d4d4; }
|
|
124
|
-
|
|
125
|
-
/* Loading & Toast */
|
|
185
|
+
.file-tree-empty, .file-tree-loading { padding: 20px; text-align: center; color: #666; font-size: 13px; }
|
|
186
|
+
.file-tree-loading { color: #888; display: flex; flex-direction: column; align-items: center; gap: 8px; }
|
|
187
|
+
.editor-explorer-wrapper { position: relative; }
|
|
188
|
+
|
|
189
|
+
/* =============================================================================
|
|
190
|
+
Syntax Highlighting (Kiro Dark Theme)
|
|
191
|
+
============================================================================= */
|
|
192
|
+
.tok-kw { color: #c586c0; }
|
|
193
|
+
.tok-str { color: #ce9178; }
|
|
194
|
+
.tok-num { color: #b5cea8; }
|
|
195
|
+
.tok-cmt { color: #6a9955; font-style: italic; }
|
|
196
|
+
.tok-fn { color: #dcdcaa; }
|
|
197
|
+
.tok-type { color: #4ec9b0; }
|
|
198
|
+
|
|
199
|
+
/* =============================================================================
|
|
200
|
+
Loading & Toast
|
|
201
|
+
============================================================================= */
|
|
126
202
|
.loading { display: flex; align-items: center; justify-content: center; height: 100%; color: #888; font-size: 14px; flex-direction: column; gap: 12px; }
|
|
127
203
|
.loading-spinner { width: 24px; height: 24px; border: 2px solid #3c3c3c; border-top-color: #7138cc; border-radius: 50%; animation: spin 1s linear infinite; }
|
|
128
204
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
129
|
-
|
|
130
205
|
.toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: #f44336; color: #fff; padding: 10px 16px; border-radius: 6px; font-size: 13px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; z-index: 1000; }
|
|
131
206
|
.toast.visible { opacity: 1; }
|
|
132
207
|
.toast.success { background: #4caf50; }
|
|
133
208
|
|
|
134
|
-
/*
|
|
209
|
+
/* =============================================================================
|
|
210
|
+
Empty State
|
|
211
|
+
============================================================================= */
|
|
135
212
|
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #888; text-align: center; padding: 20px; }
|
|
136
213
|
.empty-state .codicon { font-size: 48px; margin-bottom: 16px; opacity: 0.5; }
|
|
137
214
|
.empty-state p { font-size: 14px; max-width: 280px; }
|
|
138
|
-
|
|
139
|
-
/* Task List Styling - Match Kiro IDE */
|
|
140
|
-
[class*="task"], [class*="Task"] {
|
|
141
|
-
font-family: "Segoe WPC", "Segoe UI", -apple-system, BlinkMacSystemFont, system-ui, Ubuntu, "Droid Sans", sans-serif !important;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/* Task section headers (CURRENT TASKS, TASKS IN QUEUE) */
|
|
145
|
-
[class*="task"] h2, [class*="Task"] h2,
|
|
146
|
-
[class*="task"] h3, [class*="Task"] h3,
|
|
147
|
-
[class*="task-header"], [class*="TaskHeader"],
|
|
148
|
-
[class*="section-title"], [class*="SectionTitle"] {
|
|
149
|
-
font-size: 13px !important;
|
|
150
|
-
font-weight: 700 !important;
|
|
151
|
-
letter-spacing: 0.5px !important;
|
|
152
|
-
text-transform: uppercase !important;
|
|
153
|
-
color: #ffffff !important;
|
|
154
|
-
margin: 0 0 12px 0 !important;
|
|
155
|
-
padding: 0 !important;
|
|
156
|
-
line-height: 1.4 !important;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/* Task list containers */
|
|
160
|
-
[class*="task-list"], [class*="TaskList"],
|
|
161
|
-
[class*="task-section"], [class*="TaskSection"] {
|
|
162
|
-
margin-bottom: 24px !important;
|
|
163
|
-
padding: 0 !important;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/* Empty state messages (No active tasks, No tasks in queue) */
|
|
167
|
-
[class*="task"] p:empty, [class*="Task"] p:empty,
|
|
168
|
-
[class*="no-task"], [class*="NoTask"],
|
|
169
|
-
[class*="empty-task"], [class*="EmptyTask"],
|
|
170
|
-
[class*="task-empty"], [class*="TaskEmpty"] {
|
|
171
|
-
font-size: 13px !important;
|
|
172
|
-
font-style: italic !important;
|
|
173
|
-
color: #888888 !important;
|
|
174
|
-
margin: 0 0 16px 0 !important;
|
|
175
|
-
padding: 0 !important;
|
|
176
|
-
line-height: 1.6 !important;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/* Catch all paragraphs under task sections that look like empty states */
|
|
180
|
-
[class*="task"] p, [class*="Task"] p {
|
|
181
|
-
font-size: 13px !important;
|
|
182
|
-
line-height: 1.6 !important;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/* Specific targeting for common patterns */
|
|
186
|
-
div[class*="current"] h2, div[class*="Current"] h2,
|
|
187
|
-
div[class*="queue"] h2, div[class*="Queue"] h2 {
|
|
188
|
-
font-size: 13px !important;
|
|
189
|
-
font-weight: 700 !important;
|
|
190
|
-
letter-spacing: 0.5px !important;
|
|
191
|
-
text-transform: uppercase !important;
|
|
192
|
-
color: #ffffff !important;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/* Horizontal divider between sections */
|
|
196
|
-
[class*="task-divider"], [class*="TaskDivider"],
|
|
197
|
-
[class*="task"] hr, [class*="Task"] hr {
|
|
198
|
-
border: none !important;
|
|
199
|
-
border-top: 1px solid #3c3c3c !important;
|
|
200
|
-
margin: 20px 0 !important;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/* Individual task items (when they exist) */
|
|
204
|
-
[class*="task-item"], [class*="TaskItem"] {
|
|
205
|
-
padding: 8px 12px !important;
|
|
206
|
-
margin: 4px 0 !important;
|
|
207
|
-
background: rgba(255, 255, 255, 0.03) !important;
|
|
208
|
-
border-radius: 4px !important;
|
|
209
|
-
border-left: 2px solid #7138cc !important;
|
|
210
|
-
font-size: 13px !important;
|
|
211
|
-
color: #cccccc !important;
|
|
212
|
-
line-height: 1.5 !important;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
[class*="task-item"]:hover, [class*="TaskItem"]:hover {
|
|
216
|
-
background: rgba(255, 255, 255, 0.05) !important;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/* Task item with close button */
|
|
220
|
-
[class*="task-item"] button, [class*="TaskItem"] button {
|
|
221
|
-
margin-left: 8px !important;
|
|
222
|
-
padding: 2px 6px !important;
|
|
223
|
-
font-size: 11px !important;
|
|
224
|
-
background: rgba(255, 255, 255, 0.1) !important;
|
|
225
|
-
border: 1px solid #4a464f !important;
|
|
226
|
-
border-radius: 3px !important;
|
|
227
|
-
color: #888 !important;
|
|
228
|
-
cursor: pointer !important;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
[class*="task-item"] button:hover, [class*="TaskItem"] button:hover {
|
|
232
|
-
background: rgba(255, 255, 255, 0.15) !important;
|
|
233
|
-
color: #fff !important;
|
|
234
|
-
}
|
|
235
215
|
</style>
|
|
236
216
|
</head>
|
|
237
217
|
<body>
|
|
238
218
|
<div class="app">
|
|
239
|
-
<!-- Navigation Bar (tabs | title + indicator) -->
|
|
240
219
|
<nav class="nav-bar">
|
|
241
220
|
<div class="nav-tabs" id="navTabs">
|
|
242
221
|
<div class="nav-tab active" data-panel="chat">
|
|
@@ -245,17 +224,18 @@
|
|
|
245
224
|
</div>
|
|
246
225
|
<div class="nav-tab" data-panel="editor">
|
|
247
226
|
<span class="codicon codicon-code"></span>
|
|
248
|
-
<span>
|
|
227
|
+
<span>Code</span>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="nav-tab" data-panel="tasks">
|
|
230
|
+
<span class="codicon codicon-checklist"></span>
|
|
231
|
+
<span>Tasks</span>
|
|
249
232
|
</div>
|
|
250
233
|
</div>
|
|
251
|
-
<span class="header-title">Kiro Mobile</span>
|
|
252
234
|
<div class="status">
|
|
253
235
|
<span class="status-dot" id="statusDot"></span>
|
|
254
|
-
<span class="status-text" id="statusText">Connecting...</span>
|
|
255
236
|
</div>
|
|
256
237
|
</nav>
|
|
257
238
|
|
|
258
|
-
<!-- Panel Container -->
|
|
259
239
|
<div class="panel-container">
|
|
260
240
|
<!-- Chat Panel -->
|
|
261
241
|
<div class="panel active" id="chatPanel">
|
|
@@ -268,251 +248,214 @@
|
|
|
268
248
|
<!-- Editor Panel -->
|
|
269
249
|
<div class="panel" id="editorPanel">
|
|
270
250
|
<div class="editor-container">
|
|
271
|
-
<div class="loading" id="editorLoading"><div class="loading-spinner"></div><span>Loading
|
|
251
|
+
<div class="loading" id="editorLoading"><div class="loading-spinner"></div><span>Loading code...</span></div>
|
|
272
252
|
<div class="editor-header" id="editorHeader" style="display: none;">
|
|
253
|
+
<div class="editor-explorer-wrapper">
|
|
254
|
+
<div class="editor-explorer-btn" id="editorExplorerBtn" title="Browse and select files">
|
|
255
|
+
<span class="codicon codicon-folder" style="color: #a78bfa;"></span>
|
|
256
|
+
<span class="editor-explorer-text">EXPLORER</span>
|
|
257
|
+
<span class="codicon codicon-chevron-down editor-explorer-indicator" id="explorerChevron"></span>
|
|
258
|
+
</div>
|
|
259
|
+
<div class="file-tree-dropdown" id="fileTreeDropdown">
|
|
260
|
+
<div class="file-tree" id="fileTree">
|
|
261
|
+
<div class="file-tree-loading"><div class="loading-spinner"></div>Loading files...</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
273
265
|
<span class="codicon codicon-file-code" style="color: #888;"></span>
|
|
274
266
|
<span class="editor-filename" id="editorFilename">No file open</span>
|
|
275
|
-
<
|
|
276
|
-
<
|
|
277
|
-
<span class="codicon codicon-folder" style="color: #dcb67a;"></span>
|
|
278
|
-
<span class="editor-explorer-text">EXPLORER</span>
|
|
279
|
-
</div>
|
|
267
|
+
<div style="flex: 1;"></div>
|
|
268
|
+
<span class="editor-search-btn codicon codicon-search" id="editorSearchBtn" title="Find"></span>
|
|
280
269
|
</div>
|
|
281
270
|
<div class="editor-search-bar" id="editorSearchBar">
|
|
282
|
-
<input type="text" id="editorSearchInput" placeholder="Find
|
|
271
|
+
<input type="text" id="editorSearchInput" placeholder="Find..." autocomplete="off" />
|
|
283
272
|
<span class="editor-search-count" id="editorSearchCount"></span>
|
|
284
|
-
<span class="editor-search-nav codicon codicon-arrow-up" id="editorSearchPrev"
|
|
285
|
-
<span class="editor-search-nav codicon codicon-arrow-down" id="editorSearchNext"
|
|
286
|
-
<span class="editor-search-close codicon codicon-close" id="editorSearchClose"
|
|
287
|
-
</div>
|
|
288
|
-
<div class="file-tree-panel" id="fileTreePanel">
|
|
289
|
-
<div class="file-tree-header">
|
|
290
|
-
<span class="codicon codicon-folder-opened" style="color: #dcb67a;"></span>
|
|
291
|
-
<span class="file-tree-header-title">Explorer</span>
|
|
292
|
-
<span class="file-tree-close codicon codicon-close" id="fileTreeClose"></span>
|
|
293
|
-
</div>
|
|
294
|
-
<div class="file-tree" id="fileTree">
|
|
295
|
-
<div class="file-tree-loading"><div class="loading-spinner"></div>Loading files...</div>
|
|
296
|
-
</div>
|
|
273
|
+
<span class="editor-search-nav codicon codicon-arrow-up" id="editorSearchPrev"></span>
|
|
274
|
+
<span class="editor-search-nav codicon codicon-arrow-down" id="editorSearchNext"></span>
|
|
275
|
+
<span class="editor-search-close codicon codicon-close" id="editorSearchClose"></span>
|
|
297
276
|
</div>
|
|
298
277
|
<div class="editor-content" id="editorContent" style="display: none;"></div>
|
|
299
278
|
</div>
|
|
300
279
|
</div>
|
|
280
|
+
|
|
281
|
+
<!-- Tasks Panel -->
|
|
282
|
+
<div class="panel" id="tasksPanel">
|
|
283
|
+
<div class="tasks-container">
|
|
284
|
+
<div class="loading" id="tasksLoading"><div class="loading-spinner"></div><span>Loading tasks...</span></div>
|
|
285
|
+
<div class="tasks-header" id="tasksHeader" style="display: none;">
|
|
286
|
+
<span class="codicon codicon-checklist" style="color: #7138cc; font-size: 16px;"></span>
|
|
287
|
+
<select class="tasks-dropdown" id="tasksDropdown">
|
|
288
|
+
<option value="">Select a spec...</option>
|
|
289
|
+
</select>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="tasks-content" id="tasksContent" style="display: none;"></div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
301
294
|
</div>
|
|
302
295
|
|
|
303
296
|
<div class="toast" id="toast"></div>
|
|
304
297
|
</div>
|
|
305
298
|
|
|
306
299
|
<script>
|
|
300
|
+
// =============================================================================
|
|
307
301
|
// State
|
|
302
|
+
// =============================================================================
|
|
308
303
|
let ws = null, reconnectAttempts = 0, reconnectTimeout = null;
|
|
309
304
|
let cascades = [], selectedCascadeId = null, currentStyles = null;
|
|
310
|
-
let isTypingInKiroInput = false;
|
|
311
|
-
let
|
|
312
|
-
let
|
|
313
|
-
let
|
|
314
|
-
let
|
|
315
|
-
let
|
|
316
|
-
let expandedFolders = new Set(); // Track expanded folders
|
|
317
|
-
let updateDebounceTimer = null; // Debounce rapid snapshot updates
|
|
318
|
-
let isRendering = false; // Prevent concurrent renders
|
|
305
|
+
let isTypingInKiroInput = false, activePanel = 'chat';
|
|
306
|
+
let lastSuccessfulSnapshot = null, navigationPending = false;
|
|
307
|
+
let workspaceFiles = [], fileTreeOpen = false, expandedFolders = new Set();
|
|
308
|
+
let updateDebounceTimer = null, isRendering = false;
|
|
309
|
+
let tasksData = [], selectedTaskIndex = -1, tasksPollingTimer = null, lastTasksHash = '';
|
|
310
|
+
let searchMatches = [], currentMatchIndex = -1;
|
|
319
311
|
|
|
312
|
+
// =============================================================================
|
|
320
313
|
// DOM Elements
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
314
|
+
// =============================================================================
|
|
315
|
+
const $ = id => document.getElementById(id);
|
|
316
|
+
const statusDot = $('statusDot');
|
|
317
|
+
const navTabs = $('navTabs');
|
|
318
|
+
const toast = $('toast');
|
|
319
|
+
const fileTreeDropdown = $('fileTreeDropdown');
|
|
320
|
+
const fileTree = $('fileTree');
|
|
321
|
+
const explorerChevron = $('explorerChevron');
|
|
322
|
+
|
|
330
323
|
const panels = {
|
|
331
|
-
chat: {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
content: document.getElementById('chatContent'),
|
|
335
|
-
container: document.getElementById('chatContainer')
|
|
336
|
-
},
|
|
337
|
-
editor: {
|
|
338
|
-
panel: document.getElementById('editorPanel'),
|
|
339
|
-
loading: document.getElementById('editorLoading'),
|
|
340
|
-
content: document.getElementById('editorContent'),
|
|
341
|
-
header: document.getElementById('editorHeader'),
|
|
342
|
-
filename: document.getElementById('editorFilename')
|
|
343
|
-
}
|
|
324
|
+
chat: { panel: $('chatPanel'), loading: $('chatLoading'), content: $('chatContent'), container: $('chatContainer') },
|
|
325
|
+
editor: { panel: $('editorPanel'), loading: $('editorLoading'), content: $('editorContent'), header: $('editorHeader'), filename: $('editorFilename') },
|
|
326
|
+
tasks: { panel: $('tasksPanel'), loading: $('tasksLoading'), content: $('tasksContent'), header: $('tasksHeader'), dropdown: $('tasksDropdown') }
|
|
344
327
|
};
|
|
345
328
|
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
let originalEditorContent = '';
|
|
350
|
-
|
|
351
|
-
// Search DOM elements
|
|
352
|
-
const editorSearchBtn = document.getElementById('editorSearchBtn');
|
|
353
|
-
const editorSearchBar = document.getElementById('editorSearchBar');
|
|
354
|
-
const editorSearchInput = document.getElementById('editorSearchInput');
|
|
355
|
-
const editorSearchCount = document.getElementById('editorSearchCount');
|
|
356
|
-
const editorSearchPrev = document.getElementById('editorSearchPrev');
|
|
357
|
-
const editorSearchNext = document.getElementById('editorSearchNext');
|
|
358
|
-
const editorSearchClose = document.getElementById('editorSearchClose');
|
|
359
|
-
|
|
360
|
-
// Status management
|
|
329
|
+
// =============================================================================
|
|
330
|
+
// Utilities
|
|
331
|
+
// =============================================================================
|
|
361
332
|
function setStatus(status) {
|
|
362
333
|
statusDot.className = 'status-dot ' + status;
|
|
363
|
-
statusText.textContent = status === 'connected' ? 'Connected' : status === 'disconnected' ? 'Disconnected' : 'Connecting...';
|
|
364
334
|
}
|
|
365
|
-
|
|
335
|
+
|
|
366
336
|
function showToast(message, duration = 3000, isSuccess = false) {
|
|
367
337
|
toast.textContent = message;
|
|
368
338
|
toast.className = 'toast visible' + (isSuccess ? ' success' : '');
|
|
369
|
-
setTimeout(() =>
|
|
339
|
+
setTimeout(() => toast.classList.remove('visible'), duration);
|
|
370
340
|
}
|
|
371
|
-
|
|
341
|
+
|
|
342
|
+
function escapeHtml(text) {
|
|
343
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function showLoading(panelName, msg = 'Loading...') {
|
|
347
|
+
const p = panels[panelName];
|
|
348
|
+
if (p.loading) { p.loading.querySelector('span').textContent = msg; p.loading.style.display = 'flex'; }
|
|
349
|
+
if (p.content) p.content.style.display = 'none';
|
|
350
|
+
if (p.header) p.header.style.display = 'none';
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function hideLoading(panelName) {
|
|
354
|
+
const p = panels[panelName];
|
|
355
|
+
if (p.loading) p.loading.style.display = 'none';
|
|
356
|
+
if (p.content) p.content.style.display = 'block';
|
|
357
|
+
if (p.header) p.header.style.display = 'flex';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function showEmptyState(panelName, icon, message) {
|
|
361
|
+
const p = panels[panelName];
|
|
362
|
+
p.content.innerHTML = `<div class="empty-state"><span class="codicon ${icon}"></span><p>${message}</p></div>`;
|
|
363
|
+
hideLoading(panelName);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// =============================================================================
|
|
372
367
|
// Navigation
|
|
368
|
+
// =============================================================================
|
|
373
369
|
navTabs.addEventListener('click', (e) => {
|
|
374
370
|
const tab = e.target.closest('.nav-tab');
|
|
375
|
-
if (!tab) return;
|
|
371
|
+
if (!tab || tab.dataset.panel === activePanel) return;
|
|
376
372
|
|
|
377
|
-
|
|
378
|
-
if (panelName === activePanel) return;
|
|
373
|
+
if (activePanel === 'tasks') stopTasksPolling();
|
|
379
374
|
|
|
380
|
-
// Update tabs
|
|
381
375
|
navTabs.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
|
|
382
376
|
tab.classList.add('active');
|
|
383
377
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
panels[key].panel.classList.toggle('active', key === panelName);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
activePanel = panelName;
|
|
378
|
+
Object.keys(panels).forEach(key => panels[key].panel.classList.toggle('active', key === tab.dataset.panel));
|
|
379
|
+
activePanel = tab.dataset.panel;
|
|
390
380
|
|
|
391
|
-
|
|
392
|
-
if (selectedCascadeId) {
|
|
393
|
-
fetchPanelContent(panelName, selectedCascadeId);
|
|
394
|
-
}
|
|
381
|
+
if (selectedCascadeId) fetchPanelContent(activePanel, selectedCascadeId);
|
|
395
382
|
});
|
|
396
|
-
|
|
397
|
-
//
|
|
383
|
+
|
|
384
|
+
// =============================================================================
|
|
385
|
+
// WebSocket Connection
|
|
386
|
+
// =============================================================================
|
|
398
387
|
function connect() {
|
|
399
388
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
400
389
|
ws = new WebSocket(`${protocol}//${window.location.host}`);
|
|
401
390
|
|
|
402
|
-
ws.onopen = () => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
ws.onmessage = (e) => {
|
|
408
|
-
try {
|
|
409
|
-
handleMessage(JSON.parse(e.data));
|
|
410
|
-
} catch (err) {
|
|
411
|
-
console.error('Failed to parse message:', err);
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
ws.onclose = () => {
|
|
416
|
-
setStatus('disconnected');
|
|
417
|
-
scheduleReconnect();
|
|
418
|
-
};
|
|
419
|
-
|
|
391
|
+
ws.onopen = () => { setStatus('connected'); reconnectAttempts = 0; };
|
|
392
|
+
ws.onmessage = (e) => { try { handleMessage(JSON.parse(e.data)); } catch (err) {} };
|
|
393
|
+
ws.onclose = () => { setStatus('disconnected'); scheduleReconnect(); };
|
|
420
394
|
ws.onerror = () => {};
|
|
421
395
|
}
|
|
422
|
-
|
|
396
|
+
|
|
423
397
|
function scheduleReconnect() {
|
|
424
398
|
if (reconnectTimeout) clearTimeout(reconnectTimeout);
|
|
425
399
|
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
|
|
426
400
|
reconnectAttempts++;
|
|
427
401
|
reconnectTimeout = setTimeout(connect, delay);
|
|
428
402
|
}
|
|
429
|
-
|
|
403
|
+
|
|
430
404
|
function handleMessage(data) {
|
|
431
405
|
if (data.type === 'cascade_list') {
|
|
432
406
|
cascades = data.cascades || [];
|
|
433
|
-
if (!selectedCascadeId && cascades.length > 0)
|
|
434
|
-
selectCascade(cascades[0].id);
|
|
435
|
-
}
|
|
407
|
+
if (!selectedCascadeId && cascades.length > 0) selectCascade(cascades[0].id);
|
|
436
408
|
} else if (data.type === 'snapshot_update' && data.cascadeId === selectedCascadeId) {
|
|
437
|
-
// Only fetch if user is not typing AND the updated panel is the active one
|
|
438
|
-
// AND we're not currently rendering (prevent flickering from rapid updates)
|
|
439
409
|
const panel = data.panel || 'chat';
|
|
440
410
|
if (!isTypingInKiroInput && panel === activePanel && !isRendering) {
|
|
441
|
-
// Debounce rapid updates - wait 500ms before fetching
|
|
442
411
|
if (updateDebounceTimer) clearTimeout(updateDebounceTimer);
|
|
443
|
-
updateDebounceTimer = setTimeout(() =>
|
|
444
|
-
fetchPanelContent(panel, data.cascadeId, true); // true = isUpdate (don't scroll)
|
|
445
|
-
}, 500);
|
|
412
|
+
updateDebounceTimer = setTimeout(() => fetchPanelContent(panel, data.cascadeId, true), 500);
|
|
446
413
|
}
|
|
447
414
|
}
|
|
448
415
|
}
|
|
449
|
-
|
|
416
|
+
|
|
450
417
|
function selectCascade(cascadeId) {
|
|
451
418
|
selectedCascadeId = cascadeId;
|
|
452
419
|
currentStyles = null;
|
|
453
|
-
workspaceFiles = [];
|
|
420
|
+
workspaceFiles = [];
|
|
421
|
+
lastTasksHash = '';
|
|
422
|
+
stopTasksPolling();
|
|
454
423
|
|
|
455
424
|
if (cascadeId) {
|
|
456
|
-
fetchStyles(cascadeId).then(() =>
|
|
457
|
-
fetchPanelContent(activePanel, cascadeId);
|
|
458
|
-
});
|
|
425
|
+
fetchStyles(cascadeId).then(() => fetchPanelContent(activePanel, cascadeId));
|
|
459
426
|
} else {
|
|
460
427
|
showLoading('chat', 'Waiting for Kiro...');
|
|
461
428
|
}
|
|
462
429
|
}
|
|
463
|
-
|
|
430
|
+
|
|
431
|
+
// =============================================================================
|
|
432
|
+
// Data Fetching
|
|
433
|
+
// =============================================================================
|
|
464
434
|
async function fetchStyles(cascadeId) {
|
|
465
435
|
try {
|
|
466
436
|
const r = await fetch(`/styles/${cascadeId}`);
|
|
467
437
|
if (r.ok) currentStyles = await r.text();
|
|
468
|
-
} catch (e) {
|
|
469
|
-
console.error('Failed to fetch styles:', e);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function showLoading(panelName, msg = 'Loading...') {
|
|
474
|
-
const p = panels[panelName];
|
|
475
|
-
if (p.loading) {
|
|
476
|
-
p.loading.querySelector('span').textContent = msg;
|
|
477
|
-
p.loading.style.display = 'flex';
|
|
478
|
-
}
|
|
479
|
-
if (p.content) p.content.style.display = 'none';
|
|
480
|
-
if (p.header) p.header.style.display = 'none';
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function hideLoading(panelName) {
|
|
484
|
-
const p = panels[panelName];
|
|
485
|
-
if (p.loading) p.loading.style.display = 'none';
|
|
486
|
-
if (p.content) p.content.style.display = 'block';
|
|
487
|
-
if (p.header) p.header.style.display = 'flex';
|
|
438
|
+
} catch (e) {}
|
|
488
439
|
}
|
|
489
|
-
|
|
490
|
-
// Fetch panel content
|
|
440
|
+
|
|
491
441
|
async function fetchPanelContent(panelName, cascadeId, isUpdate = false) {
|
|
492
442
|
switch (panelName) {
|
|
493
|
-
case 'chat':
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
case 'editor':
|
|
497
|
-
await fetchEditorSnapshot(cascadeId, isUpdate);
|
|
498
|
-
break;
|
|
443
|
+
case 'chat': await fetchChatSnapshot(cascadeId, isUpdate); break;
|
|
444
|
+
case 'editor': await fetchEditorSnapshot(cascadeId, isUpdate); break;
|
|
445
|
+
case 'tasks': await fetchTasksContent(cascadeId, isUpdate); break;
|
|
499
446
|
}
|
|
500
447
|
}
|
|
501
|
-
|
|
502
|
-
// Chat snapshot
|
|
448
|
+
|
|
503
449
|
async function fetchChatSnapshot(cascadeId, isUpdate = false) {
|
|
504
450
|
try {
|
|
505
451
|
const r = await fetch(`/snapshot/${cascadeId}`);
|
|
506
452
|
if (!r.ok) {
|
|
507
|
-
if (navigationPending) {
|
|
508
|
-
setTimeout(() => fetchChatSnapshot(cascadeId, isUpdate), 500);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
453
|
+
if (navigationPending) { setTimeout(() => fetchChatSnapshot(cascadeId, isUpdate), 500); return; }
|
|
511
454
|
if (r.status === 404) showLoading('chat', 'Waiting for snapshot...');
|
|
512
455
|
return;
|
|
513
456
|
}
|
|
514
457
|
const snapshot = await r.json();
|
|
515
|
-
if (snapshot
|
|
458
|
+
if (snapshot?.html?.length > 100) {
|
|
516
459
|
lastSuccessfulSnapshot = snapshot;
|
|
517
460
|
navigationPending = false;
|
|
518
461
|
renderChatSnapshot(snapshot, isUpdate);
|
|
@@ -525,68 +468,7 @@
|
|
|
525
468
|
if (!navigationPending) showToast('Failed to load chat');
|
|
526
469
|
}
|
|
527
470
|
}
|
|
528
|
-
|
|
529
|
-
function renderChatSnapshot(snapshot, isUpdate = false) {
|
|
530
|
-
// Prevent concurrent renders
|
|
531
|
-
if (isRendering) return;
|
|
532
|
-
isRendering = true;
|
|
533
|
-
|
|
534
|
-
// Save scroll position of inner scrollable element before update
|
|
535
|
-
let innerScrollable = panels.chat.content.querySelector('.overflow-y-auto, [class*="scroll"]');
|
|
536
|
-
const hadContent = innerScrollable && innerScrollable.scrollHeight > 100;
|
|
537
|
-
const scrollTop = innerScrollable ? innerScrollable.scrollTop : 0;
|
|
538
|
-
const scrollHeight = innerScrollable ? innerScrollable.scrollHeight : 0;
|
|
539
|
-
const clientHeight = innerScrollable ? innerScrollable.clientHeight : 0;
|
|
540
|
-
const wasAtBottom = !hadContent || (scrollHeight - scrollTop - clientHeight < 100);
|
|
541
|
-
|
|
542
|
-
let html = getBaseStyles();
|
|
543
|
-
if (currentStyles) html += `<style>${currentStyles}</style>`;
|
|
544
|
-
html += snapshot.html || '';
|
|
545
|
-
|
|
546
|
-
panels.chat.content.innerHTML = html;
|
|
547
|
-
|
|
548
|
-
// Set background color - use solid dark background instead of transparent
|
|
549
|
-
if (snapshot.bodyBg) {
|
|
550
|
-
panels.chat.container.style.background = snapshot.bodyBg;
|
|
551
|
-
} else {
|
|
552
|
-
// Fallback to solid dark background matching Kiro IDE
|
|
553
|
-
panels.chat.container.style.background = '#1e1e1e';
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
hideLoading('chat');
|
|
557
|
-
|
|
558
|
-
// Remove placeholder text that overlaps with input
|
|
559
|
-
removePlaceholderText();
|
|
560
|
-
|
|
561
|
-
// Find the new inner scrollable element and scroll it
|
|
562
|
-
requestAnimationFrame(() => {
|
|
563
|
-
requestAnimationFrame(() => {
|
|
564
|
-
// Find all scrollable elements and scroll them
|
|
565
|
-
const scrollables = panels.chat.content.querySelectorAll('.overflow-y-auto, [class*="scroll"]');
|
|
566
|
-
scrollables.forEach(el => {
|
|
567
|
-
if (el.scrollHeight > el.clientHeight + 10) {
|
|
568
|
-
if (!isUpdate || wasAtBottom) {
|
|
569
|
-
el.scrollTop = el.scrollHeight;
|
|
570
|
-
} else if (hadContent) {
|
|
571
|
-
el.scrollTop = scrollTop;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
// Also scroll the main container if needed
|
|
577
|
-
if (!isUpdate || wasAtBottom) {
|
|
578
|
-
panels.chat.content.scrollTop = panels.chat.content.scrollHeight;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Release rendering lock
|
|
582
|
-
isRendering = false;
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
makeInteractive();
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Editor snapshot
|
|
471
|
+
|
|
590
472
|
async function fetchEditorSnapshot(cascadeId, isUpdate = false) {
|
|
591
473
|
try {
|
|
592
474
|
const r = await fetch(`/editor/${cascadeId}`);
|
|
@@ -594,320 +476,192 @@
|
|
|
594
476
|
showEmptyState('editor', 'codicon-file-code', 'No file is currently open. Open a file in Kiro to view it here.');
|
|
595
477
|
return;
|
|
596
478
|
}
|
|
597
|
-
|
|
598
|
-
renderEditorSnapshot(data, isUpdate);
|
|
479
|
+
renderEditorSnapshot(await r.json(), isUpdate);
|
|
599
480
|
} catch (e) {
|
|
600
481
|
showEmptyState('editor', 'codicon-file-code', 'Failed to load editor');
|
|
601
482
|
}
|
|
602
483
|
}
|
|
603
|
-
|
|
604
|
-
function
|
|
605
|
-
|
|
484
|
+
|
|
485
|
+
async function fetchTasksContent(cascadeId, isUpdate = false) {
|
|
486
|
+
// Only show loading if we don't have cached data
|
|
487
|
+
const hasCachedData = tasksData.length > 0 && lastTasksHash;
|
|
488
|
+
if (!isUpdate && !hasCachedData) showLoading('tasks', 'Loading tasks...');
|
|
606
489
|
|
|
607
|
-
|
|
608
|
-
|
|
490
|
+
try {
|
|
491
|
+
const r = await fetch(`/tasks/${cascadeId}`);
|
|
492
|
+
if (!r.ok) { showTasksEmptyState('No tasks found.'); stopTasksPolling(); return; }
|
|
493
|
+
|
|
494
|
+
const data = await r.json();
|
|
495
|
+
const newTasks = data.tasks || [];
|
|
496
|
+
const newHash = JSON.stringify(newTasks);
|
|
497
|
+
|
|
498
|
+
if (newTasks.length === 0) { showTasksEmptyState('No task files found.'); stopTasksPolling(); return; }
|
|
499
|
+
|
|
500
|
+
if (newHash !== lastTasksHash) {
|
|
501
|
+
lastTasksHash = newHash;
|
|
502
|
+
tasksData = newTasks;
|
|
503
|
+
populateTasksDropdown();
|
|
504
|
+
if (selectedTaskIndex < 0 || selectedTaskIndex >= tasksData.length) selectedTaskIndex = 0;
|
|
505
|
+
renderTaskContent(tasksData[selectedTaskIndex]);
|
|
506
|
+
}
|
|
507
|
+
// Always ensure UI is visible after successful fetch
|
|
508
|
+
panels.tasks.header.style.display = 'flex';
|
|
509
|
+
panels.tasks.content.style.display = 'block';
|
|
510
|
+
hideLoading('tasks');
|
|
511
|
+
startTasksPolling(cascadeId);
|
|
512
|
+
} catch (e) {
|
|
513
|
+
if (!isUpdate) showTasksEmptyState('Failed to load tasks');
|
|
514
|
+
stopTasksPolling();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// =============================================================================
|
|
519
|
+
// Tasks Panel
|
|
520
|
+
// =============================================================================
|
|
521
|
+
function startTasksPolling(cascadeId) {
|
|
522
|
+
if (tasksPollingTimer) return;
|
|
523
|
+
tasksPollingTimer = setInterval(() => {
|
|
524
|
+
if (activePanel === 'tasks' && selectedCascadeId) fetchTasksContent(selectedCascadeId, true);
|
|
525
|
+
}, 3000);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function stopTasksPolling() {
|
|
529
|
+
if (tasksPollingTimer) { clearInterval(tasksPollingTimer); tasksPollingTimer = null; }
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function showTasksEmptyState(message) {
|
|
533
|
+
panels.tasks.header.style.display = 'none';
|
|
534
|
+
panels.tasks.content.innerHTML = `<div class="tasks-empty"><span class="codicon codicon-checklist"></span><p>${message}</p></div>`;
|
|
535
|
+
panels.tasks.content.style.display = 'flex';
|
|
536
|
+
hideLoading('tasks');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function populateTasksDropdown() {
|
|
540
|
+
const dropdown = panels.tasks.dropdown;
|
|
541
|
+
dropdown.innerHTML = '';
|
|
542
|
+
tasksData.forEach((task, index) => {
|
|
543
|
+
const option = document.createElement('option');
|
|
544
|
+
option.value = index;
|
|
545
|
+
option.textContent = task.name;
|
|
546
|
+
if (index === selectedTaskIndex) option.selected = true;
|
|
547
|
+
dropdown.appendChild(option);
|
|
548
|
+
});
|
|
609
549
|
|
|
610
|
-
|
|
611
|
-
|
|
550
|
+
dropdown.onchange = async (e) => {
|
|
551
|
+
selectedTaskIndex = parseInt(e.target.value, 10);
|
|
552
|
+
if (tasksData[selectedTaskIndex]) {
|
|
553
|
+
renderTaskContent(tasksData[selectedTaskIndex]);
|
|
554
|
+
await openSpecInKiro(tasksData[selectedTaskIndex].name);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async function openSpecInKiro(specName) {
|
|
560
|
+
if (!selectedCascadeId || !specName) return;
|
|
561
|
+
showToast(`Opening ${specName}...`, 1500);
|
|
562
|
+
try {
|
|
563
|
+
await fetch(`/open-spec/${selectedCascadeId}`, {
|
|
564
|
+
method: 'POST',
|
|
565
|
+
headers: { 'Content-Type': 'application/json' },
|
|
566
|
+
body: JSON.stringify({ specName })
|
|
567
|
+
});
|
|
568
|
+
} catch (e) {}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function renderTaskContent(task) {
|
|
572
|
+
if (!task?.content) { panels.tasks.content.innerHTML = '<div class="tasks-empty"><p>No content</p></div>'; return; }
|
|
573
|
+
panels.tasks.content.innerHTML = `<div class="task-md">${parseTaskMarkdown(task.content)}</div>`;
|
|
574
|
+
|
|
575
|
+
panels.tasks.content.querySelectorAll('.task-group-header').forEach(header => {
|
|
576
|
+
const clickables = header.querySelectorAll('.task-group-title, .task-group-indicator, .task-group-arrow, .task-group-count');
|
|
577
|
+
clickables.forEach(el => {
|
|
578
|
+
if (el) {
|
|
579
|
+
el.style.cursor = 'pointer';
|
|
580
|
+
el.onclick = (e) => { e.stopPropagation(); header.closest('.task-group').classList.toggle('expanded'); };
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function parseTaskMarkdown(markdown) {
|
|
587
|
+
const lines = markdown.split('\n');
|
|
588
|
+
let html = '';
|
|
589
|
+
let currentGroupNumber = '', currentGroupTitle = '', currentGroupTasks = [];
|
|
612
590
|
|
|
613
|
-
|
|
614
|
-
|
|
591
|
+
const arrowIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M9 18l6-6-6-6"/></svg>`;
|
|
592
|
+
const checkIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M20 6L9 17l-5-5"/></svg>`;
|
|
615
593
|
|
|
616
|
-
|
|
617
|
-
|
|
594
|
+
const flushGroup = () => {
|
|
595
|
+
if (currentGroupTitle && currentGroupTasks.length > 0) {
|
|
596
|
+
const completed = currentGroupTasks.filter(t => t.checked).length;
|
|
597
|
+
const total = currentGroupTasks.length;
|
|
598
|
+
const isComplete = completed === total;
|
|
599
|
+
const hasIncomplete = completed < total;
|
|
600
|
+
|
|
601
|
+
html += `<div class="task-group${hasIncomplete ? ' expanded' : ''}${isComplete ? ' completed' : ''}" data-task-number="${currentGroupNumber}">`;
|
|
602
|
+
html += `<div class="task-group-header"><div class="task-group-indicator">${checkIcon}</div>`;
|
|
603
|
+
html += `<span class="task-group-number">${currentGroupNumber}</span>`;
|
|
604
|
+
html += `<span class="task-group-title">${escapeHtml(currentGroupTitle)}</span>`;
|
|
605
|
+
html += `<div class="task-group-meta"><span class="task-group-count">${completed}/${total}</span>`;
|
|
606
|
+
html += `<span class="task-group-arrow">${arrowIcon}</span></div></div>`;
|
|
607
|
+
html += `<div class="task-group-body"><div class="task-group-body-inner">`;
|
|
608
|
+
|
|
609
|
+
currentGroupTasks.slice(1).forEach(task => {
|
|
610
|
+
const indent = task.indent > 1 ? ` indent-${Math.min(task.indent, 3)}` : '';
|
|
611
|
+
html += `<div class="task-item${indent}"><span class="task-checkbox${task.checked ? ' checked' : ''}">${checkIcon}</span>`;
|
|
612
|
+
html += `<span class="task-text${task.checked ? ' completed' : ''}">${escapeHtml(task.text)}</span></div>`;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
html += `</div></div></div>`;
|
|
616
|
+
}
|
|
617
|
+
currentGroupNumber = ''; currentGroupTitle = ''; currentGroupTasks = [];
|
|
618
|
+
};
|
|
618
619
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
let html = '';
|
|
620
|
+
for (const line of lines) {
|
|
621
|
+
const trimmed = line.trim();
|
|
622
|
+
if (!trimmed || trimmed.startsWith('#') || !trimmed.startsWith('- [')) continue;
|
|
623
623
|
|
|
624
|
-
|
|
625
|
-
if (
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
624
|
+
const mainMatch = trimmed.match(/^- \[([ xX])\] (\d+)\.?\s+(.*)$/);
|
|
625
|
+
if (mainMatch) {
|
|
626
|
+
flushGroup();
|
|
627
|
+
currentGroupNumber = mainMatch[2];
|
|
628
|
+
currentGroupTitle = mainMatch[3];
|
|
629
|
+
currentGroupTasks.push({ text: mainMatch[3], checked: mainMatch[1].toLowerCase() === 'x', indent: 0 });
|
|
630
|
+
continue;
|
|
630
631
|
}
|
|
631
632
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
// Filter out empty lines at the start if there are too many
|
|
638
|
-
let startIdx = 0;
|
|
639
|
-
while (startIdx < lines.length && lines[startIdx].trim() === '' && startIdx < 3) {
|
|
640
|
-
startIdx++;
|
|
633
|
+
const subMatch = trimmed.match(/^- \[([ xX])\] (\d+\.\d+)\s+(.*)$/);
|
|
634
|
+
if (subMatch && currentGroupTitle) {
|
|
635
|
+
currentGroupTasks.push({ text: subMatch[3], checked: subMatch[1].toLowerCase() === 'x', indent: 1 });
|
|
636
|
+
continue;
|
|
641
637
|
}
|
|
642
638
|
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
panels.editor.content.innerHTML = html;
|
|
660
|
-
panels.editor.content.style.display = 'block';
|
|
661
|
-
console.log('[Editor] Rendered', displayLines.length, 'lines of code starting from line', startLineNum);
|
|
662
|
-
} else if (data.html && data.html.length > 100) {
|
|
663
|
-
// Fallback to raw HTML if no text content
|
|
664
|
-
let html = getBaseStyles();
|
|
665
|
-
if (currentStyles) html += `<style>${currentStyles}</style>`;
|
|
666
|
-
html += data.html;
|
|
667
|
-
panels.editor.content.innerHTML = html;
|
|
668
|
-
panels.editor.content.style.display = 'block';
|
|
669
|
-
console.log('[Editor] Rendered raw HTML');
|
|
670
|
-
} else {
|
|
671
|
-
showEmptyState('editor', 'codicon-file-code', 'No editor content available. Open a file in Kiro to view it here.');
|
|
672
|
-
return;
|
|
639
|
+
const taskMatch = line.match(/^(\s*)- \[([ xX])\] (.*)$/);
|
|
640
|
+
if (taskMatch) {
|
|
641
|
+
const indent = Math.floor(taskMatch[1].length / 2);
|
|
642
|
+
const isChecked = taskMatch[2].toLowerCase() === 'x';
|
|
643
|
+
const text = taskMatch[3];
|
|
644
|
+
|
|
645
|
+
const numMatch = text.match(/^(\d+)\.?\s+(.*)$/);
|
|
646
|
+
if (numMatch && indent === 0) {
|
|
647
|
+
flushGroup();
|
|
648
|
+
currentGroupNumber = numMatch[1];
|
|
649
|
+
currentGroupTitle = numMatch[2];
|
|
650
|
+
currentGroupTasks.push({ text: numMatch[2], checked: isChecked, indent: 0 });
|
|
651
|
+
} else if (currentGroupTitle) {
|
|
652
|
+
currentGroupTasks.push({ text, checked: isChecked, indent: Math.max(1, indent) });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
673
655
|
}
|
|
674
|
-
hideLoading('editor');
|
|
675
656
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
panels.editor.content.scrollTop = scrollTop;
|
|
679
|
-
}
|
|
657
|
+
flushGroup();
|
|
658
|
+
return html;
|
|
680
659
|
}
|
|
681
660
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
// Skip highlighting for very long lines or if no language
|
|
688
|
-
if (escaped.length > 1000 || !language) {
|
|
689
|
-
return escaped;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Language detection
|
|
693
|
-
const isJS = ['javascript', 'typescript', 'jsx', 'tsx', 'js', 'ts'].includes(language);
|
|
694
|
-
const isPython = language === 'python' || language === 'py';
|
|
695
|
-
const isJSON = language === 'json';
|
|
696
|
-
const isYAML = language === 'yaml' || language === 'yml';
|
|
697
|
-
const isShell = ['bash', 'sh', 'shell', 'zsh'].includes(language);
|
|
698
|
-
const isHTML = language === 'html' || language === 'xml';
|
|
699
|
-
const isCSS = language === 'css' || language === 'scss' || language === 'sass' || language === 'less';
|
|
700
|
-
const isMarkdown = language === 'markdown' || language === 'md';
|
|
701
|
-
|
|
702
|
-
// For HTML files, use a simpler approach - just colorize without spans that could break
|
|
703
|
-
if (isHTML) {
|
|
704
|
-
let result = escaped;
|
|
705
|
-
// Comments
|
|
706
|
-
result = result.replace(/(<!--[\s\S]*?-->)/g, '<span class="tok-cmt">$1</span>');
|
|
707
|
-
// Strings in attributes
|
|
708
|
-
result = result.replace(/=("[^&]*?")/g, '=<span class="tok-str">$1</span>');
|
|
709
|
-
result = result.replace(/=('[^&]*?')/g, '=<span class="tok-str">$1</span>');
|
|
710
|
-
return result;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// For CSS files
|
|
714
|
-
if (isCSS) {
|
|
715
|
-
let result = escaped;
|
|
716
|
-
// Comments
|
|
717
|
-
result = result.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="tok-cmt">$1</span>');
|
|
718
|
-
// Strings
|
|
719
|
-
result = result.replace(/("[^&]*?")/g, '<span class="tok-str">$1</span>');
|
|
720
|
-
result = result.replace(/('[^&]*?')/g, '<span class="tok-str">$1</span>');
|
|
721
|
-
// Numbers with units
|
|
722
|
-
result = result.replace(/:\s*([0-9]+(?:\.[0-9]+)?(?:px|em|rem|%|vh|vw|s|ms)?)/g, ': <span class="tok-num">$1</span>');
|
|
723
|
-
// Hex colors
|
|
724
|
-
result = result.replace(/(#[0-9a-fA-F]{3,8})\b/g, '<span class="tok-num">$1</span>');
|
|
725
|
-
return result;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// Build result by processing character by character for other languages
|
|
729
|
-
let result = '';
|
|
730
|
-
let i = 0;
|
|
731
|
-
|
|
732
|
-
while (i < escaped.length) {
|
|
733
|
-
// Check for comments
|
|
734
|
-
if (isJS && escaped.slice(i, i + 2) === '//') {
|
|
735
|
-
result += '<span class="tok-cmt">' + escaped.slice(i) + '</span>';
|
|
736
|
-
break;
|
|
737
|
-
}
|
|
738
|
-
if ((isPython || isShell || isYAML) && escaped[i] === '#') {
|
|
739
|
-
result += '<span class="tok-cmt">' + escaped.slice(i) + '</span>';
|
|
740
|
-
break;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// Check for strings (escaped quotes: " or ')
|
|
744
|
-
if (escaped.slice(i, i + 6) === '"') {
|
|
745
|
-
const endIdx = escaped.indexOf('"', i + 6);
|
|
746
|
-
if (endIdx !== -1) {
|
|
747
|
-
const str = escaped.slice(i, endIdx + 6);
|
|
748
|
-
result += '<span class="tok-str">' + str + '</span>';
|
|
749
|
-
i = endIdx + 6;
|
|
750
|
-
continue;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
if (escaped.slice(i, i + 5) === ''') {
|
|
754
|
-
const endIdx = escaped.indexOf(''', i + 5);
|
|
755
|
-
if (endIdx !== -1) {
|
|
756
|
-
const str = escaped.slice(i, endIdx + 5);
|
|
757
|
-
result += '<span class="tok-str">' + str + '</span>';
|
|
758
|
-
i = endIdx + 5;
|
|
759
|
-
continue;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// Check for numbers
|
|
764
|
-
if (/[0-9]/.test(escaped[i]) && (i === 0 || /[^a-zA-Z_]/.test(escaped[i - 1]))) {
|
|
765
|
-
let numEnd = i;
|
|
766
|
-
while (numEnd < escaped.length && /[0-9.]/.test(escaped[numEnd])) {
|
|
767
|
-
numEnd++;
|
|
768
|
-
}
|
|
769
|
-
if (numEnd > i) {
|
|
770
|
-
result += '<span class="tok-num">' + escaped.slice(i, numEnd) + '</span>';
|
|
771
|
-
i = numEnd;
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// Check for keywords (word boundary)
|
|
777
|
-
if (/[a-zA-Z_]/.test(escaped[i]) && (i === 0 || /[^a-zA-Z0-9_]/.test(escaped[i - 1]))) {
|
|
778
|
-
let wordEnd = i;
|
|
779
|
-
while (wordEnd < escaped.length && /[a-zA-Z0-9_]/.test(escaped[wordEnd])) {
|
|
780
|
-
wordEnd++;
|
|
781
|
-
}
|
|
782
|
-
const word = escaped.slice(i, wordEnd);
|
|
783
|
-
|
|
784
|
-
// Define keywords per language
|
|
785
|
-
let keywords = [];
|
|
786
|
-
if (isJS) {
|
|
787
|
-
keywords = ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'import', 'export', 'from', 'async', 'await', 'try', 'catch', 'throw', 'new', 'this', 'class', 'extends', 'true', 'false', 'null', 'undefined', 'typeof', 'instanceof', 'switch', 'case', 'break', 'continue', 'default', 'static', 'get', 'set'];
|
|
788
|
-
} else if (isPython) {
|
|
789
|
-
keywords = ['def', 'class', 'return', 'if', 'elif', 'else', 'for', 'while', 'import', 'from', 'as', 'try', 'except', 'raise', 'with', 'async', 'await', 'None', 'True', 'False', 'self', 'and', 'or', 'not', 'in', 'is', 'lambda', 'pass', 'break', 'continue'];
|
|
790
|
-
} else if (isJSON) {
|
|
791
|
-
keywords = ['true', 'false', 'null'];
|
|
792
|
-
} else if (isYAML) {
|
|
793
|
-
keywords = ['true', 'false', 'null', 'yes', 'no'];
|
|
794
|
-
} else if (!isMarkdown) {
|
|
795
|
-
keywords = ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'import', 'export', 'true', 'false', 'null', 'class', 'public', 'private', 'static', 'void', 'int', 'string', 'bool'];
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
if (keywords.includes(word)) {
|
|
799
|
-
result += '<span class="tok-kw">' + word + '</span>';
|
|
800
|
-
} else {
|
|
801
|
-
result += word;
|
|
802
|
-
}
|
|
803
|
-
i = wordEnd;
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// Default: just add the character
|
|
808
|
-
result += escaped[i];
|
|
809
|
-
i++;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
return result;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// Helper functions
|
|
816
|
-
function escapeHtml(text) {
|
|
817
|
-
return text
|
|
818
|
-
.replace(/&/g, '&')
|
|
819
|
-
.replace(/</g, '<')
|
|
820
|
-
.replace(/>/g, '>')
|
|
821
|
-
.replace(/"/g, '"')
|
|
822
|
-
.replace(/'/g, ''');
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
function showEmptyState(panelName, icon, message) {
|
|
826
|
-
const p = panels[panelName];
|
|
827
|
-
p.content.innerHTML = `
|
|
828
|
-
<div class="empty-state">
|
|
829
|
-
<span class="codicon ${icon}"></span>
|
|
830
|
-
<p>${message}</p>
|
|
831
|
-
</div>
|
|
832
|
-
`;
|
|
833
|
-
hideLoading(panelName);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
function toggleSection(header) {
|
|
837
|
-
header.classList.toggle('collapsed');
|
|
838
|
-
const content = header.nextElementSibling;
|
|
839
|
-
if (content) content.classList.toggle('collapsed');
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Remove placeholder text that overlaps with input field
|
|
843
|
-
function removePlaceholderText() {
|
|
844
|
-
const content = panels.chat.content;
|
|
845
|
-
|
|
846
|
-
// Method 1: Find elements by class name containing "placeholder"
|
|
847
|
-
content.querySelectorAll('[class*="placeholder"], [class*="Placeholder"]').forEach(el => {
|
|
848
|
-
// Don't remove actual input elements
|
|
849
|
-
if (el.matches('[contenteditable], textarea, input, [data-lexical-editor]')) return;
|
|
850
|
-
if (el.querySelector('[contenteditable], textarea, input, [data-lexical-editor]')) return;
|
|
851
|
-
|
|
852
|
-
// Hide it
|
|
853
|
-
el.style.display = 'none';
|
|
854
|
-
el.style.visibility = 'hidden';
|
|
855
|
-
el.style.opacity = '0';
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
// Method 2: Find elements with data-placeholder attribute
|
|
859
|
-
content.querySelectorAll('[data-placeholder]').forEach(el => {
|
|
860
|
-
// Remove the data-placeholder attribute to prevent CSS ::before content
|
|
861
|
-
if (!el.matches('[contenteditable], [data-lexical-editor]')) {
|
|
862
|
-
el.style.display = 'none';
|
|
863
|
-
}
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
// Method 3: Find any element containing placeholder-like text and hide it
|
|
867
|
-
const placeholderTexts = ['ask a question', 'describe a task', 'type a message', 'enter a message'];
|
|
868
|
-
|
|
869
|
-
const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, null, false);
|
|
870
|
-
const nodesToHide = [];
|
|
871
|
-
|
|
872
|
-
while (walker.nextNode()) {
|
|
873
|
-
const text = (walker.currentNode.textContent || '').toLowerCase();
|
|
874
|
-
for (const placeholder of placeholderTexts) {
|
|
875
|
-
if (text.includes(placeholder)) {
|
|
876
|
-
const parent = walker.currentNode.parentElement;
|
|
877
|
-
if (parent && !parent.matches('[contenteditable], textarea, input, [data-lexical-editor]')) {
|
|
878
|
-
nodesToHide.push(parent);
|
|
879
|
-
}
|
|
880
|
-
break;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
nodesToHide.forEach(el => {
|
|
886
|
-
el.style.display = 'none';
|
|
887
|
-
el.style.visibility = 'hidden';
|
|
888
|
-
});
|
|
889
|
-
|
|
890
|
-
// Method 4: Find absolutely positioned elements inside input containers and hide them
|
|
891
|
-
// These are often placeholder overlays
|
|
892
|
-
const inputContainers = content.querySelectorAll('[class*="input"], [class*="composer"], [class*="editor"]');
|
|
893
|
-
inputContainers.forEach(container => {
|
|
894
|
-
container.querySelectorAll('*').forEach(el => {
|
|
895
|
-
const style = window.getComputedStyle(el);
|
|
896
|
-
// If it's absolutely positioned and not the input itself, it might be a placeholder
|
|
897
|
-
if (style.position === 'absolute' &&
|
|
898
|
-
!el.matches('[contenteditable], textarea, input, [data-lexical-editor], button, svg')) {
|
|
899
|
-
const text = (el.textContent || '').toLowerCase();
|
|
900
|
-
if (text.includes('ask') || text.includes('task') || text.includes('question') ||
|
|
901
|
-
text.includes('describe') || text.includes('message') || text.includes('type')) {
|
|
902
|
-
el.style.display = 'none';
|
|
903
|
-
el.style.visibility = 'hidden';
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
});
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// Base styles for chat panel
|
|
661
|
+
|
|
662
|
+
// =============================================================================
|
|
663
|
+
// Chat Rendering
|
|
664
|
+
// =============================================================================
|
|
911
665
|
function getBaseStyles() {
|
|
912
666
|
return `<style>
|
|
913
667
|
:root, body, #root, .app {
|
|
@@ -944,38 +698,30 @@
|
|
|
944
698
|
background-color: #1e1e1e !important;
|
|
945
699
|
}
|
|
946
700
|
|
|
947
|
-
/* ========== HIDE PLACEHOLDER TEXT IN INPUT AREA ========== */
|
|
948
|
-
/* Placeholder removal is handled via JavaScript in removePlaceholderText() */
|
|
949
701
|
/* Keep input elements visible and interactive */
|
|
950
|
-
[contenteditable="true"],
|
|
951
|
-
[data-lexical-editor="true"],
|
|
952
|
-
.ProseMirror,
|
|
953
|
-
textarea {
|
|
702
|
+
[contenteditable="true"], [data-lexical-editor="true"], .ProseMirror, textarea {
|
|
954
703
|
display: block !important;
|
|
955
704
|
visibility: visible !important;
|
|
956
705
|
opacity: 1 !important;
|
|
957
706
|
pointer-events: auto !important;
|
|
958
707
|
}
|
|
959
708
|
|
|
960
|
-
/*
|
|
961
|
-
[role="tooltip"],
|
|
962
|
-
[data-tooltip],
|
|
709
|
+
/* Hide tooltips, popovers, and overlay elements */
|
|
710
|
+
[role="tooltip"], [data-tooltip],
|
|
963
711
|
[class*="tooltip"]:not(button):not([role="button"]),
|
|
964
712
|
[class*="Tooltip"]:not(button):not([role="button"]),
|
|
965
713
|
[class*="popover"]:not(button):not([role="button"]),
|
|
966
714
|
[class*="Popover"]:not(button):not([role="button"]),
|
|
967
715
|
[class*="overlay"]:not(button):not([role="button"]):not([class*="dropdown"]),
|
|
968
716
|
[class*="Overlay"]:not(button):not([role="button"]):not([class*="dropdown"]),
|
|
969
|
-
[class*="modal"],
|
|
970
|
-
[class*="Modal"] {
|
|
717
|
+
[class*="modal"], [class*="Modal"] {
|
|
971
718
|
display: none !important;
|
|
972
719
|
visibility: hidden !important;
|
|
973
720
|
opacity: 0 !important;
|
|
974
721
|
pointer-events: none !important;
|
|
975
722
|
}
|
|
976
723
|
|
|
977
|
-
/*
|
|
978
|
-
/* Dropdown menu panel - needs solid background and proper z-index */
|
|
724
|
+
/* Model selector dropdown */
|
|
979
725
|
[class*="dropdown-menu"], [class*="dropdownMenu"], [class*="DropdownMenu"],
|
|
980
726
|
[class*="dropdown-content"], [class*="dropdownContent"], [class*="DropdownContent"],
|
|
981
727
|
[class*="model-list"], [class*="modelList"], [class*="ModelList"],
|
|
@@ -1007,31 +753,12 @@
|
|
|
1007
753
|
color: #cccccc !important;
|
|
1008
754
|
}
|
|
1009
755
|
|
|
1010
|
-
/* Hide model descriptions in dropdown - only show model name */
|
|
1011
|
-
[class*="model-description"], [class*="modelDescription"], [class*="ModelDescription"],
|
|
1012
|
-
[class*="model-info"], [class*="modelInfo"], [class*="ModelInfo"],
|
|
1013
|
-
[class*="dropdown-item"] > span:not(:first-child),
|
|
1014
|
-
[class*="model-option"] > span:not(:first-child),
|
|
1015
|
-
[role="option"] > div:last-child,
|
|
1016
|
-
[role="option"] > span:last-child:not(:first-child),
|
|
1017
|
-
[role="menuitem"] > div:last-child,
|
|
1018
|
-
[class*="credit"], [class*="Credit"] {
|
|
1019
|
-
display: none !important;
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
/* Simplify model dropdown items - just show the name */
|
|
1023
|
-
[role="option"], [role="menuitem"] {
|
|
1024
|
-
flex-direction: row !important;
|
|
1025
|
-
align-items: center !important;
|
|
1026
|
-
gap: 8px !important;
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
756
|
[class*="dropdown-item"]:hover, [class*="dropdownItem"]:hover,
|
|
1030
757
|
[role="option"]:hover, [role="menuitem"]:hover {
|
|
1031
758
|
background: rgba(255, 255, 255, 0.1) !important;
|
|
1032
759
|
}
|
|
1033
760
|
|
|
1034
|
-
/* Model selector button
|
|
761
|
+
/* Model selector button */
|
|
1035
762
|
[class*="model-selector"], [class*="modelSelector"], [class*="ModelSelector"],
|
|
1036
763
|
[class*="model-dropdown"], [class*="modelDropdown"], [class*="ModelDropdown"],
|
|
1037
764
|
button[class*="dropdown"], [role="button"][class*="dropdown"],
|
|
@@ -1056,61 +783,28 @@
|
|
|
1056
783
|
background: rgba(255, 255, 255, 0.1) !important;
|
|
1057
784
|
}
|
|
1058
785
|
|
|
1059
|
-
/*
|
|
1060
|
-
[class*="notification"], [class*="Notification"],
|
|
1061
|
-
[class*="toast"], [class*="Toast"],
|
|
1062
|
-
[class*="banner"], [class*="Banner"],
|
|
1063
|
-
[class*="change-accepted"], [class*="changeAccepted"],
|
|
1064
|
-
[class*="revert"], [class*="Revert"],
|
|
1065
|
-
[class*="view-all"], [class*="viewAll"],
|
|
1066
|
-
[class*="status-bar-notification"],
|
|
1067
|
-
div[role="status"], div[role="alert"] {
|
|
1068
|
-
display: none !important;
|
|
1069
|
-
visibility: hidden !important;
|
|
1070
|
-
opacity: 0 !important;
|
|
1071
|
-
height: 0 !important;
|
|
1072
|
-
overflow: hidden !important;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
/* Model selector chevron/arrow icon */
|
|
1076
|
-
[class*="model-selector"] svg, [class*="modelSelector"] svg,
|
|
1077
|
-
[class*="model-dropdown"] svg, button[class*="dropdown"] svg {
|
|
1078
|
-
width: 12px !important;
|
|
1079
|
-
height: 12px !important;
|
|
1080
|
-
flex-shrink: 0 !important;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
/* Model name text inside selector */
|
|
1084
|
-
[class*="model-selector"] span, [class*="modelSelector"] span,
|
|
1085
|
-
[class*="model-name"], [class*="modelName"] {
|
|
1086
|
-
overflow: hidden !important;
|
|
1087
|
-
text-overflow: ellipsis !important;
|
|
1088
|
-
white-space: nowrap !important;
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
/* Reset positioning for captured content to prevent layout issues */
|
|
786
|
+
/* Reset positioning */
|
|
1092
787
|
#root, .app, [class*="cascade"], [class*="Cascade"] {
|
|
1093
788
|
position: relative !important;
|
|
1094
789
|
}
|
|
1095
790
|
|
|
1096
|
-
|
|
1097
|
-
[style*="position: fixed"],
|
|
1098
|
-
[style*="position:fixed"] {
|
|
791
|
+
[style*="position: fixed"], [style*="position:fixed"] {
|
|
1099
792
|
position: absolute !important;
|
|
1100
793
|
}
|
|
1101
794
|
|
|
1102
795
|
* { font-family: inherit; }
|
|
1103
796
|
code, pre, .monaco-editor { font-family: Menlo, Monaco, "Courier New", monospace !important; }
|
|
1104
|
-
|
|
797
|
+
|
|
798
|
+
/* Icon sizes */
|
|
1105
799
|
svg[viewBox="0 0 24 24"] { width: 16px !important; height: 16px !important; }
|
|
1106
800
|
svg[viewBox="0 0 16 16"] { width: 14px !important; height: 14px !important; }
|
|
1107
801
|
svg { color: inherit; max-width: 20px !important; max-height: 20px !important; }
|
|
1108
802
|
svg path, svg rect { fill: currentColor; }
|
|
1109
803
|
svg[fill="none"] path { fill: none; }
|
|
1110
|
-
/* Preserve stroke-based SVG circles (like context window progress ring) */
|
|
1111
804
|
svg circle[stroke] { fill: none !important; }
|
|
1112
805
|
svg circle:not([stroke]) { fill: currentColor; }
|
|
1113
|
-
|
|
806
|
+
|
|
807
|
+
/* Context window progress circle */
|
|
1114
808
|
svg[class*="context"], svg[class*="progress"], [class*="context"] svg {
|
|
1115
809
|
overflow: visible !important;
|
|
1116
810
|
}
|
|
@@ -1118,7 +812,6 @@
|
|
|
1118
812
|
fill: none !important;
|
|
1119
813
|
stroke-linecap: round !important;
|
|
1120
814
|
}
|
|
1121
|
-
/* Context window indicator - circular progress ring */
|
|
1122
815
|
[class*="context-window"], [class*="contextWindow"], [class*="ContextWindow"],
|
|
1123
816
|
[class*="context-indicator"], [class*="contextIndicator"], [class*="ContextIndicator"],
|
|
1124
817
|
[class*="token-usage"], [class*="tokenUsage"], [class*="TokenUsage"] {
|
|
@@ -1132,7 +825,6 @@
|
|
|
1132
825
|
position: relative !important;
|
|
1133
826
|
flex-shrink: 0 !important;
|
|
1134
827
|
}
|
|
1135
|
-
/* Create gray background circle using box-shadow on the container */
|
|
1136
828
|
[class*="context-window"]::before, [class*="contextWindow"]::before, [class*="ContextWindow"]::before,
|
|
1137
829
|
[class*="context-indicator"]::before, [class*="contextIndicator"]::before {
|
|
1138
830
|
content: '' !important;
|
|
@@ -1161,15 +853,12 @@
|
|
|
1161
853
|
stroke-linecap: round !important;
|
|
1162
854
|
stroke: #4ade80 !important;
|
|
1163
855
|
}
|
|
1164
|
-
|
|
1165
|
-
[class*="context"] svg circle,
|
|
1166
|
-
[class*="Context"] svg circle {
|
|
856
|
+
[class*="context"] svg circle, [class*="Context"] svg circle {
|
|
1167
857
|
display: block !important;
|
|
1168
858
|
visibility: visible !important;
|
|
1169
859
|
}
|
|
1170
860
|
|
|
1171
|
-
/*
|
|
1172
|
-
/* The toolbar/footer area containing model selector, context, autopilot, input */
|
|
861
|
+
/* Chat input toolbar area */
|
|
1173
862
|
[class*="chat-input"], [class*="chatInput"], [class*="ChatInput"],
|
|
1174
863
|
[class*="input-area"], [class*="inputArea"], [class*="InputArea"],
|
|
1175
864
|
[class*="message-input"], [class*="messageInput"], [class*="MessageInput"],
|
|
@@ -1183,7 +872,7 @@
|
|
|
1183
872
|
border-top: 1px solid var(--color-border-primary, #4a464f) !important;
|
|
1184
873
|
}
|
|
1185
874
|
|
|
1186
|
-
/* Input toolbar row
|
|
875
|
+
/* Input toolbar row */
|
|
1187
876
|
[class*="input-toolbar"], [class*="inputToolbar"], [class*="InputToolbar"],
|
|
1188
877
|
[class*="chat-toolbar"], [class*="chatToolbar"], [class*="ChatToolbar"],
|
|
1189
878
|
[class*="composer-toolbar"], [class*="composerToolbar"] {
|
|
@@ -1194,7 +883,7 @@
|
|
|
1194
883
|
flex-wrap: wrap !important;
|
|
1195
884
|
}
|
|
1196
885
|
|
|
1197
|
-
/* Context button (# symbol)
|
|
886
|
+
/* Context button (# symbol) */
|
|
1198
887
|
[class*="context-button"], [class*="contextButton"], [class*="ContextButton"],
|
|
1199
888
|
button[aria-label*="context"], button[aria-label*="Context"],
|
|
1200
889
|
[class*="hash-button"], [class*="hashButton"] {
|
|
@@ -1219,30 +908,15 @@
|
|
|
1219
908
|
background: rgba(255, 255, 255, 0.1) !important;
|
|
1220
909
|
}
|
|
1221
910
|
|
|
1222
|
-
/*
|
|
1223
|
-
|
|
1224
|
-
[class*="message"] p,
|
|
1225
|
-
[class*="Message"] p,
|
|
1226
|
-
[class*="chat"] p,
|
|
1227
|
-
[class*="Chat"] p {
|
|
911
|
+
/* Message text flow - inline elements */
|
|
912
|
+
[class*="message"] p, [class*="Message"] p, [class*="chat"] p, [class*="Chat"] p {
|
|
1228
913
|
display: block !important;
|
|
1229
914
|
}
|
|
1230
915
|
|
|
1231
|
-
|
|
1232
|
-
[class*="message"] a,
|
|
1233
|
-
[class*="
|
|
1234
|
-
[class*="
|
|
1235
|
-
[class*="Message"] code,
|
|
1236
|
-
[class*="message"] span,
|
|
1237
|
-
[class*="Message"] span,
|
|
1238
|
-
[class*="chat"] a,
|
|
1239
|
-
[class*="Chat"] a,
|
|
1240
|
-
[class*="chat"] code,
|
|
1241
|
-
[class*="Chat"] code,
|
|
1242
|
-
[class*="file-link"],
|
|
1243
|
-
[class*="FileLink"],
|
|
1244
|
-
[class*="inline"],
|
|
1245
|
-
code:not(pre code) {
|
|
916
|
+
[class*="message"] a, [class*="Message"] a, [class*="message"] code, [class*="Message"] code,
|
|
917
|
+
[class*="message"] span, [class*="Message"] span, [class*="chat"] a, [class*="Chat"] a,
|
|
918
|
+
[class*="chat"] code, [class*="Chat"] code, [class*="file-link"], [class*="FileLink"],
|
|
919
|
+
[class*="inline"], code:not(pre code) {
|
|
1246
920
|
display: inline !important;
|
|
1247
921
|
vertical-align: baseline !important;
|
|
1248
922
|
}
|
|
@@ -1256,17 +930,10 @@
|
|
|
1256
930
|
font-size: 12px !important;
|
|
1257
931
|
}
|
|
1258
932
|
|
|
1259
|
-
/* File badges/chips
|
|
1260
|
-
[class*="file-badge"],
|
|
1261
|
-
[class*="
|
|
1262
|
-
[class*="
|
|
1263
|
-
[class*="FileChip"],
|
|
1264
|
-
[class*="deleted"],
|
|
1265
|
-
[class*="Deleted"],
|
|
1266
|
-
[class*="created"],
|
|
1267
|
-
[class*="Created"],
|
|
1268
|
-
[class*="modified"],
|
|
1269
|
-
[class*="Modified"] {
|
|
933
|
+
/* File badges/chips */
|
|
934
|
+
[class*="file-badge"], [class*="FileBadge"], [class*="file-chip"], [class*="FileChip"],
|
|
935
|
+
[class*="deleted"], [class*="Deleted"], [class*="created"], [class*="Created"],
|
|
936
|
+
[class*="modified"], [class*="Modified"] {
|
|
1270
937
|
display: inline-flex !important;
|
|
1271
938
|
align-items: center !important;
|
|
1272
939
|
gap: 4px !important;
|
|
@@ -1278,19 +945,10 @@
|
|
|
1278
945
|
vertical-align: middle !important;
|
|
1279
946
|
}
|
|
1280
947
|
|
|
1281
|
-
/* Bullet lists
|
|
1282
|
-
ul li, ol li {
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
ul li > *, ol li > * {
|
|
1287
|
-
display: inline !important;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
ul li > p, ol li > p {
|
|
1291
|
-
display: inline !important;
|
|
1292
|
-
margin: 0 !important;
|
|
1293
|
-
}
|
|
948
|
+
/* Bullet lists */
|
|
949
|
+
ul li, ol li { display: list-item !important; }
|
|
950
|
+
ul li > *, ol li > * { display: inline !important; }
|
|
951
|
+
ul li > p, ol li > p { display: inline !important; margin: 0 !important; }
|
|
1294
952
|
|
|
1295
953
|
[role="switch"], [class*="toggle"] {
|
|
1296
954
|
overflow: visible !important;
|
|
@@ -1298,117 +956,7 @@
|
|
|
1298
956
|
flex-shrink: 0 !important;
|
|
1299
957
|
}
|
|
1300
958
|
|
|
1301
|
-
/*
|
|
1302
|
-
|
|
1303
|
-
/* Force solid background for task containers */
|
|
1304
|
-
[class*="task"], [class*="Task"],
|
|
1305
|
-
div:has(> h1:contains("CURRENT TASKS")),
|
|
1306
|
-
div:has(> h2:contains("CURRENT TASKS")),
|
|
1307
|
-
div:has(> h3:contains("CURRENT TASKS")),
|
|
1308
|
-
div:has(> h1:contains("TASKS IN QUEUE")),
|
|
1309
|
-
div:has(> h2:contains("TASKS IN QUEUE")),
|
|
1310
|
-
div:has(> h3:contains("TASKS IN QUEUE")) {
|
|
1311
|
-
font-family: "Segoe WPC", "Segoe UI", -apple-system, BlinkMacSystemFont, system-ui, Ubuntu, "Droid Sans", sans-serif !important;
|
|
1312
|
-
background: #1e1e1e !important;
|
|
1313
|
-
background-color: #1e1e1e !important;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
/* Task section headers - BOLD WHITE UPPERCASE */
|
|
1317
|
-
h1:contains("CURRENT TASKS"),
|
|
1318
|
-
h2:contains("CURRENT TASKS"),
|
|
1319
|
-
h3:contains("CURRENT TASKS"),
|
|
1320
|
-
h1:contains("TASKS IN QUEUE"),
|
|
1321
|
-
h2:contains("TASKS IN QUEUE"),
|
|
1322
|
-
h3:contains("TASKS IN QUEUE"),
|
|
1323
|
-
[class*="task"] h1, [class*="Task"] h1,
|
|
1324
|
-
[class*="task"] h2, [class*="Task"] h2,
|
|
1325
|
-
[class*="task"] h3, [class*="Task"] h3,
|
|
1326
|
-
[class*="task-header"], [class*="TaskHeader"],
|
|
1327
|
-
[class*="section-title"], [class*="SectionTitle"],
|
|
1328
|
-
div[class*="current"] h1, div[class*="Current"] h1,
|
|
1329
|
-
div[class*="current"] h2, div[class*="Current"] h2,
|
|
1330
|
-
div[class*="current"] h3, div[class*="Current"] h3,
|
|
1331
|
-
div[class*="queue"] h1, div[class*="Queue"] h1,
|
|
1332
|
-
div[class*="queue"] h2, div[class*="Queue"] h2,
|
|
1333
|
-
div[class*="queue"] h3, div[class*="Queue"] h3 {
|
|
1334
|
-
font-size: 13px !important;
|
|
1335
|
-
font-weight: 700 !important;
|
|
1336
|
-
letter-spacing: 0.5px !important;
|
|
1337
|
-
text-transform: uppercase !important;
|
|
1338
|
-
color: #ffffff !important;
|
|
1339
|
-
margin: 0 0 12px 0 !important;
|
|
1340
|
-
padding: 0 !important;
|
|
1341
|
-
line-height: 1.4 !important;
|
|
1342
|
-
background: transparent !important;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
/* Empty state messages - ITALIC GRAY */
|
|
1346
|
-
p:contains("No active tasks"),
|
|
1347
|
-
p:contains("No tasks in queue"),
|
|
1348
|
-
[class*="task"] p:not([class*="task-item"]),
|
|
1349
|
-
[class*="Task"] p:not([class*="TaskItem"]),
|
|
1350
|
-
[class*="no-task"], [class*="NoTask"],
|
|
1351
|
-
[class*="empty-task"], [class*="EmptyTask"],
|
|
1352
|
-
[class*="task-empty"], [class*="TaskEmpty"] {
|
|
1353
|
-
font-size: 13px !important;
|
|
1354
|
-
font-style: italic !important;
|
|
1355
|
-
color: #888888 !important;
|
|
1356
|
-
margin: 0 0 16px 0 !important;
|
|
1357
|
-
padding: 0 !important;
|
|
1358
|
-
line-height: 1.6 !important;
|
|
1359
|
-
background: transparent !important;
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
/* Task list containers */
|
|
1363
|
-
[class*="task-list"], [class*="TaskList"],
|
|
1364
|
-
[class*="task-section"], [class*="TaskSection"] {
|
|
1365
|
-
margin-bottom: 24px !important;
|
|
1366
|
-
padding: 0 !important;
|
|
1367
|
-
background: #1e1e1e !important;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
/* Horizontal divider between sections */
|
|
1371
|
-
[class*="task-divider"], [class*="TaskDivider"],
|
|
1372
|
-
[class*="task"] hr, [class*="Task"] hr {
|
|
1373
|
-
border: none !important;
|
|
1374
|
-
border-top: 1px solid #3c3c3c !important;
|
|
1375
|
-
margin: 20px 0 !important;
|
|
1376
|
-
background: transparent !important;
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
/* Individual task items (when they exist) */
|
|
1380
|
-
[class*="task-item"], [class*="TaskItem"] {
|
|
1381
|
-
padding: 8px 12px !important;
|
|
1382
|
-
margin: 4px 0 !important;
|
|
1383
|
-
background: rgba(255, 255, 255, 0.03) !important;
|
|
1384
|
-
border-radius: 4px !important;
|
|
1385
|
-
border-left: 2px solid #7138cc !important;
|
|
1386
|
-
font-size: 13px !important;
|
|
1387
|
-
color: #cccccc !important;
|
|
1388
|
-
line-height: 1.5 !important;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
[class*="task-item"]:hover, [class*="TaskItem"]:hover {
|
|
1392
|
-
background: rgba(255, 255, 255, 0.05) !important;
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
/* Task item with close button */
|
|
1396
|
-
[class*="task-item"] button, [class*="TaskItem"] button {
|
|
1397
|
-
margin-left: 8px !important;
|
|
1398
|
-
padding: 2px 6px !important;
|
|
1399
|
-
font-size: 11px !important;
|
|
1400
|
-
background: rgba(255, 255, 255, 0.1) !important;
|
|
1401
|
-
border: 1px solid #4a464f !important;
|
|
1402
|
-
border-radius: 3px !important;
|
|
1403
|
-
color: #888 !important;
|
|
1404
|
-
cursor: pointer !important;
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
[class*="task-item"] button:hover, [class*="TaskItem"] button:hover {
|
|
1408
|
-
background: rgba(255, 255, 255, 0.15) !important;
|
|
1409
|
-
color: #fff !important;
|
|
1410
|
-
}
|
|
1411
|
-
|
|
959
|
+
/* Toggle switch styling */
|
|
1412
960
|
.kiro-toggle-switch {
|
|
1413
961
|
display: flex !important;
|
|
1414
962
|
align-items: center !important;
|
|
@@ -1447,207 +995,586 @@
|
|
|
1447
995
|
}
|
|
1448
996
|
</style>`;
|
|
1449
997
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
998
|
+
|
|
999
|
+
function renderChatSnapshot(snapshot, isUpdate = false) {
|
|
1000
|
+
if (isRendering) return;
|
|
1001
|
+
isRendering = true;
|
|
1454
1002
|
|
|
1455
|
-
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
el.addEventListener('blur', () => { setTimeout(() => { isTypingInKiroInput = false; }, 500); });
|
|
1462
|
-
el.addEventListener('keydown', async (e) => {
|
|
1463
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1464
|
-
e.preventDefault();
|
|
1465
|
-
const text = el.textContent || el.value || '';
|
|
1466
|
-
if (text.trim()) {
|
|
1467
|
-
await sendToKiro(text.trim());
|
|
1468
|
-
el.textContent = '';
|
|
1469
|
-
el.innerHTML = '';
|
|
1470
|
-
isTypingInKiroInput = false;
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
});
|
|
1474
|
-
});
|
|
1475
|
-
}
|
|
1003
|
+
let innerScrollable = panels.chat.content.querySelector('.overflow-y-auto, [class*="scroll"]');
|
|
1004
|
+
const hadContent = innerScrollable && innerScrollable.scrollHeight > 100;
|
|
1005
|
+
const scrollTop = innerScrollable ? innerScrollable.scrollTop : 0;
|
|
1006
|
+
const scrollHeight = innerScrollable ? innerScrollable.scrollHeight : 0;
|
|
1007
|
+
const clientHeight = innerScrollable ? innerScrollable.clientHeight : 0;
|
|
1008
|
+
const wasAtBottom = !hadContent || (scrollHeight - scrollTop - clientHeight < 100);
|
|
1476
1009
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
const wrapper = toggle.closest('.kiro-toggle-switch') || toggle;
|
|
1481
|
-
|
|
1482
|
-
wrapper.style.cursor = 'pointer';
|
|
1483
|
-
wrapper.onclick = async (e) => {
|
|
1484
|
-
e.preventDefault();
|
|
1485
|
-
e.stopPropagation();
|
|
1486
|
-
|
|
1487
|
-
const label = wrapper.querySelector('label');
|
|
1488
|
-
const clickInfo = {
|
|
1489
|
-
tag: 'div',
|
|
1490
|
-
text: label ? label.textContent.trim() : 'toggle',
|
|
1491
|
-
role: 'switch',
|
|
1492
|
-
isToggle: true,
|
|
1493
|
-
toggleId: input ? input.id : '',
|
|
1494
|
-
checked: input ? input.checked : false
|
|
1495
|
-
};
|
|
1496
|
-
await sendClickToKiro(clickInfo);
|
|
1497
|
-
return false;
|
|
1498
|
-
};
|
|
1499
|
-
});
|
|
1010
|
+
let html = getBaseStyles();
|
|
1011
|
+
if (currentStyles) html += `<style>${currentStyles}</style>`;
|
|
1012
|
+
html += snapshot.html || '';
|
|
1500
1013
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1014
|
+
panels.chat.content.innerHTML = html;
|
|
1015
|
+
panels.chat.container.style.background = snapshot.bodyBg || '#1e1e1e';
|
|
1016
|
+
hideLoading('chat');
|
|
1503
1017
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
btn.onclick = async (e) => {
|
|
1514
|
-
e.preventDefault();
|
|
1515
|
-
e.stopPropagation();
|
|
1516
|
-
|
|
1517
|
-
// Find the input field and get its text
|
|
1518
|
-
const inputSelectors = ['.tiptap', '.ProseMirror', '[data-lexical-editor="true"]', '[contenteditable="true"]', 'textarea'];
|
|
1519
|
-
let inputText = '';
|
|
1520
|
-
let inputEl = null;
|
|
1521
|
-
|
|
1522
|
-
for (const inputSel of inputSelectors) {
|
|
1523
|
-
const input = content.querySelector(inputSel);
|
|
1524
|
-
if (input) {
|
|
1525
|
-
inputEl = input;
|
|
1526
|
-
inputText = input.textContent || input.innerText || input.value || '';
|
|
1527
|
-
if (inputText.trim()) break;
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1018
|
+
removePlaceholderText();
|
|
1019
|
+
fixContextWindowCircles();
|
|
1020
|
+
|
|
1021
|
+
requestAnimationFrame(() => {
|
|
1022
|
+
requestAnimationFrame(() => {
|
|
1023
|
+
// Only detect history panel by class names - NOT by date patterns
|
|
1024
|
+
// Date patterns are too aggressive and match regular chat timestamps
|
|
1025
|
+
const hasHistoryClass = panels.chat.content.querySelector('[class*="history"], [class*="History"], [class*="session-list"], [class*="SessionList"], [class*="conversation-list"], [class*="ConversationList"]');
|
|
1530
1026
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
} else if (
|
|
1539
|
-
|
|
1027
|
+
panels.chat.content.querySelectorAll('.overflow-y-auto, [class*="scroll"]').forEach(el => {
|
|
1028
|
+
const isHistoryContainer = el.matches('[class*="history"], [class*="History"], [class*="session-list"], [class*="SessionList"]') ||
|
|
1029
|
+
el.closest('[class*="history"], [class*="History"], [class*="session-list"], [class*="SessionList"]');
|
|
1030
|
+
|
|
1031
|
+
if (el.scrollHeight > el.clientHeight + 10) {
|
|
1032
|
+
if (isHistoryContainer) {
|
|
1033
|
+
el.scrollTop = 0;
|
|
1034
|
+
} else if (!isUpdate || wasAtBottom) {
|
|
1035
|
+
el.scrollTop = el.scrollHeight;
|
|
1036
|
+
} else if (hadContent) {
|
|
1037
|
+
el.scrollTop = scrollTop;
|
|
1540
1038
|
}
|
|
1541
1039
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
// Only scroll main content to top if it's clearly a history panel
|
|
1043
|
+
if (hasHistoryClass) {
|
|
1044
|
+
panels.chat.content.scrollTop = 0;
|
|
1045
|
+
} else if (!isUpdate || wasAtBottom) {
|
|
1046
|
+
panels.chat.content.scrollTop = panels.chat.content.scrollHeight;
|
|
1545
1047
|
}
|
|
1546
|
-
|
|
1547
|
-
};
|
|
1548
|
-
};
|
|
1549
|
-
|
|
1550
|
-
// PRIMARY METHOD: Find the submit button by data-variant="submit" (CONFIRMED via Playwriter)
|
|
1551
|
-
const submitBtn = content.querySelector('button[data-variant="submit"]');
|
|
1552
|
-
if (submitBtn) {
|
|
1553
|
-
attachSendHandler(submitBtn);
|
|
1554
|
-
}
|
|
1048
|
+
isRendering = false;
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
1555
1051
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1052
|
+
makeInteractive();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function removePlaceholderText() {
|
|
1056
|
+
const content = panels.chat.content;
|
|
1057
|
+
content.querySelectorAll('[class*="placeholder"], [class*="Placeholder"]').forEach(el => {
|
|
1058
|
+
if (!el.matches('[contenteditable], textarea, input, [data-lexical-editor]') &&
|
|
1059
|
+
!el.querySelector('[contenteditable], textarea, input, [data-lexical-editor]')) {
|
|
1060
|
+
el.style.display = 'none';
|
|
1562
1061
|
}
|
|
1563
1062
|
});
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
const
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function fixContextWindowCircles() {
|
|
1066
|
+
panels.chat.content.querySelectorAll('svg').forEach(svg => {
|
|
1067
|
+
const circles = svg.querySelectorAll('circle');
|
|
1068
|
+
if (circles.length >= 1) {
|
|
1069
|
+
let progressCircle = null;
|
|
1070
|
+
circles.forEach(circle => {
|
|
1071
|
+
const dashArray = circle.getAttribute('stroke-dasharray') || circle.style.strokeDasharray;
|
|
1072
|
+
if (dashArray && dashArray !== 'none') progressCircle = circle;
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
if (progressCircle && !svg.querySelector('circle[data-track="true"]')) {
|
|
1076
|
+
const cx = progressCircle.getAttribute('cx') || '8';
|
|
1077
|
+
const cy = progressCircle.getAttribute('cy') || '8';
|
|
1078
|
+
const r = progressCircle.getAttribute('r') || '6';
|
|
1079
|
+
const strokeWidth = progressCircle.getAttribute('stroke-width') || '2';
|
|
1080
|
+
|
|
1081
|
+
const trackCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
1082
|
+
trackCircle.setAttribute('cx', cx);
|
|
1083
|
+
trackCircle.setAttribute('cy', cy);
|
|
1084
|
+
trackCircle.setAttribute('r', r);
|
|
1085
|
+
trackCircle.setAttribute('stroke', '#3c3c3c');
|
|
1086
|
+
trackCircle.setAttribute('stroke-width', strokeWidth);
|
|
1087
|
+
trackCircle.setAttribute('fill', 'none');
|
|
1088
|
+
trackCircle.setAttribute('data-track', 'true');
|
|
1089
|
+
|
|
1090
|
+
svg.insertBefore(trackCircle, svg.firstChild);
|
|
1091
|
+
progressCircle.setAttribute('stroke', '#4ade80');
|
|
1573
1092
|
}
|
|
1574
1093
|
}
|
|
1575
1094
|
});
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// =============================================================================
|
|
1098
|
+
// Editor Rendering
|
|
1099
|
+
// =============================================================================
|
|
1100
|
+
function renderEditorSnapshot(data, isUpdate = false) {
|
|
1101
|
+
const scrollTop = panels.editor.content.scrollTop;
|
|
1102
|
+
panels.editor.filename.textContent = data.fileName || 'Untitled';
|
|
1103
|
+
panels.editor.header.style.display = 'flex';
|
|
1104
|
+
closeEditorSearch();
|
|
1576
1105
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1106
|
+
if (data.content?.trim().length > 0) {
|
|
1107
|
+
const lines = data.content.split('\n');
|
|
1108
|
+
let html = '';
|
|
1109
|
+
|
|
1110
|
+
if (data.isPartial || data.note) {
|
|
1111
|
+
html += `<div style="padding: 8px 12px; background: #2d2d30; color: #888; font-size: 11px; border-bottom: 1px solid #3c3c3c;">
|
|
1112
|
+
<span class="codicon codicon-info" style="margin-right: 6px;"></span>${data.note || 'Showing visible lines only.'}
|
|
1113
|
+
</div>`;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
html += '<pre class="editor-code">';
|
|
1117
|
+
const startLineNum = data.startLine || 1;
|
|
1118
|
+
let startIdx = 0;
|
|
1119
|
+
while (startIdx < lines.length && lines[startIdx].trim() === '' && startIdx < 3) startIdx++;
|
|
1120
|
+
|
|
1121
|
+
lines.slice(startIdx).forEach((line, idx) => {
|
|
1122
|
+
const lineNum = startLineNum + startIdx + idx;
|
|
1123
|
+
const highlighted = highlightSyntax(line.replace(/\t/g, ' '), data.language);
|
|
1124
|
+
html += `<div class="editor-line"><span class="editor-line-num">${lineNum}</span><span class="editor-line-code">${highlighted || ' '}</span></div>`;
|
|
1125
|
+
});
|
|
1126
|
+
html += '</pre>';
|
|
1127
|
+
|
|
1128
|
+
const endLine = startLineNum + startIdx + lines.slice(startIdx).length - 1;
|
|
1129
|
+
html += `<div style="padding: 6px 12px; background: #252526; color: #666; font-size: 11px; border-top: 1px solid #3c3c3c;">
|
|
1130
|
+
Lines ${startLineNum}-${endLine}${data.lineCount ? ` of ${data.lineCount}` : ''}
|
|
1131
|
+
</div>`;
|
|
1132
|
+
|
|
1133
|
+
panels.editor.content.innerHTML = html;
|
|
1134
|
+
panels.editor.content.style.display = 'block';
|
|
1135
|
+
} else {
|
|
1136
|
+
showEmptyState('editor', 'codicon-file-code', 'No editor content available.');
|
|
1137
|
+
return;
|
|
1597
1138
|
}
|
|
1598
1139
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1140
|
+
hideLoading('editor');
|
|
1141
|
+
if (isUpdate) panels.editor.content.scrollTop = scrollTop;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function highlightSyntax(line, language) {
|
|
1145
|
+
const escaped = escapeHtml(line);
|
|
1146
|
+
if (escaped.length > 1000 || !language) return escaped;
|
|
1147
|
+
|
|
1148
|
+
const isJS = ['javascript', 'typescript', 'jsx', 'tsx', 'js', 'ts'].includes(language);
|
|
1149
|
+
const isPython = language === 'python' || language === 'py';
|
|
1150
|
+
|
|
1151
|
+
let result = '';
|
|
1152
|
+
let i = 0;
|
|
1153
|
+
|
|
1154
|
+
while (i < escaped.length) {
|
|
1155
|
+
// Comments
|
|
1156
|
+
if (isJS && escaped.slice(i, i + 2) === '//') { result += '<span class="tok-cmt">' + escaped.slice(i) + '</span>'; break; }
|
|
1157
|
+
if ((isPython) && escaped[i] === '#') { result += '<span class="tok-cmt">' + escaped.slice(i) + '</span>'; break; }
|
|
1603
1158
|
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1159
|
+
// Strings
|
|
1160
|
+
if (escaped.slice(i, i + 6) === '"') {
|
|
1161
|
+
const endIdx = escaped.indexOf('"', i + 6);
|
|
1162
|
+
if (endIdx !== -1) { result += '<span class="tok-str">' + escaped.slice(i, endIdx + 6) + '</span>'; i = endIdx + 6; continue; }
|
|
1163
|
+
}
|
|
1164
|
+
if (escaped.slice(i, i + 5) === ''') {
|
|
1165
|
+
const endIdx = escaped.indexOf(''', i + 5);
|
|
1166
|
+
if (endIdx !== -1) { result += '<span class="tok-str">' + escaped.slice(i, endIdx + 5) + '</span>'; i = endIdx + 5; continue; }
|
|
1167
|
+
}
|
|
1607
1168
|
|
|
1608
|
-
//
|
|
1609
|
-
if (
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1169
|
+
// Numbers
|
|
1170
|
+
if (/[0-9]/.test(escaped[i]) && (i === 0 || /[^a-zA-Z_]/.test(escaped[i - 1]))) {
|
|
1171
|
+
let numEnd = i;
|
|
1172
|
+
while (numEnd < escaped.length && /[0-9.]/.test(escaped[numEnd])) numEnd++;
|
|
1173
|
+
if (numEnd > i) { result += '<span class="tok-num">' + escaped.slice(i, numEnd) + '</span>'; i = numEnd; continue; }
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Keywords
|
|
1177
|
+
if (/[a-zA-Z_]/.test(escaped[i]) && (i === 0 || /[^a-zA-Z0-9_]/.test(escaped[i - 1]))) {
|
|
1178
|
+
let wordEnd = i;
|
|
1179
|
+
while (wordEnd < escaped.length && /[a-zA-Z0-9_]/.test(escaped[wordEnd])) wordEnd++;
|
|
1180
|
+
const word = escaped.slice(i, wordEnd);
|
|
1181
|
+
|
|
1182
|
+
let keywords = [];
|
|
1183
|
+
if (isJS) keywords = ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'import', 'export', 'from', 'async', 'await', 'try', 'catch', 'throw', 'new', 'this', 'class', 'extends', 'true', 'false', 'null', 'undefined'];
|
|
1184
|
+
else if (isPython) keywords = ['def', 'class', 'return', 'if', 'elif', 'else', 'for', 'while', 'import', 'from', 'as', 'try', 'except', 'raise', 'with', 'async', 'await', 'None', 'True', 'False', 'self'];
|
|
1185
|
+
|
|
1186
|
+
if (keywords.includes(word)) result += '<span class="tok-kw">' + word + '</span>';
|
|
1187
|
+
else result += word;
|
|
1188
|
+
i = wordEnd;
|
|
1189
|
+
continue;
|
|
1615
1190
|
}
|
|
1616
1191
|
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1192
|
+
result += escaped[i];
|
|
1193
|
+
i++;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
return result;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// =============================================================================
|
|
1200
|
+
// Editor Search
|
|
1201
|
+
// =============================================================================
|
|
1202
|
+
function openEditorSearch() {
|
|
1203
|
+
$('editorSearchBar').classList.add('open');
|
|
1204
|
+
$('editorSearchInput').focus();
|
|
1205
|
+
$('editorSearchInput').select();
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function closeEditorSearch() {
|
|
1209
|
+
$('editorSearchBar').classList.remove('open');
|
|
1210
|
+
$('editorSearchInput').value = '';
|
|
1211
|
+
searchMatches = [];
|
|
1212
|
+
currentMatchIndex = -1;
|
|
1213
|
+
$('editorSearchCount').textContent = '';
|
|
1214
|
+
panels.editor.content.querySelectorAll('.editor-line-code[data-original-html]').forEach(el => {
|
|
1215
|
+
el.innerHTML = el.dataset.originalHtml;
|
|
1216
|
+
delete el.dataset.originalHtml;
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function performSearch(query) {
|
|
1221
|
+
panels.editor.content.querySelectorAll('.editor-line-code[data-original-html]').forEach(el => {
|
|
1222
|
+
el.innerHTML = el.dataset.originalHtml;
|
|
1223
|
+
delete el.dataset.originalHtml;
|
|
1224
|
+
});
|
|
1225
|
+
searchMatches = [];
|
|
1226
|
+
currentMatchIndex = -1;
|
|
1227
|
+
|
|
1228
|
+
if (!query || query.length < 1) { $('editorSearchCount').textContent = ''; return; }
|
|
1229
|
+
|
|
1230
|
+
const queryLower = query.toLowerCase();
|
|
1231
|
+
panels.editor.content.querySelectorAll('.editor-line-code').forEach((lineEl, lineIndex) => {
|
|
1232
|
+
const text = lineEl.textContent;
|
|
1233
|
+
const textLower = text.toLowerCase();
|
|
1234
|
+
let startIndex = 0, matchIndex;
|
|
1620
1235
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1236
|
+
while ((matchIndex = textLower.indexOf(queryLower, startIndex)) !== -1) {
|
|
1237
|
+
searchMatches.push({ lineIndex, lineEl, startIndex: matchIndex, endIndex: matchIndex + query.length });
|
|
1238
|
+
startIndex = matchIndex + 1;
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
if (searchMatches.length > 0) {
|
|
1243
|
+
highlightSearchMatches(query);
|
|
1244
|
+
currentMatchIndex = 0;
|
|
1245
|
+
scrollToSearchMatch(0);
|
|
1246
|
+
updateSearchCount();
|
|
1247
|
+
} else {
|
|
1248
|
+
$('editorSearchCount').textContent = 'No results';
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function highlightSearchMatches(query) {
|
|
1253
|
+
const queryLower = query.toLowerCase();
|
|
1254
|
+
panels.editor.content.querySelectorAll('.editor-line-code').forEach(lineEl => {
|
|
1255
|
+
const text = lineEl.textContent;
|
|
1256
|
+
if (text.toLowerCase().includes(queryLower)) {
|
|
1257
|
+
if (!lineEl.dataset.originalHtml) lineEl.dataset.originalHtml = lineEl.innerHTML;
|
|
1258
|
+
|
|
1259
|
+
const matches = [];
|
|
1260
|
+
let idx = 0;
|
|
1261
|
+
while ((idx = text.toLowerCase().indexOf(queryLower, idx)) !== -1) {
|
|
1262
|
+
matches.push({ start: idx, end: idx + query.length });
|
|
1263
|
+
idx++;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
if (matches.length > 0) {
|
|
1267
|
+
let result = '', lastEnd = 0;
|
|
1268
|
+
matches.forEach(match => {
|
|
1269
|
+
result += escapeHtml(text.slice(lastEnd, match.start));
|
|
1270
|
+
result += `<span class="search-highlight">${escapeHtml(text.slice(match.start, match.end))}</span>`;
|
|
1271
|
+
lastEnd = match.end;
|
|
1272
|
+
});
|
|
1273
|
+
result += escapeHtml(text.slice(lastEnd));
|
|
1274
|
+
lineEl.innerHTML = result;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function scrollToSearchMatch(index) {
|
|
1281
|
+
const highlights = panels.editor.content.querySelectorAll('.search-highlight');
|
|
1282
|
+
highlights.forEach(el => el.classList.remove('current'));
|
|
1283
|
+
|
|
1284
|
+
if (highlights[index]) {
|
|
1285
|
+
highlights[index].classList.add('current');
|
|
1286
|
+
const lineEl = highlights[index].closest('.editor-line');
|
|
1287
|
+
if (lineEl) {
|
|
1288
|
+
const container = panels.editor.content;
|
|
1289
|
+
const containerRect = container.getBoundingClientRect();
|
|
1290
|
+
const lineRect = lineEl.getBoundingClientRect();
|
|
1291
|
+
const scrollTop = container.scrollTop + (lineRect.top - containerRect.top) - (containerRect.height / 2) + (lineRect.height / 2);
|
|
1292
|
+
container.scrollTo({ top: Math.max(0, scrollTop), behavior: 'smooth' });
|
|
1624
1293
|
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function updateSearchCount() {
|
|
1298
|
+
$('editorSearchCount').textContent = searchMatches.length > 0 ? `${currentMatchIndex + 1}/${searchMatches.length}` : 'No results';
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function goToNextMatch() {
|
|
1302
|
+
if (searchMatches.length === 0) return;
|
|
1303
|
+
currentMatchIndex = (currentMatchIndex + 1) % searchMatches.length;
|
|
1304
|
+
scrollToSearchMatch(currentMatchIndex);
|
|
1305
|
+
updateSearchCount();
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function goToPrevMatch() {
|
|
1309
|
+
if (searchMatches.length === 0) return;
|
|
1310
|
+
currentMatchIndex = (currentMatchIndex - 1 + searchMatches.length) % searchMatches.length;
|
|
1311
|
+
scrollToSearchMatch(currentMatchIndex);
|
|
1312
|
+
updateSearchCount();
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// Setup search
|
|
1316
|
+
$('editorSearchBtn').onclick = openEditorSearch;
|
|
1317
|
+
$('editorSearchClose').onclick = closeEditorSearch;
|
|
1318
|
+
$('editorSearchNext').onclick = goToNextMatch;
|
|
1319
|
+
$('editorSearchPrev').onclick = goToPrevMatch;
|
|
1320
|
+
$('editorSearchInput').oninput = (e) => performSearch(e.target.value);
|
|
1321
|
+
$('editorSearchInput').onkeydown = (e) => {
|
|
1322
|
+
if (e.key === 'Enter') { e.preventDefault(); e.shiftKey ? goToPrevMatch() : goToNextMatch(); }
|
|
1323
|
+
else if (e.key === 'Escape') closeEditorSearch();
|
|
1324
|
+
};
|
|
1325
|
+
document.addEventListener('keydown', (e) => {
|
|
1326
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'f' && activePanel === 'editor') { e.preventDefault(); openEditorSearch(); }
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
// =============================================================================
|
|
1330
|
+
// File Tree
|
|
1331
|
+
// =============================================================================
|
|
1332
|
+
function toggleFileTree() {
|
|
1333
|
+
fileTreeOpen = !fileTreeOpen;
|
|
1334
|
+
fileTreeDropdown.classList.toggle('open', fileTreeOpen);
|
|
1335
|
+
explorerChevron.style.transform = fileTreeOpen ? 'rotate(180deg)' : '';
|
|
1336
|
+
if (fileTreeOpen && workspaceFiles.length === 0) fetchWorkspaceFiles();
|
|
1337
|
+
else if (fileTreeOpen) renderFileTree(workspaceFiles);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function closeFileTree() {
|
|
1341
|
+
fileTreeOpen = false;
|
|
1342
|
+
fileTreeDropdown.classList.remove('open');
|
|
1343
|
+
explorerChevron.style.transform = '';
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
async function fetchWorkspaceFiles() {
|
|
1347
|
+
if (!selectedCascadeId) return;
|
|
1348
|
+
fileTree.innerHTML = '<div class="file-tree-loading"><div class="loading-spinner"></div>Loading files...</div>';
|
|
1349
|
+
|
|
1350
|
+
try {
|
|
1351
|
+
const r = await fetch(`/files/${selectedCascadeId}`);
|
|
1352
|
+
if (!r.ok) throw new Error('Failed to fetch files');
|
|
1353
|
+
const data = await r.json();
|
|
1354
|
+
workspaceFiles = data.files || [];
|
|
1355
|
+
renderFileTree(workspaceFiles);
|
|
1356
|
+
} catch (e) {
|
|
1357
|
+
fileTree.innerHTML = '<div class="file-tree-empty">Failed to load files</div>';
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function buildFileTree(files) {
|
|
1362
|
+
const root = { folders: {}, files: [] };
|
|
1363
|
+
files.forEach(file => {
|
|
1364
|
+
const parts = file.path.split(/[/\\]/);
|
|
1365
|
+
let current = root;
|
|
1366
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1367
|
+
const folderName = parts[i];
|
|
1368
|
+
if (!current.folders[folderName]) current.folders[folderName] = { folders: {}, files: [] };
|
|
1369
|
+
current = current.folders[folderName];
|
|
1370
|
+
}
|
|
1371
|
+
current.files.push({ name: parts[parts.length - 1], path: file.path });
|
|
1372
|
+
});
|
|
1373
|
+
return root;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function getFileIcon(name) {
|
|
1377
|
+
const ext = name.split('.').pop()?.toLowerCase();
|
|
1378
|
+
const iconMap = {
|
|
1379
|
+
'js': 'codicon-file-code', 'jsx': 'codicon-file-code', 'ts': 'codicon-file-code', 'tsx': 'codicon-file-code',
|
|
1380
|
+
'html': 'codicon-file-code', 'css': 'codicon-file-code', 'json': 'codicon-json', 'md': 'codicon-markdown',
|
|
1381
|
+
'py': 'codicon-file-code', 'java': 'codicon-file-code', 'go': 'codicon-file-code', 'rs': 'codicon-file-code',
|
|
1382
|
+
'sql': 'codicon-database', 'txt': 'codicon-file'
|
|
1383
|
+
};
|
|
1384
|
+
return iconMap[ext] || 'codicon-file';
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
function renderTreeNode(node, path = '') {
|
|
1388
|
+
let html = '';
|
|
1389
|
+
const folderNames = Object.keys(node.folders).sort();
|
|
1390
|
+
const sortedFiles = [...node.files].sort((a, b) => a.name.localeCompare(b.name));
|
|
1391
|
+
|
|
1392
|
+
folderNames.forEach(folderName => {
|
|
1393
|
+
const folderPath = path ? `${path}/${folderName}` : folderName;
|
|
1394
|
+
const isExpanded = expandedFolders.has(folderPath);
|
|
1395
|
+
html += `<div class="file-tree-folder" data-path="${escapeHtml(folderPath)}">
|
|
1396
|
+
<div class="file-tree-folder-header" data-folder="${escapeHtml(folderPath)}">
|
|
1397
|
+
<span class="file-tree-folder-icon codicon codicon-chevron-right ${isExpanded ? 'expanded' : ''}"></span>
|
|
1398
|
+
<span class="codicon codicon-folder${isExpanded ? '-opened' : ''}" style="color: #dcb67a;"></span>
|
|
1399
|
+
<span class="file-tree-folder-name">${escapeHtml(folderName)}</span>
|
|
1400
|
+
</div>
|
|
1401
|
+
<div class="file-tree-folder-contents ${isExpanded ? 'expanded' : ''}">${renderTreeNode(node.folders[folderName], folderPath)}</div>
|
|
1402
|
+
</div>`;
|
|
1403
|
+
});
|
|
1404
|
+
|
|
1405
|
+
sortedFiles.forEach(file => {
|
|
1406
|
+
html += `<div class="file-tree-file" data-path="${escapeHtml(file.path)}">
|
|
1407
|
+
<span class="file-tree-file-icon codicon ${getFileIcon(file.name)}"></span>
|
|
1408
|
+
<span class="file-tree-file-name">${escapeHtml(file.name)}</span>
|
|
1409
|
+
</div>`;
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
return html;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function renderFileTree(files) {
|
|
1416
|
+
if (files.length === 0) { fileTree.innerHTML = '<div class="file-tree-empty">No files found</div>'; return; }
|
|
1417
|
+
|
|
1418
|
+
const tree = buildFileTree(files);
|
|
1419
|
+
fileTree.innerHTML = renderTreeNode(tree);
|
|
1420
|
+
|
|
1421
|
+
fileTree.querySelectorAll('.file-tree-folder-header').forEach(header => {
|
|
1422
|
+
header.onclick = (e) => {
|
|
1423
|
+
e.stopPropagation();
|
|
1424
|
+
const folderPath = header.dataset.folder;
|
|
1425
|
+
const folder = header.closest('.file-tree-folder');
|
|
1426
|
+
const contents = folder.querySelector('.file-tree-folder-contents');
|
|
1427
|
+
const chevron = header.querySelector('.file-tree-folder-icon');
|
|
1428
|
+
const folderIcon = header.querySelector('.codicon-folder, .codicon-folder-opened');
|
|
1429
|
+
|
|
1430
|
+
if (expandedFolders.has(folderPath)) {
|
|
1431
|
+
expandedFolders.delete(folderPath);
|
|
1432
|
+
contents.classList.remove('expanded');
|
|
1433
|
+
chevron.classList.remove('expanded');
|
|
1434
|
+
folderIcon.classList.remove('codicon-folder-opened');
|
|
1435
|
+
folderIcon.classList.add('codicon-folder');
|
|
1436
|
+
} else {
|
|
1437
|
+
expandedFolders.add(folderPath);
|
|
1438
|
+
contents.classList.add('expanded');
|
|
1439
|
+
chevron.classList.add('expanded');
|
|
1440
|
+
folderIcon.classList.remove('codicon-folder');
|
|
1441
|
+
folderIcon.classList.add('codicon-folder-opened');
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
fileTree.querySelectorAll('.file-tree-file').forEach(item => {
|
|
1447
|
+
item.onclick = () => { closeFileTree(); openFileInEditor(item.dataset.path); };
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// =============================================================================
|
|
1452
|
+
// Chat Interactivity
|
|
1453
|
+
// =============================================================================
|
|
1454
|
+
function makeInteractive() {
|
|
1455
|
+
const content = panels.chat.content;
|
|
1456
|
+
|
|
1457
|
+
// Input fields
|
|
1458
|
+
const inputSelectors = ['[data-lexical-editor="true"]', '[contenteditable="true"]', 'textarea', '.ProseMirror', '.tiptap'];
|
|
1459
|
+
inputSelectors.forEach(selector => {
|
|
1460
|
+
content.querySelectorAll(selector).forEach(el => {
|
|
1461
|
+
el.style.cursor = 'text';
|
|
1462
|
+
el.addEventListener('focus', () => { isTypingInKiroInput = true; });
|
|
1463
|
+
el.addEventListener('blur', () => { setTimeout(() => { isTypingInKiroInput = false; }, 500); });
|
|
1464
|
+
el.addEventListener('keydown', async (e) => {
|
|
1465
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1466
|
+
e.preventDefault();
|
|
1467
|
+
const text = el.textContent || el.value || '';
|
|
1468
|
+
if (text.trim()) {
|
|
1469
|
+
await sendToKiro(text.trim());
|
|
1470
|
+
el.textContent = ''; el.innerHTML = '';
|
|
1471
|
+
isTypingInKiroInput = false;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
});
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
// Send button
|
|
1479
|
+
const attachSendHandler = (btn) => {
|
|
1480
|
+
if (!btn || btn.dataset.sendHandlerAttached) return;
|
|
1481
|
+
btn.dataset.sendHandlerAttached = 'true';
|
|
1482
|
+
btn.removeAttribute('disabled');
|
|
1483
|
+
btn.style.cursor = 'pointer';
|
|
1484
|
+
|
|
1485
|
+
btn.onclick = async (e) => {
|
|
1486
|
+
e.preventDefault(); e.stopPropagation();
|
|
1487
|
+
let inputText = '', inputEl = null;
|
|
1488
|
+
|
|
1489
|
+
// Find the ACTUAL chat input (last visible editor, not old messages)
|
|
1490
|
+
// Look for input containers first, then fall back to last visible editor
|
|
1491
|
+
const inputContainerSelectors = [
|
|
1492
|
+
'[class*="chat-input"]',
|
|
1493
|
+
'[class*="message-input"]',
|
|
1494
|
+
'[class*="composer"]',
|
|
1495
|
+
'[class*="input-area"]',
|
|
1496
|
+
'[class*="InputArea"]',
|
|
1497
|
+
'form[class*="chat"]'
|
|
1498
|
+
];
|
|
1499
|
+
|
|
1500
|
+
// Strategy 1: Find editor inside input container
|
|
1501
|
+
for (const containerSel of inputContainerSelectors) {
|
|
1502
|
+
const container = content.querySelector(containerSel);
|
|
1503
|
+
if (container) {
|
|
1504
|
+
const editorInContainer = container.querySelector('.tiptap, .ProseMirror, [data-lexical-editor="true"], [contenteditable="true"], textarea');
|
|
1505
|
+
if (editorInContainer && editorInContainer.offsetParent !== null) {
|
|
1506
|
+
inputEl = editorInContainer;
|
|
1507
|
+
inputText = editorInContainer.textContent || editorInContainer.innerText || editorInContainer.value || '';
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// Strategy 2: Find editor near the submit button itself
|
|
1514
|
+
if (!inputEl) {
|
|
1515
|
+
const btnParent = btn.closest('form') || btn.parentElement?.parentElement?.parentElement;
|
|
1516
|
+
if (btnParent) {
|
|
1517
|
+
const nearbyEditor = btnParent.querySelector('.tiptap, .ProseMirror, [data-lexical-editor="true"], [contenteditable="true"], textarea');
|
|
1518
|
+
if (nearbyEditor && nearbyEditor.offsetParent !== null) {
|
|
1519
|
+
inputEl = nearbyEditor;
|
|
1520
|
+
inputText = nearbyEditor.textContent || nearbyEditor.innerText || nearbyEditor.value || '';
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// Strategy 3: Get the LAST visible editor (input is typically at bottom)
|
|
1526
|
+
if (!inputEl) {
|
|
1527
|
+
for (const sel of ['.tiptap', '.ProseMirror', '[data-lexical-editor="true"]', '[contenteditable="true"]', 'textarea']) {
|
|
1528
|
+
const allEditors = [...content.querySelectorAll(sel)].filter(el => el.offsetParent !== null);
|
|
1529
|
+
if (allEditors.length > 0) {
|
|
1530
|
+
inputEl = allEditors.at(-1); // Get LAST one, not first
|
|
1531
|
+
inputText = inputEl.textContent || inputEl.innerText || inputEl.value || '';
|
|
1532
|
+
if (inputText.trim()) break;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
if (inputText.trim()) {
|
|
1538
|
+
await sendToKiro(inputText.trim());
|
|
1539
|
+
if (inputEl) { inputEl.textContent = ''; inputEl.innerHTML = ''; }
|
|
1540
|
+
showToast('Sent!', 1500, true);
|
|
1541
|
+
} else {
|
|
1542
|
+
showToast('Type a message first', 1500);
|
|
1543
|
+
}
|
|
1544
|
+
return false;
|
|
1545
|
+
};
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1548
|
+
const submitBtn = content.querySelector('button[data-variant="submit"]');
|
|
1549
|
+
if (submitBtn) attachSendHandler(submitBtn);
|
|
1550
|
+
|
|
1551
|
+
content.querySelectorAll('button').forEach(btn => {
|
|
1552
|
+
if (btn.dataset.sendHandlerAttached) return;
|
|
1553
|
+
if (btn.querySelector('.codicon-arrow-up, .codicon-send')) attachSendHandler(btn);
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
// Toggle switches
|
|
1557
|
+
content.querySelectorAll('[role="switch"], input[type="checkbox"][role="switch"]').forEach(toggle => {
|
|
1558
|
+
const wrapper = toggle.closest('.kiro-toggle-switch') || toggle;
|
|
1559
|
+
wrapper.style.cursor = 'pointer';
|
|
1560
|
+
wrapper.onclick = async (e) => {
|
|
1561
|
+
e.preventDefault(); e.stopPropagation();
|
|
1562
|
+
const label = wrapper.querySelector('label');
|
|
1563
|
+
await sendClickToKiro({ tag: 'div', text: label ? label.textContent.trim() : 'toggle', role: 'switch', isToggle: true });
|
|
1564
|
+
return false;
|
|
1565
|
+
};
|
|
1625
1566
|
});
|
|
1626
1567
|
|
|
1627
1568
|
// Tabs
|
|
1628
1569
|
content.querySelectorAll('[role="tab"]').forEach(tab => {
|
|
1629
1570
|
const closeBtn = tab.querySelector('[aria-label="close"], [class*="close"]');
|
|
1630
|
-
|
|
1631
1571
|
if (closeBtn) {
|
|
1632
1572
|
closeBtn.style.cursor = 'pointer';
|
|
1633
1573
|
closeBtn.onclick = async (e) => {
|
|
1634
|
-
e.preventDefault();
|
|
1635
|
-
e.stopPropagation();
|
|
1636
|
-
|
|
1637
|
-
// FIXED: Include tab label to identify which tab's close button to click
|
|
1574
|
+
e.preventDefault(); e.stopPropagation();
|
|
1638
1575
|
const labelEl = tab.querySelector('.kiro-tabs-item-label, [class*="label"]');
|
|
1639
1576
|
const tabLabel = labelEl ? labelEl.textContent.trim() : tab.textContent.trim();
|
|
1640
|
-
|
|
1641
|
-
console.log('[Tab Close] Closing tab:', tabLabel);
|
|
1642
|
-
|
|
1643
|
-
await sendClickToKiro({
|
|
1644
|
-
tag: 'button',
|
|
1645
|
-
text: 'close',
|
|
1646
|
-
ariaLabel: 'close',
|
|
1647
|
-
role: 'button',
|
|
1648
|
-
isCloseButton: true,
|
|
1649
|
-
parentTabLabel: tabLabel // NEW: Identify which tab this close button belongs to
|
|
1650
|
-
});
|
|
1577
|
+
await sendClickToKiro({ tag: 'button', text: 'close', ariaLabel: 'close', role: 'button', isCloseButton: true, parentTabLabel: tabLabel });
|
|
1651
1578
|
return false;
|
|
1652
1579
|
};
|
|
1653
1580
|
}
|
|
@@ -1655,129 +1582,167 @@
|
|
|
1655
1582
|
tab.style.cursor = 'pointer';
|
|
1656
1583
|
tab.onclick = async (e) => {
|
|
1657
1584
|
if (e.target.closest('[aria-label="close"], [class*="close"], button')) return;
|
|
1658
|
-
e.preventDefault();
|
|
1659
|
-
e.stopPropagation();
|
|
1660
|
-
|
|
1585
|
+
e.preventDefault(); e.stopPropagation();
|
|
1661
1586
|
const labelEl = tab.querySelector('.kiro-tabs-item-label, [class*="label"]');
|
|
1662
1587
|
const labelText = labelEl ? labelEl.textContent.trim() : tab.textContent.trim();
|
|
1663
|
-
|
|
1664
1588
|
await sendClickToKiro({ tag: 'div', text: labelText, role: 'tab', isTab: true, tabLabel: labelText });
|
|
1665
1589
|
return false;
|
|
1666
1590
|
};
|
|
1667
1591
|
});
|
|
1668
1592
|
|
|
1669
|
-
// File links
|
|
1670
|
-
const fileExtensions = /\.(ts|tsx|js|jsx|py|java|html|css|json|md|yaml|yml|xml|sql|go|rs|c|cpp|h|cs|rb|php|sh|vue|svelte
|
|
1671
|
-
const filePathPattern = /^[a-zA-Z0-9_\-./\\]+\.[a-zA-Z0-9]+$/;
|
|
1672
|
-
|
|
1673
|
-
// Look for elements that might contain file paths
|
|
1593
|
+
// File links
|
|
1594
|
+
const fileExtensions = /\.(ts|tsx|js|jsx|py|java|html|css|json|md|yaml|yml|xml|sql|go|rs|c|cpp|h|cs|rb|php|sh|vue|svelte)$/i;
|
|
1674
1595
|
content.querySelectorAll('a, code, span, [class*="file"], [class*="path"], [data-path]').forEach(el => {
|
|
1675
1596
|
if (el.onclick) return;
|
|
1676
1597
|
const text = (el.textContent || '').trim();
|
|
1677
1598
|
const dataPath = el.getAttribute('data-path') || '';
|
|
1678
1599
|
const href = el.getAttribute('href') || '';
|
|
1679
1600
|
|
|
1680
|
-
|
|
1681
|
-
const isFilePath = (
|
|
1682
|
-
fileExtensions.test(text) ||
|
|
1683
|
-
fileExtensions.test(dataPath) ||
|
|
1684
|
-
fileExtensions.test(href) ||
|
|
1685
|
-
(filePathPattern.test(text) && text.includes('/'))
|
|
1686
|
-
);
|
|
1687
|
-
|
|
1688
|
-
if (isFilePath) {
|
|
1601
|
+
if (fileExtensions.test(text) || fileExtensions.test(dataPath) || fileExtensions.test(href)) {
|
|
1689
1602
|
const filePath = dataPath || text || href;
|
|
1690
1603
|
el.style.cursor = 'pointer';
|
|
1691
|
-
el.style.textDecoration = 'underline';
|
|
1692
1604
|
el.style.color = '#7eb6ff';
|
|
1693
1605
|
el.title = `Open ${filePath} in Editor`;
|
|
1694
|
-
|
|
1695
|
-
el.onclick = async (e) => {
|
|
1696
|
-
e.preventDefault();
|
|
1697
|
-
e.stopPropagation();
|
|
1698
|
-
await openFileInEditor(filePath);
|
|
1699
|
-
return false;
|
|
1700
|
-
};
|
|
1606
|
+
el.onclick = async (e) => { e.preventDefault(); e.stopPropagation(); await openFileInEditor(filePath); return false; };
|
|
1701
1607
|
}
|
|
1702
1608
|
});
|
|
1703
1609
|
|
|
1704
1610
|
// Buttons and clickable elements
|
|
1705
1611
|
const clickableSelectors = ['button', '[role="button"]', '[role="menuitem"]', '[role="option"]', '[role="checkbox"]', 'a[href]', '[tabindex="0"]'];
|
|
1706
1612
|
const allClickables = new Set();
|
|
1707
|
-
clickableSelectors.forEach(sel => {
|
|
1708
|
-
try { content.querySelectorAll(sel).forEach(el => allClickables.add(el)); } catch(e) {}
|
|
1709
|
-
});
|
|
1613
|
+
clickableSelectors.forEach(sel => { try { content.querySelectorAll(sel).forEach(el => allClickables.add(el)); } catch(e) {} });
|
|
1710
1614
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1615
|
+
allClickables.forEach(el => {
|
|
1616
|
+
if (el.matches && el.matches('[contenteditable], textarea, input:not([type="checkbox"])')) return;
|
|
1617
|
+
if (el.closest('[role="tab"]') || el.closest('.kiro-toggle-switch') || el.onclick) return;
|
|
1618
|
+
|
|
1619
|
+
el.style.cursor = 'pointer';
|
|
1620
|
+
el.onclick = async (e) => {
|
|
1621
|
+
e.preventDefault(); e.stopPropagation();
|
|
1622
|
+
const clickInfo = {
|
|
1623
|
+
tag: el.tagName?.toLowerCase() || '',
|
|
1624
|
+
text: (el.textContent || '').trim().substring(0, 100),
|
|
1625
|
+
ariaLabel: el.getAttribute('aria-label') || '',
|
|
1626
|
+
role: el.getAttribute('role') || '',
|
|
1627
|
+
className: el.className || ''
|
|
1628
|
+
};
|
|
1629
|
+
await sendClickToKiro(clickInfo);
|
|
1630
|
+
return false;
|
|
1631
|
+
};
|
|
1714
1632
|
});
|
|
1715
1633
|
|
|
1716
|
-
//
|
|
1717
|
-
//
|
|
1718
|
-
|
|
1719
|
-
|
|
1634
|
+
// Chat history items - detect and make clickable
|
|
1635
|
+
// Kiro history items typically have: icon, title text, and date
|
|
1636
|
+
// We need to find these items even if they don't have semantic roles
|
|
1637
|
+
|
|
1638
|
+
// First, find all potential history item containers
|
|
1639
|
+
const potentialHistoryItems = [];
|
|
1640
|
+
|
|
1641
|
+
// Look for elements that contain date patterns (history items have dates)
|
|
1642
|
+
const datePattern = /\d{1,2}\/\d{1,2}\/\d{4}|\d{1,2}:\d{2}:\d{2}\s*(AM|PM)?/i;
|
|
1643
|
+
|
|
1644
|
+
content.querySelectorAll('div, li, article, section').forEach(el => {
|
|
1645
|
+
// Skip if already has handler
|
|
1646
|
+
if (el.onclick) return;
|
|
1647
|
+
// Skip inputs
|
|
1648
|
+
if (el.matches('[contenteditable], textarea, input')) return;
|
|
1649
|
+
// Skip very large containers
|
|
1650
|
+
if (el.children.length > 15) return;
|
|
1651
|
+
// Skip if inside tab or toggle
|
|
1652
|
+
if (el.closest('[role="tab"]') || el.closest('.kiro-toggle-switch')) return;
|
|
1653
|
+
|
|
1720
1654
|
const text = el.textContent || '';
|
|
1721
|
-
const hasDate =
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
|
|
1655
|
+
const hasDate = datePattern.test(text);
|
|
1656
|
+
const hasIcon = el.querySelector('svg, [class*="icon"], [class*="Icon"], .codicon');
|
|
1657
|
+
const textLength = text.trim().length;
|
|
1658
|
+
|
|
1659
|
+
// History items typically have: date + some text (20-500 chars) + possibly an icon
|
|
1660
|
+
if (hasDate && textLength > 20 && textLength < 500) {
|
|
1661
|
+
// Check if this looks like a list item (has siblings with similar structure)
|
|
1662
|
+
const parent = el.parentElement;
|
|
1663
|
+
const siblings = parent ? [...parent.children].filter(c => c.tagName === el.tagName) : [];
|
|
1664
|
+
const isInList = siblings.length > 1;
|
|
1665
|
+
|
|
1666
|
+
// Also check for cursor pointer style or clickable class
|
|
1667
|
+
const computedStyle = window.getComputedStyle(el);
|
|
1668
|
+
const hasPointer = computedStyle.cursor === 'pointer';
|
|
1669
|
+
const hasClickableClass = el.className && (
|
|
1670
|
+
el.className.includes('item') ||
|
|
1671
|
+
el.className.includes('Item') ||
|
|
1672
|
+
el.className.includes('row') ||
|
|
1673
|
+
el.className.includes('Row') ||
|
|
1674
|
+
el.className.includes('entry') ||
|
|
1675
|
+
el.className.includes('Entry')
|
|
1676
|
+
);
|
|
1677
|
+
|
|
1678
|
+
if (isInList || hasPointer || hasClickableClass || hasIcon) {
|
|
1679
|
+
potentialHistoryItems.push(el);
|
|
1680
|
+
}
|
|
1725
1681
|
}
|
|
1726
1682
|
});
|
|
1727
1683
|
|
|
1728
|
-
// Also
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1684
|
+
// Also add standard selectors
|
|
1685
|
+
const historySelectors = [
|
|
1686
|
+
'[role="listitem"]',
|
|
1687
|
+
'[role="option"]',
|
|
1688
|
+
'[class*="history-item"]',
|
|
1689
|
+
'[class*="historyItem"]',
|
|
1690
|
+
'[class*="session-item"]',
|
|
1691
|
+
'[class*="sessionItem"]',
|
|
1692
|
+
'[class*="chat-item"]',
|
|
1693
|
+
'[class*="chatItem"]'
|
|
1694
|
+
];
|
|
1695
|
+
|
|
1696
|
+
historySelectors.forEach(selector => {
|
|
1697
|
+
try {
|
|
1698
|
+
content.querySelectorAll(selector).forEach(el => {
|
|
1699
|
+
if (!el.onclick && !potentialHistoryItems.includes(el)) {
|
|
1700
|
+
potentialHistoryItems.push(el);
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
} catch(e) {}
|
|
1734
1704
|
});
|
|
1735
1705
|
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
if (el.closest('[role="tab"]')) return;
|
|
1739
|
-
if (el.closest('.kiro-toggle-switch')) return;
|
|
1740
|
-
if (el.onclick) return;
|
|
1741
|
-
|
|
1706
|
+
// Attach click handlers to all potential history items
|
|
1707
|
+
potentialHistoryItems.forEach(el => {
|
|
1742
1708
|
el.style.cursor = 'pointer';
|
|
1743
1709
|
el.onclick = async (e) => {
|
|
1744
1710
|
e.preventDefault();
|
|
1745
1711
|
e.stopPropagation();
|
|
1746
|
-
const clickInfo = getElementClickInfo(el);
|
|
1747
1712
|
|
|
1748
|
-
//
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1713
|
+
// Extract the title (first meaningful text, not the date)
|
|
1714
|
+
let itemText = '';
|
|
1715
|
+
const textNodes = [];
|
|
1716
|
+
el.querySelectorAll('span, p, div').forEach(child => {
|
|
1717
|
+
const t = child.textContent?.trim();
|
|
1718
|
+
if (t && t.length > 5 && !datePattern.test(t)) {
|
|
1719
|
+
textNodes.push(t);
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
itemText = textNodes[0] || el.textContent?.trim().split('\n')[0] || '';
|
|
1723
|
+
itemText = itemText.substring(0, 100);
|
|
1724
|
+
|
|
1725
|
+
const clickInfo = {
|
|
1726
|
+
tag: el.tagName?.toLowerCase() || 'div',
|
|
1727
|
+
text: itemText,
|
|
1728
|
+
ariaLabel: el.getAttribute('aria-label') || '',
|
|
1729
|
+
role: el.getAttribute('role') || 'listitem',
|
|
1730
|
+
className: el.className || '',
|
|
1731
|
+
isHistoryItem: true
|
|
1732
|
+
};
|
|
1754
1733
|
|
|
1755
1734
|
await sendClickToKiro(clickInfo);
|
|
1735
|
+
|
|
1736
|
+
// Refresh snapshot after clicking history item
|
|
1737
|
+
setTimeout(() => fetchChatSnapshot(selectedCascadeId), 500);
|
|
1756
1738
|
return false;
|
|
1757
1739
|
};
|
|
1758
1740
|
});
|
|
1759
1741
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
text: (el.textContent || '').trim().substring(0, 100),
|
|
1765
|
-
ariaLabel: el.getAttribute('aria-label') || '',
|
|
1766
|
-
title: el.getAttribute('title') || '',
|
|
1767
|
-
role: el.getAttribute('role') || '',
|
|
1768
|
-
className: el.className || '',
|
|
1769
|
-
isTab: el.getAttribute('role') === 'tab',
|
|
1770
|
-
dataTestId: el.getAttribute('data-testid') || ''
|
|
1771
|
-
};
|
|
1772
|
-
|
|
1773
|
-
if (!info.ariaLabel) {
|
|
1774
|
-
const parent = el.closest('[aria-label]');
|
|
1775
|
-
if (parent) info.ariaLabel = parent.getAttribute('aria-label') || '';
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
return info;
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1742
|
+
|
|
1743
|
+
// =============================================================================
|
|
1744
|
+
// Kiro Communication
|
|
1745
|
+
// =============================================================================
|
|
1781
1746
|
async function sendToKiro(message) {
|
|
1782
1747
|
if (!message || !selectedCascadeId) return;
|
|
1783
1748
|
try {
|
|
@@ -1793,21 +1758,15 @@
|
|
|
1793
1758
|
showToast('Failed to send');
|
|
1794
1759
|
}
|
|
1795
1760
|
}
|
|
1796
|
-
|
|
1761
|
+
|
|
1797
1762
|
async function sendClickToKiro(clickInfo) {
|
|
1798
1763
|
if (!selectedCascadeId) return;
|
|
1799
1764
|
|
|
1800
|
-
const isNavigation =
|
|
1801
|
-
clickInfo.ariaLabel?.toLowerCase().includes('back') ||
|
|
1765
|
+
const isNavigation = clickInfo.ariaLabel?.toLowerCase().includes('back') ||
|
|
1802
1766
|
clickInfo.ariaLabel?.toLowerCase().includes('close') ||
|
|
1803
1767
|
clickInfo.text?.toLowerCase().includes('back') ||
|
|
1804
1768
|
clickInfo.role === 'tab';
|
|
1805
1769
|
|
|
1806
|
-
const isModelSelection = clickInfo.isModelSelection ||
|
|
1807
|
-
clickInfo.role === 'option' ||
|
|
1808
|
-
clickInfo.role === 'menuitem' ||
|
|
1809
|
-
clickInfo.className?.includes('model');
|
|
1810
|
-
|
|
1811
1770
|
if (isNavigation) navigationPending = true;
|
|
1812
1771
|
|
|
1813
1772
|
try {
|
|
@@ -1817,39 +1776,30 @@
|
|
|
1817
1776
|
body: JSON.stringify(clickInfo)
|
|
1818
1777
|
});
|
|
1819
1778
|
|
|
1820
|
-
if (isNavigation
|
|
1821
|
-
// Refresh snapshot after navigation or model selection
|
|
1822
|
-
setTimeout(() => fetchChatSnapshot(selectedCascadeId), 300);
|
|
1823
|
-
}
|
|
1779
|
+
if (isNavigation) setTimeout(() => fetchChatSnapshot(selectedCascadeId), 300);
|
|
1824
1780
|
} catch (e) {
|
|
1825
1781
|
navigationPending = false;
|
|
1826
1782
|
}
|
|
1827
1783
|
}
|
|
1828
|
-
|
|
1829
|
-
// Open file in Editor tab
|
|
1784
|
+
|
|
1830
1785
|
async function openFileInEditor(filePath) {
|
|
1831
1786
|
if (!selectedCascadeId || !filePath) return;
|
|
1832
1787
|
|
|
1833
1788
|
showToast(`Opening ${filePath}...`, 1500);
|
|
1834
1789
|
|
|
1835
|
-
// Switch to Editor tab
|
|
1790
|
+
// Switch to Editor tab
|
|
1836
1791
|
const editorTab = document.querySelector('[data-panel="editor"]');
|
|
1837
1792
|
if (editorTab) {
|
|
1838
1793
|
navTabs.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
|
|
1839
|
-
editorTab.classList.add('active');
|
|
1840
|
-
|
|
1841
|
-
Object.values(panels).forEach(p => {
|
|
1842
|
-
if (p.panel) p.panel.classList.remove('active');
|
|
1843
|
-
});
|
|
1794
|
+
editorTab.classList.add('active');
|
|
1795
|
+
Object.values(panels).forEach(p => { if (p.panel) p.panel.classList.remove('active'); });
|
|
1844
1796
|
panels.editor.panel.classList.add('active');
|
|
1845
1797
|
activePanel = 'editor';
|
|
1846
|
-
|
|
1847
|
-
// Show loading state
|
|
1848
1798
|
showLoading('editor', 'Loading file...');
|
|
1849
1799
|
}
|
|
1850
1800
|
|
|
1851
1801
|
try {
|
|
1852
|
-
//
|
|
1802
|
+
// Try direct file read first
|
|
1853
1803
|
const readResult = await fetch(`/readFile/${selectedCascadeId}`, {
|
|
1854
1804
|
method: 'POST',
|
|
1855
1805
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1861,472 +1811,61 @@
|
|
|
1861
1811
|
if (data.content) {
|
|
1862
1812
|
renderEditorSnapshot(data);
|
|
1863
1813
|
showToast(`Opened ${data.fileName || filePath}`, 1500, true);
|
|
1864
|
-
console.log('[Editor] Loaded file directly:', data.fileName, data.lineCount, 'lines');
|
|
1865
1814
|
return;
|
|
1866
1815
|
}
|
|
1867
|
-
} else {
|
|
1868
|
-
const errorData = await readResult.json().catch(() => ({}));
|
|
1869
|
-
console.log('[Editor] Direct file read failed:', errorData.error || readResult.status);
|
|
1870
1816
|
}
|
|
1871
1817
|
|
|
1872
|
-
// Fallback:
|
|
1873
|
-
|
|
1874
|
-
const clickResult = await fetch(`/click/${selectedCascadeId}`, {
|
|
1818
|
+
// Fallback: Click file link in Kiro
|
|
1819
|
+
await fetch(`/click/${selectedCascadeId}`, {
|
|
1875
1820
|
method: 'POST',
|
|
1876
1821
|
headers: { 'Content-Type': 'application/json' },
|
|
1877
|
-
body: JSON.stringify({
|
|
1878
|
-
tag: 'a',
|
|
1879
|
-
text: filePath,
|
|
1880
|
-
isFileLink: true,
|
|
1881
|
-
filePath: filePath
|
|
1882
|
-
})
|
|
1822
|
+
body: JSON.stringify({ tag: 'a', text: filePath, isFileLink: true, filePath })
|
|
1883
1823
|
});
|
|
1884
1824
|
|
|
1885
|
-
const result = await clickResult.json();
|
|
1886
|
-
console.log('Click result:', result);
|
|
1887
|
-
|
|
1888
|
-
// Wait for the file to open in Kiro
|
|
1889
1825
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
1890
1826
|
|
|
1891
|
-
// Fetch the editor content from Monaco
|
|
1892
1827
|
const fetchEditor = async () => {
|
|
1893
1828
|
try {
|
|
1894
1829
|
const r = await fetch(`/editor/${selectedCascadeId}`);
|
|
1895
1830
|
if (r.ok) {
|
|
1896
1831
|
const data = await r.json();
|
|
1897
|
-
if (data.content || data.html) {
|
|
1898
|
-
renderEditorSnapshot(data);
|
|
1899
|
-
showToast(`Opened ${data.fileName || filePath}`, 1500, true);
|
|
1900
|
-
return true;
|
|
1901
|
-
}
|
|
1832
|
+
if (data.content || data.html) { renderEditorSnapshot(data); showToast(`Opened ${data.fileName || filePath}`, 1500, true); return true; }
|
|
1902
1833
|
}
|
|
1903
1834
|
} catch (e) {}
|
|
1904
1835
|
return false;
|
|
1905
1836
|
};
|
|
1906
1837
|
|
|
1907
|
-
// Try fetching a few times with delays
|
|
1908
1838
|
if (!await fetchEditor()) {
|
|
1909
1839
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1910
1840
|
if (!await fetchEditor()) {
|
|
1911
|
-
|
|
1912
|
-
if (!await fetchEditor()) {
|
|
1913
|
-
showEmptyState('editor', 'codicon-file-code', `Could not load ${filePath}. File may not exist or path is incorrect.`);
|
|
1914
|
-
}
|
|
1841
|
+
showEmptyState('editor', 'codicon-file-code', `Could not load ${filePath}.`);
|
|
1915
1842
|
}
|
|
1916
1843
|
}
|
|
1917
|
-
|
|
1918
1844
|
} catch (e) {
|
|
1919
1845
|
showToast('Failed to open file');
|
|
1920
|
-
|
|
1921
|
-
showEmptyState('editor', 'codicon-file-code', 'Failed to load file. Check console for details.');
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
// ==================== File Tree ====================
|
|
1926
|
-
|
|
1927
|
-
// Toggle file tree panel
|
|
1928
|
-
function toggleFileTree() {
|
|
1929
|
-
fileTreeOpen = !fileTreeOpen;
|
|
1930
|
-
fileTreePanel.classList.toggle('open', fileTreeOpen);
|
|
1931
|
-
|
|
1932
|
-
if (fileTreeOpen) {
|
|
1933
|
-
// Load files if not cached
|
|
1934
|
-
if (workspaceFiles.length === 0) {
|
|
1935
|
-
fetchWorkspaceFiles();
|
|
1936
|
-
} else {
|
|
1937
|
-
renderFileTree(workspaceFiles);
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
// Close file tree
|
|
1943
|
-
function closeFileTree() {
|
|
1944
|
-
fileTreeOpen = false;
|
|
1945
|
-
fileTreePanel.classList.remove('open');
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
// Fetch workspace files from server
|
|
1949
|
-
async function fetchWorkspaceFiles() {
|
|
1950
|
-
if (!selectedCascadeId) return;
|
|
1951
|
-
|
|
1952
|
-
fileTree.innerHTML = '<div class="file-tree-loading"><div class="loading-spinner"></div>Loading files...</div>';
|
|
1953
|
-
|
|
1954
|
-
try {
|
|
1955
|
-
const r = await fetch(`/files/${selectedCascadeId}`);
|
|
1956
|
-
if (!r.ok) throw new Error('Failed to fetch files');
|
|
1957
|
-
|
|
1958
|
-
const data = await r.json();
|
|
1959
|
-
workspaceFiles = data.files || [];
|
|
1960
|
-
console.log(`[FileTree] Loaded ${workspaceFiles.length} files`);
|
|
1961
|
-
renderFileTree(workspaceFiles);
|
|
1962
|
-
} catch (e) {
|
|
1963
|
-
console.error('Failed to fetch files:', e);
|
|
1964
|
-
fileTree.innerHTML = '<div class="file-tree-empty">Failed to load files</div>';
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
|
|
1968
|
-
// Build tree structure from flat file list
|
|
1969
|
-
function buildFileTree(files) {
|
|
1970
|
-
const root = { folders: {}, files: [] };
|
|
1971
|
-
|
|
1972
|
-
files.forEach(file => {
|
|
1973
|
-
const parts = file.path.split(/[/\\]/);
|
|
1974
|
-
let current = root;
|
|
1975
|
-
|
|
1976
|
-
// Navigate/create folder structure
|
|
1977
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1978
|
-
const folderName = parts[i];
|
|
1979
|
-
if (!current.folders[folderName]) {
|
|
1980
|
-
current.folders[folderName] = { folders: {}, files: [] };
|
|
1981
|
-
}
|
|
1982
|
-
current = current.folders[folderName];
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
// Add file to current folder
|
|
1986
|
-
current.files.push({ name: parts[parts.length - 1], path: file.path });
|
|
1987
|
-
});
|
|
1988
|
-
|
|
1989
|
-
return root;
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
|
-
// Get file icon based on extension
|
|
1993
|
-
function getFileIcon(name) {
|
|
1994
|
-
const ext = name.split('.').pop()?.toLowerCase();
|
|
1995
|
-
const iconMap = {
|
|
1996
|
-
'js': 'codicon-file-code', 'jsx': 'codicon-file-code',
|
|
1997
|
-
'ts': 'codicon-file-code', 'tsx': 'codicon-file-code',
|
|
1998
|
-
'html': 'codicon-file-code', 'css': 'codicon-file-code',
|
|
1999
|
-
'json': 'codicon-json', 'md': 'codicon-markdown',
|
|
2000
|
-
'py': 'codicon-file-code', 'java': 'codicon-file-code',
|
|
2001
|
-
'go': 'codicon-file-code', 'rs': 'codicon-file-code',
|
|
2002
|
-
'cob': 'codicon-file-code', 'cbl': 'codicon-file-code',
|
|
2003
|
-
'sql': 'codicon-database', 'txt': 'codicon-file'
|
|
2004
|
-
};
|
|
2005
|
-
return iconMap[ext] || 'codicon-file';
|
|
2006
|
-
}
|
|
2007
|
-
|
|
2008
|
-
// Render tree node recursively
|
|
2009
|
-
function renderTreeNode(node, path = '', depth = 0) {
|
|
2010
|
-
let html = '';
|
|
2011
|
-
|
|
2012
|
-
// Sort folders first, then files
|
|
2013
|
-
const folderNames = Object.keys(node.folders).sort();
|
|
2014
|
-
const sortedFiles = [...node.files].sort((a, b) => a.name.localeCompare(b.name));
|
|
2015
|
-
|
|
2016
|
-
// Render folders
|
|
2017
|
-
folderNames.forEach(folderName => {
|
|
2018
|
-
const folderPath = path ? `${path}/${folderName}` : folderName;
|
|
2019
|
-
const isExpanded = expandedFolders.has(folderPath);
|
|
2020
|
-
|
|
2021
|
-
html += `
|
|
2022
|
-
<div class="file-tree-folder" data-path="${escapeHtml(folderPath)}">
|
|
2023
|
-
<div class="file-tree-folder-header" data-folder="${escapeHtml(folderPath)}">
|
|
2024
|
-
<span class="file-tree-folder-icon codicon codicon-chevron-right ${isExpanded ? 'expanded' : ''}"></span>
|
|
2025
|
-
<span class="codicon codicon-folder${isExpanded ? '-opened' : ''}" style="color: #dcb67a;"></span>
|
|
2026
|
-
<span class="file-tree-folder-name">${escapeHtml(folderName)}</span>
|
|
2027
|
-
</div>
|
|
2028
|
-
<div class="file-tree-folder-contents ${isExpanded ? 'expanded' : ''}">
|
|
2029
|
-
${renderTreeNode(node.folders[folderName], folderPath, depth + 1)}
|
|
2030
|
-
</div>
|
|
2031
|
-
</div>
|
|
2032
|
-
`;
|
|
2033
|
-
});
|
|
2034
|
-
|
|
2035
|
-
// Render files
|
|
2036
|
-
sortedFiles.forEach(file => {
|
|
2037
|
-
html += `
|
|
2038
|
-
<div class="file-tree-file" data-path="${escapeHtml(file.path)}">
|
|
2039
|
-
<span class="file-tree-file-icon codicon ${getFileIcon(file.name)}"></span>
|
|
2040
|
-
<span class="file-tree-file-name">${escapeHtml(file.name)}</span>
|
|
2041
|
-
</div>
|
|
2042
|
-
`;
|
|
2043
|
-
});
|
|
2044
|
-
|
|
2045
|
-
return html;
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
// Render file tree
|
|
2049
|
-
function renderFileTree(files) {
|
|
2050
|
-
if (files.length === 0) {
|
|
2051
|
-
fileTree.innerHTML = '<div class="file-tree-empty">No files found</div>';
|
|
2052
|
-
return;
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
// Build and render tree structure
|
|
2056
|
-
const tree = buildFileTree(files);
|
|
2057
|
-
fileTree.innerHTML = renderTreeNode(tree);
|
|
2058
|
-
|
|
2059
|
-
// Add click handlers for folders
|
|
2060
|
-
fileTree.querySelectorAll('.file-tree-folder-header').forEach(header => {
|
|
2061
|
-
header.onclick = (e) => {
|
|
2062
|
-
e.stopPropagation();
|
|
2063
|
-
const folderPath = header.dataset.folder;
|
|
2064
|
-
const folder = header.closest('.file-tree-folder');
|
|
2065
|
-
const contents = folder.querySelector('.file-tree-folder-contents');
|
|
2066
|
-
const chevron = header.querySelector('.file-tree-folder-icon');
|
|
2067
|
-
const folderIcon = header.querySelector('.codicon-folder, .codicon-folder-opened');
|
|
2068
|
-
|
|
2069
|
-
if (expandedFolders.has(folderPath)) {
|
|
2070
|
-
expandedFolders.delete(folderPath);
|
|
2071
|
-
contents.classList.remove('expanded');
|
|
2072
|
-
chevron.classList.remove('expanded');
|
|
2073
|
-
folderIcon.classList.remove('codicon-folder-opened');
|
|
2074
|
-
folderIcon.classList.add('codicon-folder');
|
|
2075
|
-
} else {
|
|
2076
|
-
expandedFolders.add(folderPath);
|
|
2077
|
-
contents.classList.add('expanded');
|
|
2078
|
-
chevron.classList.add('expanded');
|
|
2079
|
-
folderIcon.classList.remove('codicon-folder');
|
|
2080
|
-
folderIcon.classList.add('codicon-folder-opened');
|
|
2081
|
-
}
|
|
2082
|
-
};
|
|
2083
|
-
});
|
|
2084
|
-
|
|
2085
|
-
// Add click handlers for files
|
|
2086
|
-
fileTree.querySelectorAll('.file-tree-file').forEach(item => {
|
|
2087
|
-
item.onclick = () => {
|
|
2088
|
-
const path = item.dataset.path;
|
|
2089
|
-
closeFileTree();
|
|
2090
|
-
openFileInEditor(path);
|
|
2091
|
-
};
|
|
2092
|
-
});
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
// Setup file tree event listeners
|
|
2096
|
-
function setupFileTree() {
|
|
2097
|
-
const explorerBtn = document.getElementById('editorExplorerBtn');
|
|
2098
|
-
|
|
2099
|
-
// Explorer button click toggles file tree
|
|
2100
|
-
explorerBtn.onclick = (e) => {
|
|
2101
|
-
e.stopPropagation();
|
|
2102
|
-
toggleFileTree();
|
|
2103
|
-
};
|
|
2104
|
-
|
|
2105
|
-
// Close button
|
|
2106
|
-
fileTreeClose.onclick = (e) => {
|
|
2107
|
-
e.stopPropagation();
|
|
2108
|
-
closeFileTree();
|
|
2109
|
-
};
|
|
2110
|
-
|
|
2111
|
-
// Prevent clicks inside panel from closing it
|
|
2112
|
-
fileTreePanel.onclick = (e) => {
|
|
2113
|
-
e.stopPropagation();
|
|
2114
|
-
};
|
|
2115
|
-
|
|
2116
|
-
// Close on escape key
|
|
2117
|
-
document.addEventListener('keydown', (e) => {
|
|
2118
|
-
if (e.key === 'Escape' && fileTreeOpen) {
|
|
2119
|
-
closeFileTree();
|
|
2120
|
-
}
|
|
2121
|
-
});
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
// Initialize file tree
|
|
2125
|
-
setupFileTree();
|
|
2126
|
-
|
|
2127
|
-
// ==================== Editor Search ====================
|
|
2128
|
-
|
|
2129
|
-
function openEditorSearch() {
|
|
2130
|
-
editorSearchBar.classList.add('open');
|
|
2131
|
-
editorSearchInput.focus();
|
|
2132
|
-
editorSearchInput.select();
|
|
2133
|
-
}
|
|
2134
|
-
|
|
2135
|
-
function closeEditorSearch() {
|
|
2136
|
-
editorSearchBar.classList.remove('open');
|
|
2137
|
-
editorSearchInput.value = '';
|
|
2138
|
-
searchMatches = [];
|
|
2139
|
-
currentMatchIndex = -1;
|
|
2140
|
-
editorSearchCount.textContent = '';
|
|
2141
|
-
clearSearchHighlights();
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
function clearSearchHighlights() {
|
|
2145
|
-
// Restore original HTML for lines that were modified
|
|
2146
|
-
const modifiedLines = panels.editor.content.querySelectorAll('.editor-line-code[data-original-html]');
|
|
2147
|
-
modifiedLines.forEach(el => {
|
|
2148
|
-
el.innerHTML = el.dataset.originalHtml;
|
|
2149
|
-
delete el.dataset.originalHtml;
|
|
2150
|
-
});
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
function performSearch(query) {
|
|
2154
|
-
clearSearchHighlights();
|
|
2155
|
-
searchMatches = [];
|
|
2156
|
-
currentMatchIndex = -1;
|
|
2157
|
-
|
|
2158
|
-
if (!query || query.length < 1) {
|
|
2159
|
-
editorSearchCount.textContent = '';
|
|
2160
|
-
return;
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
const codeElements = panels.editor.content.querySelectorAll('.editor-line-code');
|
|
2164
|
-
const queryLower = query.toLowerCase();
|
|
2165
|
-
|
|
2166
|
-
codeElements.forEach((lineEl, lineIndex) => {
|
|
2167
|
-
const text = lineEl.textContent;
|
|
2168
|
-
const textLower = text.toLowerCase();
|
|
2169
|
-
let startIndex = 0;
|
|
2170
|
-
let matchIndex;
|
|
2171
|
-
|
|
2172
|
-
while ((matchIndex = textLower.indexOf(queryLower, startIndex)) !== -1) {
|
|
2173
|
-
searchMatches.push({
|
|
2174
|
-
lineIndex,
|
|
2175
|
-
lineEl,
|
|
2176
|
-
startIndex: matchIndex,
|
|
2177
|
-
endIndex: matchIndex + query.length
|
|
2178
|
-
});
|
|
2179
|
-
startIndex = matchIndex + 1;
|
|
2180
|
-
}
|
|
2181
|
-
});
|
|
2182
|
-
|
|
2183
|
-
if (searchMatches.length > 0) {
|
|
2184
|
-
highlightMatches(query);
|
|
2185
|
-
currentMatchIndex = 0;
|
|
2186
|
-
scrollToMatch(0);
|
|
2187
|
-
updateSearchCount();
|
|
2188
|
-
} else {
|
|
2189
|
-
editorSearchCount.textContent = 'No results';
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
function highlightMatches(query) {
|
|
2194
|
-
const codeElements = panels.editor.content.querySelectorAll('.editor-line-code');
|
|
2195
|
-
const queryLower = query.toLowerCase();
|
|
2196
|
-
|
|
2197
|
-
codeElements.forEach(lineEl => {
|
|
2198
|
-
const text = lineEl.textContent;
|
|
2199
|
-
const textLower = text.toLowerCase();
|
|
2200
|
-
|
|
2201
|
-
if (textLower.includes(queryLower)) {
|
|
2202
|
-
// Store original HTML to restore later
|
|
2203
|
-
if (!lineEl.dataset.originalHtml) {
|
|
2204
|
-
lineEl.dataset.originalHtml = lineEl.innerHTML;
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
// Work with text content, find all match positions
|
|
2208
|
-
const matches = [];
|
|
2209
|
-
let idx = 0;
|
|
2210
|
-
while ((idx = textLower.indexOf(queryLower, idx)) !== -1) {
|
|
2211
|
-
matches.push({ start: idx, end: idx + query.length });
|
|
2212
|
-
idx++;
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
if (matches.length > 0) {
|
|
2216
|
-
// Rebuild the line with highlights
|
|
2217
|
-
let result = '';
|
|
2218
|
-
let lastEnd = 0;
|
|
2219
|
-
|
|
2220
|
-
matches.forEach(match => {
|
|
2221
|
-
// Add text before match (escaped)
|
|
2222
|
-
result += escapeHtml(text.slice(lastEnd, match.start));
|
|
2223
|
-
// Add highlighted match
|
|
2224
|
-
result += `<span class="search-highlight">${escapeHtml(text.slice(match.start, match.end))}</span>`;
|
|
2225
|
-
lastEnd = match.end;
|
|
2226
|
-
});
|
|
2227
|
-
|
|
2228
|
-
// Add remaining text
|
|
2229
|
-
result += escapeHtml(text.slice(lastEnd));
|
|
2230
|
-
|
|
2231
|
-
lineEl.innerHTML = result;
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
});
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
function scrollToMatch(index) {
|
|
2238
|
-
const highlights = panels.editor.content.querySelectorAll('.search-highlight');
|
|
2239
|
-
|
|
2240
|
-
// Remove current class from all
|
|
2241
|
-
highlights.forEach(el => el.classList.remove('current'));
|
|
2242
|
-
|
|
2243
|
-
if (highlights[index]) {
|
|
2244
|
-
highlights[index].classList.add('current');
|
|
2245
|
-
|
|
2246
|
-
// Get the scroll container and the highlight element
|
|
2247
|
-
const container = panels.editor.content;
|
|
2248
|
-
const element = highlights[index];
|
|
2249
|
-
|
|
2250
|
-
// Get the line element (parent of the highlight)
|
|
2251
|
-
const lineEl = element.closest('.editor-line');
|
|
2252
|
-
if (lineEl) {
|
|
2253
|
-
// Calculate scroll position to center the line
|
|
2254
|
-
const containerRect = container.getBoundingClientRect();
|
|
2255
|
-
const lineRect = lineEl.getBoundingClientRect();
|
|
2256
|
-
const scrollTop = container.scrollTop + (lineRect.top - containerRect.top) - (containerRect.height / 2) + (lineRect.height / 2);
|
|
2257
|
-
|
|
2258
|
-
container.scrollTo({
|
|
2259
|
-
top: Math.max(0, scrollTop),
|
|
2260
|
-
behavior: 'smooth'
|
|
2261
|
-
});
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
function updateSearchCount() {
|
|
2267
|
-
if (searchMatches.length > 0) {
|
|
2268
|
-
editorSearchCount.textContent = `${currentMatchIndex + 1}/${searchMatches.length}`;
|
|
2269
|
-
} else {
|
|
2270
|
-
editorSearchCount.textContent = 'No results';
|
|
1846
|
+
showEmptyState('editor', 'codicon-file-code', 'Failed to load file.');
|
|
2271
1847
|
}
|
|
2272
1848
|
}
|
|
2273
1849
|
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
}
|
|
1850
|
+
// =============================================================================
|
|
1851
|
+
// Event Listeners Setup
|
|
1852
|
+
// =============================================================================
|
|
1853
|
+
$('editorExplorerBtn').onclick = (e) => { e.stopPropagation(); toggleFileTree(); };
|
|
1854
|
+
fileTreeDropdown.onclick = (e) => { e.stopPropagation(); };
|
|
2280
1855
|
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
updateSearchCount();
|
|
2286
|
-
}
|
|
1856
|
+
// Close dropdown when clicking outside
|
|
1857
|
+
document.addEventListener('click', (e) => {
|
|
1858
|
+
if (fileTreeOpen && !e.target.closest('.editor-explorer-wrapper')) closeFileTree();
|
|
1859
|
+
});
|
|
2287
1860
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
editorSearchClose.onclick = () => closeEditorSearch();
|
|
2292
|
-
editorSearchNext.onclick = () => goToNextMatch();
|
|
2293
|
-
editorSearchPrev.onclick = () => goToPrevMatch();
|
|
2294
|
-
|
|
2295
|
-
editorSearchInput.oninput = (e) => {
|
|
2296
|
-
performSearch(e.target.value);
|
|
2297
|
-
};
|
|
2298
|
-
|
|
2299
|
-
editorSearchInput.onkeydown = (e) => {
|
|
2300
|
-
if (e.key === 'Enter') {
|
|
2301
|
-
e.preventDefault();
|
|
2302
|
-
if (e.shiftKey) {
|
|
2303
|
-
goToPrevMatch();
|
|
2304
|
-
} else {
|
|
2305
|
-
goToNextMatch();
|
|
2306
|
-
}
|
|
2307
|
-
} else if (e.key === 'Escape') {
|
|
2308
|
-
closeEditorSearch();
|
|
2309
|
-
}
|
|
2310
|
-
};
|
|
2311
|
-
|
|
2312
|
-
// Ctrl+F / Cmd+F to open search
|
|
2313
|
-
document.addEventListener('keydown', (e) => {
|
|
2314
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'f' && activePanel === 'editor') {
|
|
2315
|
-
e.preventDefault();
|
|
2316
|
-
openEditorSearch();
|
|
2317
|
-
}
|
|
2318
|
-
});
|
|
2319
|
-
}
|
|
1861
|
+
document.addEventListener('keydown', (e) => {
|
|
1862
|
+
if (e.key === 'Escape' && fileTreeOpen) closeFileTree();
|
|
1863
|
+
});
|
|
2320
1864
|
|
|
2321
|
-
setupEditorSearch();
|
|
2322
|
-
|
|
2323
|
-
// Visibility change handler
|
|
2324
1865
|
document.addEventListener('visibilitychange', () => {
|
|
2325
|
-
if (document.visibilityState === 'visible' && (!ws || ws.readyState !== WebSocket.OPEN))
|
|
2326
|
-
connect();
|
|
2327
|
-
}
|
|
1866
|
+
if (document.visibilityState === 'visible' && (!ws || ws.readyState !== WebSocket.OPEN)) connect();
|
|
2328
1867
|
});
|
|
2329
|
-
|
|
1868
|
+
|
|
2330
1869
|
// Initialize
|
|
2331
1870
|
connect();
|
|
2332
1871
|
</script>
|