kiro-mobile-bridge 1.0.6 → 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 +1167 -1598
- package/src/routes/api.js +358 -0
- package/src/server.js +271 -2566
- 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
|
-
.editor-code { margin: 0; padding: 8px 0; font-family:
|
|
63
|
-
.editor-line { display: flex; min-height:
|
|
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; }
|
|
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:
|
|
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,45 +783,28 @@
|
|
|
1056
783
|
background: rgba(255, 255, 255, 0.1) !important;
|
|
1057
784
|
}
|
|
1058
785
|
|
|
1059
|
-
/*
|
|
1060
|
-
[class*="model-selector"] svg, [class*="modelSelector"] svg,
|
|
1061
|
-
[class*="model-dropdown"] svg, button[class*="dropdown"] svg {
|
|
1062
|
-
width: 12px !important;
|
|
1063
|
-
height: 12px !important;
|
|
1064
|
-
flex-shrink: 0 !important;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
/* Model name text inside selector */
|
|
1068
|
-
[class*="model-selector"] span, [class*="modelSelector"] span,
|
|
1069
|
-
[class*="model-name"], [class*="modelName"] {
|
|
1070
|
-
overflow: hidden !important;
|
|
1071
|
-
text-overflow: ellipsis !important;
|
|
1072
|
-
white-space: nowrap !important;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
/* Reset positioning for captured content to prevent layout issues */
|
|
786
|
+
/* Reset positioning */
|
|
1076
787
|
#root, .app, [class*="cascade"], [class*="Cascade"] {
|
|
1077
788
|
position: relative !important;
|
|
1078
789
|
}
|
|
1079
790
|
|
|
1080
|
-
|
|
1081
|
-
[style*="position: fixed"],
|
|
1082
|
-
[style*="position:fixed"] {
|
|
791
|
+
[style*="position: fixed"], [style*="position:fixed"] {
|
|
1083
792
|
position: absolute !important;
|
|
1084
793
|
}
|
|
1085
794
|
|
|
1086
795
|
* { font-family: inherit; }
|
|
1087
796
|
code, pre, .monaco-editor { font-family: Menlo, Monaco, "Courier New", monospace !important; }
|
|
1088
|
-
|
|
797
|
+
|
|
798
|
+
/* Icon sizes */
|
|
1089
799
|
svg[viewBox="0 0 24 24"] { width: 16px !important; height: 16px !important; }
|
|
1090
800
|
svg[viewBox="0 0 16 16"] { width: 14px !important; height: 14px !important; }
|
|
1091
801
|
svg { color: inherit; max-width: 20px !important; max-height: 20px !important; }
|
|
1092
802
|
svg path, svg rect { fill: currentColor; }
|
|
1093
803
|
svg[fill="none"] path { fill: none; }
|
|
1094
|
-
/* Preserve stroke-based SVG circles (like context window progress ring) */
|
|
1095
804
|
svg circle[stroke] { fill: none !important; }
|
|
1096
805
|
svg circle:not([stroke]) { fill: currentColor; }
|
|
1097
|
-
|
|
806
|
+
|
|
807
|
+
/* Context window progress circle */
|
|
1098
808
|
svg[class*="context"], svg[class*="progress"], [class*="context"] svg {
|
|
1099
809
|
overflow: visible !important;
|
|
1100
810
|
}
|
|
@@ -1102,7 +812,6 @@
|
|
|
1102
812
|
fill: none !important;
|
|
1103
813
|
stroke-linecap: round !important;
|
|
1104
814
|
}
|
|
1105
|
-
/* Context window indicator - circular progress ring */
|
|
1106
815
|
[class*="context-window"], [class*="contextWindow"], [class*="ContextWindow"],
|
|
1107
816
|
[class*="context-indicator"], [class*="contextIndicator"], [class*="ContextIndicator"],
|
|
1108
817
|
[class*="token-usage"], [class*="tokenUsage"], [class*="TokenUsage"] {
|
|
@@ -1116,7 +825,6 @@
|
|
|
1116
825
|
position: relative !important;
|
|
1117
826
|
flex-shrink: 0 !important;
|
|
1118
827
|
}
|
|
1119
|
-
/* Create gray background circle using box-shadow on the container */
|
|
1120
828
|
[class*="context-window"]::before, [class*="contextWindow"]::before, [class*="ContextWindow"]::before,
|
|
1121
829
|
[class*="context-indicator"]::before, [class*="contextIndicator"]::before {
|
|
1122
830
|
content: '' !important;
|
|
@@ -1145,15 +853,12 @@
|
|
|
1145
853
|
stroke-linecap: round !important;
|
|
1146
854
|
stroke: #4ade80 !important;
|
|
1147
855
|
}
|
|
1148
|
-
|
|
1149
|
-
[class*="context"] svg circle,
|
|
1150
|
-
[class*="Context"] svg circle {
|
|
856
|
+
[class*="context"] svg circle, [class*="Context"] svg circle {
|
|
1151
857
|
display: block !important;
|
|
1152
858
|
visibility: visible !important;
|
|
1153
859
|
}
|
|
1154
860
|
|
|
1155
|
-
/*
|
|
1156
|
-
/* The toolbar/footer area containing model selector, context, autopilot, input */
|
|
861
|
+
/* Chat input toolbar area */
|
|
1157
862
|
[class*="chat-input"], [class*="chatInput"], [class*="ChatInput"],
|
|
1158
863
|
[class*="input-area"], [class*="inputArea"], [class*="InputArea"],
|
|
1159
864
|
[class*="message-input"], [class*="messageInput"], [class*="MessageInput"],
|
|
@@ -1167,7 +872,7 @@
|
|
|
1167
872
|
border-top: 1px solid var(--color-border-primary, #4a464f) !important;
|
|
1168
873
|
}
|
|
1169
874
|
|
|
1170
|
-
/* Input toolbar row
|
|
875
|
+
/* Input toolbar row */
|
|
1171
876
|
[class*="input-toolbar"], [class*="inputToolbar"], [class*="InputToolbar"],
|
|
1172
877
|
[class*="chat-toolbar"], [class*="chatToolbar"], [class*="ChatToolbar"],
|
|
1173
878
|
[class*="composer-toolbar"], [class*="composerToolbar"] {
|
|
@@ -1178,7 +883,7 @@
|
|
|
1178
883
|
flex-wrap: wrap !important;
|
|
1179
884
|
}
|
|
1180
885
|
|
|
1181
|
-
/* Context button (# symbol)
|
|
886
|
+
/* Context button (# symbol) */
|
|
1182
887
|
[class*="context-button"], [class*="contextButton"], [class*="ContextButton"],
|
|
1183
888
|
button[aria-label*="context"], button[aria-label*="Context"],
|
|
1184
889
|
[class*="hash-button"], [class*="hashButton"] {
|
|
@@ -1203,30 +908,15 @@
|
|
|
1203
908
|
background: rgba(255, 255, 255, 0.1) !important;
|
|
1204
909
|
}
|
|
1205
910
|
|
|
1206
|
-
/*
|
|
1207
|
-
|
|
1208
|
-
[class*="message"] p,
|
|
1209
|
-
[class*="Message"] p,
|
|
1210
|
-
[class*="chat"] p,
|
|
1211
|
-
[class*="Chat"] p {
|
|
911
|
+
/* Message text flow - inline elements */
|
|
912
|
+
[class*="message"] p, [class*="Message"] p, [class*="chat"] p, [class*="Chat"] p {
|
|
1212
913
|
display: block !important;
|
|
1213
914
|
}
|
|
1214
915
|
|
|
1215
|
-
|
|
1216
|
-
[class*="message"] a,
|
|
1217
|
-
[class*="
|
|
1218
|
-
[class*="
|
|
1219
|
-
[class*="Message"] code,
|
|
1220
|
-
[class*="message"] span,
|
|
1221
|
-
[class*="Message"] span,
|
|
1222
|
-
[class*="chat"] a,
|
|
1223
|
-
[class*="Chat"] a,
|
|
1224
|
-
[class*="chat"] code,
|
|
1225
|
-
[class*="Chat"] code,
|
|
1226
|
-
[class*="file-link"],
|
|
1227
|
-
[class*="FileLink"],
|
|
1228
|
-
[class*="inline"],
|
|
1229
|
-
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) {
|
|
1230
920
|
display: inline !important;
|
|
1231
921
|
vertical-align: baseline !important;
|
|
1232
922
|
}
|
|
@@ -1240,17 +930,10 @@
|
|
|
1240
930
|
font-size: 12px !important;
|
|
1241
931
|
}
|
|
1242
932
|
|
|
1243
|
-
/* File badges/chips
|
|
1244
|
-
[class*="file-badge"],
|
|
1245
|
-
[class*="
|
|
1246
|
-
[class*="
|
|
1247
|
-
[class*="FileChip"],
|
|
1248
|
-
[class*="deleted"],
|
|
1249
|
-
[class*="Deleted"],
|
|
1250
|
-
[class*="created"],
|
|
1251
|
-
[class*="Created"],
|
|
1252
|
-
[class*="modified"],
|
|
1253
|
-
[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"] {
|
|
1254
937
|
display: inline-flex !important;
|
|
1255
938
|
align-items: center !important;
|
|
1256
939
|
gap: 4px !important;
|
|
@@ -1262,19 +945,10 @@
|
|
|
1262
945
|
vertical-align: middle !important;
|
|
1263
946
|
}
|
|
1264
947
|
|
|
1265
|
-
/* Bullet lists
|
|
1266
|
-
ul li, ol li {
|
|
1267
|
-
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
ul li > *, ol li > * {
|
|
1271
|
-
display: inline !important;
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
ul li > p, ol li > p {
|
|
1275
|
-
display: inline !important;
|
|
1276
|
-
margin: 0 !important;
|
|
1277
|
-
}
|
|
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; }
|
|
1278
952
|
|
|
1279
953
|
[role="switch"], [class*="toggle"] {
|
|
1280
954
|
overflow: visible !important;
|
|
@@ -1282,117 +956,7 @@
|
|
|
1282
956
|
flex-shrink: 0 !important;
|
|
1283
957
|
}
|
|
1284
958
|
|
|
1285
|
-
/*
|
|
1286
|
-
|
|
1287
|
-
/* Force solid background for task containers */
|
|
1288
|
-
[class*="task"], [class*="Task"],
|
|
1289
|
-
div:has(> h1:contains("CURRENT TASKS")),
|
|
1290
|
-
div:has(> h2:contains("CURRENT TASKS")),
|
|
1291
|
-
div:has(> h3:contains("CURRENT TASKS")),
|
|
1292
|
-
div:has(> h1:contains("TASKS IN QUEUE")),
|
|
1293
|
-
div:has(> h2:contains("TASKS IN QUEUE")),
|
|
1294
|
-
div:has(> h3:contains("TASKS IN QUEUE")) {
|
|
1295
|
-
font-family: "Segoe WPC", "Segoe UI", -apple-system, BlinkMacSystemFont, system-ui, Ubuntu, "Droid Sans", sans-serif !important;
|
|
1296
|
-
background: #1e1e1e !important;
|
|
1297
|
-
background-color: #1e1e1e !important;
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
/* Task section headers - BOLD WHITE UPPERCASE */
|
|
1301
|
-
h1:contains("CURRENT TASKS"),
|
|
1302
|
-
h2:contains("CURRENT TASKS"),
|
|
1303
|
-
h3:contains("CURRENT TASKS"),
|
|
1304
|
-
h1:contains("TASKS IN QUEUE"),
|
|
1305
|
-
h2:contains("TASKS IN QUEUE"),
|
|
1306
|
-
h3:contains("TASKS IN QUEUE"),
|
|
1307
|
-
[class*="task"] h1, [class*="Task"] h1,
|
|
1308
|
-
[class*="task"] h2, [class*="Task"] h2,
|
|
1309
|
-
[class*="task"] h3, [class*="Task"] h3,
|
|
1310
|
-
[class*="task-header"], [class*="TaskHeader"],
|
|
1311
|
-
[class*="section-title"], [class*="SectionTitle"],
|
|
1312
|
-
div[class*="current"] h1, div[class*="Current"] h1,
|
|
1313
|
-
div[class*="current"] h2, div[class*="Current"] h2,
|
|
1314
|
-
div[class*="current"] h3, div[class*="Current"] h3,
|
|
1315
|
-
div[class*="queue"] h1, div[class*="Queue"] h1,
|
|
1316
|
-
div[class*="queue"] h2, div[class*="Queue"] h2,
|
|
1317
|
-
div[class*="queue"] h3, div[class*="Queue"] h3 {
|
|
1318
|
-
font-size: 13px !important;
|
|
1319
|
-
font-weight: 700 !important;
|
|
1320
|
-
letter-spacing: 0.5px !important;
|
|
1321
|
-
text-transform: uppercase !important;
|
|
1322
|
-
color: #ffffff !important;
|
|
1323
|
-
margin: 0 0 12px 0 !important;
|
|
1324
|
-
padding: 0 !important;
|
|
1325
|
-
line-height: 1.4 !important;
|
|
1326
|
-
background: transparent !important;
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
/* Empty state messages - ITALIC GRAY */
|
|
1330
|
-
p:contains("No active tasks"),
|
|
1331
|
-
p:contains("No tasks in queue"),
|
|
1332
|
-
[class*="task"] p:not([class*="task-item"]),
|
|
1333
|
-
[class*="Task"] p:not([class*="TaskItem"]),
|
|
1334
|
-
[class*="no-task"], [class*="NoTask"],
|
|
1335
|
-
[class*="empty-task"], [class*="EmptyTask"],
|
|
1336
|
-
[class*="task-empty"], [class*="TaskEmpty"] {
|
|
1337
|
-
font-size: 13px !important;
|
|
1338
|
-
font-style: italic !important;
|
|
1339
|
-
color: #888888 !important;
|
|
1340
|
-
margin: 0 0 16px 0 !important;
|
|
1341
|
-
padding: 0 !important;
|
|
1342
|
-
line-height: 1.6 !important;
|
|
1343
|
-
background: transparent !important;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
/* Task list containers */
|
|
1347
|
-
[class*="task-list"], [class*="TaskList"],
|
|
1348
|
-
[class*="task-section"], [class*="TaskSection"] {
|
|
1349
|
-
margin-bottom: 24px !important;
|
|
1350
|
-
padding: 0 !important;
|
|
1351
|
-
background: #1e1e1e !important;
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
/* Horizontal divider between sections */
|
|
1355
|
-
[class*="task-divider"], [class*="TaskDivider"],
|
|
1356
|
-
[class*="task"] hr, [class*="Task"] hr {
|
|
1357
|
-
border: none !important;
|
|
1358
|
-
border-top: 1px solid #3c3c3c !important;
|
|
1359
|
-
margin: 20px 0 !important;
|
|
1360
|
-
background: transparent !important;
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
/* Individual task items (when they exist) */
|
|
1364
|
-
[class*="task-item"], [class*="TaskItem"] {
|
|
1365
|
-
padding: 8px 12px !important;
|
|
1366
|
-
margin: 4px 0 !important;
|
|
1367
|
-
background: rgba(255, 255, 255, 0.03) !important;
|
|
1368
|
-
border-radius: 4px !important;
|
|
1369
|
-
border-left: 2px solid #7138cc !important;
|
|
1370
|
-
font-size: 13px !important;
|
|
1371
|
-
color: #cccccc !important;
|
|
1372
|
-
line-height: 1.5 !important;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
[class*="task-item"]:hover, [class*="TaskItem"]:hover {
|
|
1376
|
-
background: rgba(255, 255, 255, 0.05) !important;
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
/* Task item with close button */
|
|
1380
|
-
[class*="task-item"] button, [class*="TaskItem"] button {
|
|
1381
|
-
margin-left: 8px !important;
|
|
1382
|
-
padding: 2px 6px !important;
|
|
1383
|
-
font-size: 11px !important;
|
|
1384
|
-
background: rgba(255, 255, 255, 0.1) !important;
|
|
1385
|
-
border: 1px solid #4a464f !important;
|
|
1386
|
-
border-radius: 3px !important;
|
|
1387
|
-
color: #888 !important;
|
|
1388
|
-
cursor: pointer !important;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
[class*="task-item"] button:hover, [class*="TaskItem"] button:hover {
|
|
1392
|
-
background: rgba(255, 255, 255, 0.15) !important;
|
|
1393
|
-
color: #fff !important;
|
|
1394
|
-
}
|
|
1395
|
-
|
|
959
|
+
/* Toggle switch styling */
|
|
1396
960
|
.kiro-toggle-switch {
|
|
1397
961
|
display: flex !important;
|
|
1398
962
|
align-items: center !important;
|
|
@@ -1431,193 +995,586 @@
|
|
|
1431
995
|
}
|
|
1432
996
|
</style>`;
|
|
1433
997
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
998
|
+
|
|
999
|
+
function renderChatSnapshot(snapshot, isUpdate = false) {
|
|
1000
|
+
if (isRendering) return;
|
|
1001
|
+
isRendering = true;
|
|
1438
1002
|
|
|
1439
|
-
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
el.addEventListener('blur', () => { setTimeout(() => { isTypingInKiroInput = false; }, 500); });
|
|
1446
|
-
el.addEventListener('keydown', async (e) => {
|
|
1447
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1448
|
-
e.preventDefault();
|
|
1449
|
-
const text = el.textContent || el.value || '';
|
|
1450
|
-
if (text.trim()) {
|
|
1451
|
-
await sendToKiro(text.trim());
|
|
1452
|
-
el.textContent = '';
|
|
1453
|
-
el.innerHTML = '';
|
|
1454
|
-
isTypingInKiroInput = false;
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
});
|
|
1458
|
-
});
|
|
1459
|
-
}
|
|
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);
|
|
1460
1009
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
const wrapper = toggle.closest('.kiro-toggle-switch') || toggle;
|
|
1465
|
-
|
|
1466
|
-
wrapper.style.cursor = 'pointer';
|
|
1467
|
-
wrapper.onclick = async (e) => {
|
|
1468
|
-
e.preventDefault();
|
|
1469
|
-
e.stopPropagation();
|
|
1470
|
-
|
|
1471
|
-
const label = wrapper.querySelector('label');
|
|
1472
|
-
const clickInfo = {
|
|
1473
|
-
tag: 'div',
|
|
1474
|
-
text: label ? label.textContent.trim() : 'toggle',
|
|
1475
|
-
role: 'switch',
|
|
1476
|
-
isToggle: true,
|
|
1477
|
-
toggleId: input ? input.id : '',
|
|
1478
|
-
checked: input ? input.checked : false
|
|
1479
|
-
};
|
|
1480
|
-
await sendClickToKiro(clickInfo);
|
|
1481
|
-
return false;
|
|
1482
|
-
};
|
|
1483
|
-
});
|
|
1010
|
+
let html = getBaseStyles();
|
|
1011
|
+
if (currentStyles) html += `<style>${currentStyles}</style>`;
|
|
1012
|
+
html += snapshot.html || '';
|
|
1484
1013
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1014
|
+
panels.chat.content.innerHTML = html;
|
|
1015
|
+
panels.chat.container.style.background = snapshot.bodyBg || '#1e1e1e';
|
|
1016
|
+
hideLoading('chat');
|
|
1487
1017
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
btn.onclick = async (e) => {
|
|
1498
|
-
e.preventDefault();
|
|
1499
|
-
e.stopPropagation();
|
|
1500
|
-
|
|
1501
|
-
// Find the input field and get its text
|
|
1502
|
-
const inputSelectors = ['.tiptap', '.ProseMirror', '[data-lexical-editor="true"]', '[contenteditable="true"]', 'textarea'];
|
|
1503
|
-
let inputText = '';
|
|
1504
|
-
let inputEl = null;
|
|
1505
|
-
|
|
1506
|
-
for (const inputSel of inputSelectors) {
|
|
1507
|
-
const input = content.querySelector(inputSel);
|
|
1508
|
-
if (input) {
|
|
1509
|
-
inputEl = input;
|
|
1510
|
-
inputText = input.textContent || input.innerText || input.value || '';
|
|
1511
|
-
if (inputText.trim()) break;
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
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"]');
|
|
1514
1026
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
} else if (
|
|
1523
|
-
|
|
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;
|
|
1524
1038
|
}
|
|
1525
1039
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
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;
|
|
1529
1047
|
}
|
|
1530
|
-
|
|
1531
|
-
};
|
|
1532
|
-
};
|
|
1533
|
-
|
|
1534
|
-
// PRIMARY METHOD: Find the submit button by data-variant="submit" (CONFIRMED via Playwriter)
|
|
1535
|
-
const submitBtn = content.querySelector('button[data-variant="submit"]');
|
|
1536
|
-
if (submitBtn) {
|
|
1537
|
-
attachSendHandler(submitBtn);
|
|
1538
|
-
}
|
|
1048
|
+
isRendering = false;
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
1539
1051
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
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';
|
|
1546
1061
|
}
|
|
1547
1062
|
});
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
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');
|
|
1557
1092
|
}
|
|
1558
1093
|
}
|
|
1559
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();
|
|
1560
1105
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
|
|
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;
|
|
1138
|
+
}
|
|
1582
1139
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
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; }
|
|
1158
|
+
|
|
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
|
+
}
|
|
1587
1168
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
+
}
|
|
1591
1175
|
|
|
1592
|
-
//
|
|
1593
|
-
if (
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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;
|
|
1599
1190
|
}
|
|
1600
1191
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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;
|
|
1604
1235
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1236
|
+
while ((matchIndex = textLower.indexOf(queryLower, startIndex)) !== -1) {
|
|
1237
|
+
searchMatches.push({ lineIndex, lineEl, startIndex: matchIndex, endIndex: matchIndex + query.length });
|
|
1238
|
+
startIndex = matchIndex + 1;
|
|
1608
1239
|
}
|
|
1609
1240
|
});
|
|
1610
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' });
|
|
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
|
+
};
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1611
1568
|
// Tabs
|
|
1612
1569
|
content.querySelectorAll('[role="tab"]').forEach(tab => {
|
|
1613
1570
|
const closeBtn = tab.querySelector('[aria-label="close"], [class*="close"]');
|
|
1614
|
-
|
|
1615
1571
|
if (closeBtn) {
|
|
1616
1572
|
closeBtn.style.cursor = 'pointer';
|
|
1617
1573
|
closeBtn.onclick = async (e) => {
|
|
1618
|
-
e.preventDefault();
|
|
1619
|
-
|
|
1620
|
-
|
|
1574
|
+
e.preventDefault(); e.stopPropagation();
|
|
1575
|
+
const labelEl = tab.querySelector('.kiro-tabs-item-label, [class*="label"]');
|
|
1576
|
+
const tabLabel = labelEl ? labelEl.textContent.trim() : tab.textContent.trim();
|
|
1577
|
+
await sendClickToKiro({ tag: 'button', text: 'close', ariaLabel: 'close', role: 'button', isCloseButton: true, parentTabLabel: tabLabel });
|
|
1621
1578
|
return false;
|
|
1622
1579
|
};
|
|
1623
1580
|
}
|
|
@@ -1625,129 +1582,167 @@
|
|
|
1625
1582
|
tab.style.cursor = 'pointer';
|
|
1626
1583
|
tab.onclick = async (e) => {
|
|
1627
1584
|
if (e.target.closest('[aria-label="close"], [class*="close"], button')) return;
|
|
1628
|
-
e.preventDefault();
|
|
1629
|
-
e.stopPropagation();
|
|
1630
|
-
|
|
1585
|
+
e.preventDefault(); e.stopPropagation();
|
|
1631
1586
|
const labelEl = tab.querySelector('.kiro-tabs-item-label, [class*="label"]');
|
|
1632
1587
|
const labelText = labelEl ? labelEl.textContent.trim() : tab.textContent.trim();
|
|
1633
|
-
|
|
1634
1588
|
await sendClickToKiro({ tag: 'div', text: labelText, role: 'tab', isTab: true, tabLabel: labelText });
|
|
1635
1589
|
return false;
|
|
1636
1590
|
};
|
|
1637
1591
|
});
|
|
1638
1592
|
|
|
1639
|
-
// File links
|
|
1640
|
-
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
|
|
1641
|
-
const filePathPattern = /^[a-zA-Z0-9_\-./\\]+\.[a-zA-Z0-9]+$/;
|
|
1642
|
-
|
|
1643
|
-
// 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;
|
|
1644
1595
|
content.querySelectorAll('a, code, span, [class*="file"], [class*="path"], [data-path]').forEach(el => {
|
|
1645
1596
|
if (el.onclick) return;
|
|
1646
1597
|
const text = (el.textContent || '').trim();
|
|
1647
1598
|
const dataPath = el.getAttribute('data-path') || '';
|
|
1648
1599
|
const href = el.getAttribute('href') || '';
|
|
1649
1600
|
|
|
1650
|
-
|
|
1651
|
-
const isFilePath = (
|
|
1652
|
-
fileExtensions.test(text) ||
|
|
1653
|
-
fileExtensions.test(dataPath) ||
|
|
1654
|
-
fileExtensions.test(href) ||
|
|
1655
|
-
(filePathPattern.test(text) && text.includes('/'))
|
|
1656
|
-
);
|
|
1657
|
-
|
|
1658
|
-
if (isFilePath) {
|
|
1601
|
+
if (fileExtensions.test(text) || fileExtensions.test(dataPath) || fileExtensions.test(href)) {
|
|
1659
1602
|
const filePath = dataPath || text || href;
|
|
1660
1603
|
el.style.cursor = 'pointer';
|
|
1661
|
-
el.style.textDecoration = 'underline';
|
|
1662
1604
|
el.style.color = '#7eb6ff';
|
|
1663
1605
|
el.title = `Open ${filePath} in Editor`;
|
|
1664
|
-
|
|
1665
|
-
el.onclick = async (e) => {
|
|
1666
|
-
e.preventDefault();
|
|
1667
|
-
e.stopPropagation();
|
|
1668
|
-
await openFileInEditor(filePath);
|
|
1669
|
-
return false;
|
|
1670
|
-
};
|
|
1606
|
+
el.onclick = async (e) => { e.preventDefault(); e.stopPropagation(); await openFileInEditor(filePath); return false; };
|
|
1671
1607
|
}
|
|
1672
1608
|
});
|
|
1673
1609
|
|
|
1674
1610
|
// Buttons and clickable elements
|
|
1675
1611
|
const clickableSelectors = ['button', '[role="button"]', '[role="menuitem"]', '[role="option"]', '[role="checkbox"]', 'a[href]', '[tabindex="0"]'];
|
|
1676
1612
|
const allClickables = new Set();
|
|
1677
|
-
clickableSelectors.forEach(sel => {
|
|
1678
|
-
try { content.querySelectorAll(sel).forEach(el => allClickables.add(el)); } catch(e) {}
|
|
1679
|
-
});
|
|
1613
|
+
clickableSelectors.forEach(sel => { try { content.querySelectorAll(sel).forEach(el => allClickables.add(el)); } catch(e) {} });
|
|
1680
1614
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
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
|
+
};
|
|
1684
1632
|
});
|
|
1685
1633
|
|
|
1686
|
-
//
|
|
1687
|
-
//
|
|
1688
|
-
|
|
1689
|
-
|
|
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
|
+
|
|
1690
1654
|
const text = el.textContent || '';
|
|
1691
|
-
const hasDate =
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1694
|
-
|
|
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
|
+
}
|
|
1695
1681
|
}
|
|
1696
1682
|
});
|
|
1697
1683
|
|
|
1698
|
-
// Also
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
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) {}
|
|
1704
1704
|
});
|
|
1705
1705
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
if (el.closest('[role="tab"]')) return;
|
|
1709
|
-
if (el.closest('.kiro-toggle-switch')) return;
|
|
1710
|
-
if (el.onclick) return;
|
|
1711
|
-
|
|
1706
|
+
// Attach click handlers to all potential history items
|
|
1707
|
+
potentialHistoryItems.forEach(el => {
|
|
1712
1708
|
el.style.cursor = 'pointer';
|
|
1713
1709
|
el.onclick = async (e) => {
|
|
1714
1710
|
e.preventDefault();
|
|
1715
1711
|
e.stopPropagation();
|
|
1716
|
-
const clickInfo = getElementClickInfo(el);
|
|
1717
1712
|
|
|
1718
|
-
//
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
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
|
+
};
|
|
1724
1733
|
|
|
1725
1734
|
await sendClickToKiro(clickInfo);
|
|
1735
|
+
|
|
1736
|
+
// Refresh snapshot after clicking history item
|
|
1737
|
+
setTimeout(() => fetchChatSnapshot(selectedCascadeId), 500);
|
|
1726
1738
|
return false;
|
|
1727
1739
|
};
|
|
1728
1740
|
});
|
|
1729
1741
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
text: (el.textContent || '').trim().substring(0, 100),
|
|
1735
|
-
ariaLabel: el.getAttribute('aria-label') || '',
|
|
1736
|
-
title: el.getAttribute('title') || '',
|
|
1737
|
-
role: el.getAttribute('role') || '',
|
|
1738
|
-
className: el.className || '',
|
|
1739
|
-
isTab: el.getAttribute('role') === 'tab',
|
|
1740
|
-
dataTestId: el.getAttribute('data-testid') || ''
|
|
1741
|
-
};
|
|
1742
|
-
|
|
1743
|
-
if (!info.ariaLabel) {
|
|
1744
|
-
const parent = el.closest('[aria-label]');
|
|
1745
|
-
if (parent) info.ariaLabel = parent.getAttribute('aria-label') || '';
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
return info;
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1742
|
+
|
|
1743
|
+
// =============================================================================
|
|
1744
|
+
// Kiro Communication
|
|
1745
|
+
// =============================================================================
|
|
1751
1746
|
async function sendToKiro(message) {
|
|
1752
1747
|
if (!message || !selectedCascadeId) return;
|
|
1753
1748
|
try {
|
|
@@ -1763,21 +1758,15 @@
|
|
|
1763
1758
|
showToast('Failed to send');
|
|
1764
1759
|
}
|
|
1765
1760
|
}
|
|
1766
|
-
|
|
1761
|
+
|
|
1767
1762
|
async function sendClickToKiro(clickInfo) {
|
|
1768
1763
|
if (!selectedCascadeId) return;
|
|
1769
1764
|
|
|
1770
|
-
const isNavigation =
|
|
1771
|
-
clickInfo.ariaLabel?.toLowerCase().includes('back') ||
|
|
1765
|
+
const isNavigation = clickInfo.ariaLabel?.toLowerCase().includes('back') ||
|
|
1772
1766
|
clickInfo.ariaLabel?.toLowerCase().includes('close') ||
|
|
1773
1767
|
clickInfo.text?.toLowerCase().includes('back') ||
|
|
1774
1768
|
clickInfo.role === 'tab';
|
|
1775
1769
|
|
|
1776
|
-
const isModelSelection = clickInfo.isModelSelection ||
|
|
1777
|
-
clickInfo.role === 'option' ||
|
|
1778
|
-
clickInfo.role === 'menuitem' ||
|
|
1779
|
-
clickInfo.className?.includes('model');
|
|
1780
|
-
|
|
1781
1770
|
if (isNavigation) navigationPending = true;
|
|
1782
1771
|
|
|
1783
1772
|
try {
|
|
@@ -1787,39 +1776,30 @@
|
|
|
1787
1776
|
body: JSON.stringify(clickInfo)
|
|
1788
1777
|
});
|
|
1789
1778
|
|
|
1790
|
-
if (isNavigation
|
|
1791
|
-
// Refresh snapshot after navigation or model selection
|
|
1792
|
-
setTimeout(() => fetchChatSnapshot(selectedCascadeId), 300);
|
|
1793
|
-
}
|
|
1779
|
+
if (isNavigation) setTimeout(() => fetchChatSnapshot(selectedCascadeId), 300);
|
|
1794
1780
|
} catch (e) {
|
|
1795
1781
|
navigationPending = false;
|
|
1796
1782
|
}
|
|
1797
1783
|
}
|
|
1798
|
-
|
|
1799
|
-
// Open file in Editor tab
|
|
1784
|
+
|
|
1800
1785
|
async function openFileInEditor(filePath) {
|
|
1801
1786
|
if (!selectedCascadeId || !filePath) return;
|
|
1802
1787
|
|
|
1803
1788
|
showToast(`Opening ${filePath}...`, 1500);
|
|
1804
1789
|
|
|
1805
|
-
// Switch to Editor tab
|
|
1790
|
+
// Switch to Editor tab
|
|
1806
1791
|
const editorTab = document.querySelector('[data-panel="editor"]');
|
|
1807
1792
|
if (editorTab) {
|
|
1808
1793
|
navTabs.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
|
|
1809
|
-
editorTab.classList.add('active');
|
|
1810
|
-
|
|
1811
|
-
Object.values(panels).forEach(p => {
|
|
1812
|
-
if (p.panel) p.panel.classList.remove('active');
|
|
1813
|
-
});
|
|
1794
|
+
editorTab.classList.add('active');
|
|
1795
|
+
Object.values(panels).forEach(p => { if (p.panel) p.panel.classList.remove('active'); });
|
|
1814
1796
|
panels.editor.panel.classList.add('active');
|
|
1815
1797
|
activePanel = 'editor';
|
|
1816
|
-
|
|
1817
|
-
// Show loading state
|
|
1818
1798
|
showLoading('editor', 'Loading file...');
|
|
1819
1799
|
}
|
|
1820
1800
|
|
|
1821
1801
|
try {
|
|
1822
|
-
//
|
|
1802
|
+
// Try direct file read first
|
|
1823
1803
|
const readResult = await fetch(`/readFile/${selectedCascadeId}`, {
|
|
1824
1804
|
method: 'POST',
|
|
1825
1805
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1831,472 +1811,61 @@
|
|
|
1831
1811
|
if (data.content) {
|
|
1832
1812
|
renderEditorSnapshot(data);
|
|
1833
1813
|
showToast(`Opened ${data.fileName || filePath}`, 1500, true);
|
|
1834
|
-
console.log('[Editor] Loaded file directly:', data.fileName, data.lineCount, 'lines');
|
|
1835
1814
|
return;
|
|
1836
1815
|
}
|
|
1837
|
-
} else {
|
|
1838
|
-
const errorData = await readResult.json().catch(() => ({}));
|
|
1839
|
-
console.log('[Editor] Direct file read failed:', errorData.error || readResult.status);
|
|
1840
1816
|
}
|
|
1841
1817
|
|
|
1842
|
-
// Fallback:
|
|
1843
|
-
|
|
1844
|
-
const clickResult = await fetch(`/click/${selectedCascadeId}`, {
|
|
1818
|
+
// Fallback: Click file link in Kiro
|
|
1819
|
+
await fetch(`/click/${selectedCascadeId}`, {
|
|
1845
1820
|
method: 'POST',
|
|
1846
1821
|
headers: { 'Content-Type': 'application/json' },
|
|
1847
|
-
body: JSON.stringify({
|
|
1848
|
-
tag: 'a',
|
|
1849
|
-
text: filePath,
|
|
1850
|
-
isFileLink: true,
|
|
1851
|
-
filePath: filePath
|
|
1852
|
-
})
|
|
1822
|
+
body: JSON.stringify({ tag: 'a', text: filePath, isFileLink: true, filePath })
|
|
1853
1823
|
});
|
|
1854
1824
|
|
|
1855
|
-
const result = await clickResult.json();
|
|
1856
|
-
console.log('Click result:', result);
|
|
1857
|
-
|
|
1858
|
-
// Wait for the file to open in Kiro
|
|
1859
1825
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
1860
1826
|
|
|
1861
|
-
// Fetch the editor content from Monaco
|
|
1862
1827
|
const fetchEditor = async () => {
|
|
1863
1828
|
try {
|
|
1864
1829
|
const r = await fetch(`/editor/${selectedCascadeId}`);
|
|
1865
1830
|
if (r.ok) {
|
|
1866
1831
|
const data = await r.json();
|
|
1867
|
-
if (data.content || data.html) {
|
|
1868
|
-
renderEditorSnapshot(data);
|
|
1869
|
-
showToast(`Opened ${data.fileName || filePath}`, 1500, true);
|
|
1870
|
-
return true;
|
|
1871
|
-
}
|
|
1832
|
+
if (data.content || data.html) { renderEditorSnapshot(data); showToast(`Opened ${data.fileName || filePath}`, 1500, true); return true; }
|
|
1872
1833
|
}
|
|
1873
1834
|
} catch (e) {}
|
|
1874
1835
|
return false;
|
|
1875
1836
|
};
|
|
1876
1837
|
|
|
1877
|
-
// Try fetching a few times with delays
|
|
1878
1838
|
if (!await fetchEditor()) {
|
|
1879
1839
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1880
1840
|
if (!await fetchEditor()) {
|
|
1881
|
-
|
|
1882
|
-
if (!await fetchEditor()) {
|
|
1883
|
-
showEmptyState('editor', 'codicon-file-code', `Could not load ${filePath}. File may not exist or path is incorrect.`);
|
|
1884
|
-
}
|
|
1841
|
+
showEmptyState('editor', 'codicon-file-code', `Could not load ${filePath}.`);
|
|
1885
1842
|
}
|
|
1886
1843
|
}
|
|
1887
|
-
|
|
1888
1844
|
} catch (e) {
|
|
1889
1845
|
showToast('Failed to open file');
|
|
1890
|
-
|
|
1891
|
-
showEmptyState('editor', 'codicon-file-code', 'Failed to load file. Check console for details.');
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
// ==================== File Tree ====================
|
|
1896
|
-
|
|
1897
|
-
// Toggle file tree panel
|
|
1898
|
-
function toggleFileTree() {
|
|
1899
|
-
fileTreeOpen = !fileTreeOpen;
|
|
1900
|
-
fileTreePanel.classList.toggle('open', fileTreeOpen);
|
|
1901
|
-
|
|
1902
|
-
if (fileTreeOpen) {
|
|
1903
|
-
// Load files if not cached
|
|
1904
|
-
if (workspaceFiles.length === 0) {
|
|
1905
|
-
fetchWorkspaceFiles();
|
|
1906
|
-
} else {
|
|
1907
|
-
renderFileTree(workspaceFiles);
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// Close file tree
|
|
1913
|
-
function closeFileTree() {
|
|
1914
|
-
fileTreeOpen = false;
|
|
1915
|
-
fileTreePanel.classList.remove('open');
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
// Fetch workspace files from server
|
|
1919
|
-
async function fetchWorkspaceFiles() {
|
|
1920
|
-
if (!selectedCascadeId) return;
|
|
1921
|
-
|
|
1922
|
-
fileTree.innerHTML = '<div class="file-tree-loading"><div class="loading-spinner"></div>Loading files...</div>';
|
|
1923
|
-
|
|
1924
|
-
try {
|
|
1925
|
-
const r = await fetch(`/files/${selectedCascadeId}`);
|
|
1926
|
-
if (!r.ok) throw new Error('Failed to fetch files');
|
|
1927
|
-
|
|
1928
|
-
const data = await r.json();
|
|
1929
|
-
workspaceFiles = data.files || [];
|
|
1930
|
-
console.log(`[FileTree] Loaded ${workspaceFiles.length} files`);
|
|
1931
|
-
renderFileTree(workspaceFiles);
|
|
1932
|
-
} catch (e) {
|
|
1933
|
-
console.error('Failed to fetch files:', e);
|
|
1934
|
-
fileTree.innerHTML = '<div class="file-tree-empty">Failed to load files</div>';
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
// Build tree structure from flat file list
|
|
1939
|
-
function buildFileTree(files) {
|
|
1940
|
-
const root = { folders: {}, files: [] };
|
|
1941
|
-
|
|
1942
|
-
files.forEach(file => {
|
|
1943
|
-
const parts = file.path.split(/[/\\]/);
|
|
1944
|
-
let current = root;
|
|
1945
|
-
|
|
1946
|
-
// Navigate/create folder structure
|
|
1947
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1948
|
-
const folderName = parts[i];
|
|
1949
|
-
if (!current.folders[folderName]) {
|
|
1950
|
-
current.folders[folderName] = { folders: {}, files: [] };
|
|
1951
|
-
}
|
|
1952
|
-
current = current.folders[folderName];
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
// Add file to current folder
|
|
1956
|
-
current.files.push({ name: parts[parts.length - 1], path: file.path });
|
|
1957
|
-
});
|
|
1958
|
-
|
|
1959
|
-
return root;
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
// Get file icon based on extension
|
|
1963
|
-
function getFileIcon(name) {
|
|
1964
|
-
const ext = name.split('.').pop()?.toLowerCase();
|
|
1965
|
-
const iconMap = {
|
|
1966
|
-
'js': 'codicon-file-code', 'jsx': 'codicon-file-code',
|
|
1967
|
-
'ts': 'codicon-file-code', 'tsx': 'codicon-file-code',
|
|
1968
|
-
'html': 'codicon-file-code', 'css': 'codicon-file-code',
|
|
1969
|
-
'json': 'codicon-json', 'md': 'codicon-markdown',
|
|
1970
|
-
'py': 'codicon-file-code', 'java': 'codicon-file-code',
|
|
1971
|
-
'go': 'codicon-file-code', 'rs': 'codicon-file-code',
|
|
1972
|
-
'cob': 'codicon-file-code', 'cbl': 'codicon-file-code',
|
|
1973
|
-
'sql': 'codicon-database', 'txt': 'codicon-file'
|
|
1974
|
-
};
|
|
1975
|
-
return iconMap[ext] || 'codicon-file';
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
// Render tree node recursively
|
|
1979
|
-
function renderTreeNode(node, path = '', depth = 0) {
|
|
1980
|
-
let html = '';
|
|
1981
|
-
|
|
1982
|
-
// Sort folders first, then files
|
|
1983
|
-
const folderNames = Object.keys(node.folders).sort();
|
|
1984
|
-
const sortedFiles = [...node.files].sort((a, b) => a.name.localeCompare(b.name));
|
|
1985
|
-
|
|
1986
|
-
// Render folders
|
|
1987
|
-
folderNames.forEach(folderName => {
|
|
1988
|
-
const folderPath = path ? `${path}/${folderName}` : folderName;
|
|
1989
|
-
const isExpanded = expandedFolders.has(folderPath);
|
|
1990
|
-
|
|
1991
|
-
html += `
|
|
1992
|
-
<div class="file-tree-folder" data-path="${escapeHtml(folderPath)}">
|
|
1993
|
-
<div class="file-tree-folder-header" data-folder="${escapeHtml(folderPath)}">
|
|
1994
|
-
<span class="file-tree-folder-icon codicon codicon-chevron-right ${isExpanded ? 'expanded' : ''}"></span>
|
|
1995
|
-
<span class="codicon codicon-folder${isExpanded ? '-opened' : ''}" style="color: #dcb67a;"></span>
|
|
1996
|
-
<span class="file-tree-folder-name">${escapeHtml(folderName)}</span>
|
|
1997
|
-
</div>
|
|
1998
|
-
<div class="file-tree-folder-contents ${isExpanded ? 'expanded' : ''}">
|
|
1999
|
-
${renderTreeNode(node.folders[folderName], folderPath, depth + 1)}
|
|
2000
|
-
</div>
|
|
2001
|
-
</div>
|
|
2002
|
-
`;
|
|
2003
|
-
});
|
|
2004
|
-
|
|
2005
|
-
// Render files
|
|
2006
|
-
sortedFiles.forEach(file => {
|
|
2007
|
-
html += `
|
|
2008
|
-
<div class="file-tree-file" data-path="${escapeHtml(file.path)}">
|
|
2009
|
-
<span class="file-tree-file-icon codicon ${getFileIcon(file.name)}"></span>
|
|
2010
|
-
<span class="file-tree-file-name">${escapeHtml(file.name)}</span>
|
|
2011
|
-
</div>
|
|
2012
|
-
`;
|
|
2013
|
-
});
|
|
2014
|
-
|
|
2015
|
-
return html;
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
// Render file tree
|
|
2019
|
-
function renderFileTree(files) {
|
|
2020
|
-
if (files.length === 0) {
|
|
2021
|
-
fileTree.innerHTML = '<div class="file-tree-empty">No files found</div>';
|
|
2022
|
-
return;
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
|
-
// Build and render tree structure
|
|
2026
|
-
const tree = buildFileTree(files);
|
|
2027
|
-
fileTree.innerHTML = renderTreeNode(tree);
|
|
2028
|
-
|
|
2029
|
-
// Add click handlers for folders
|
|
2030
|
-
fileTree.querySelectorAll('.file-tree-folder-header').forEach(header => {
|
|
2031
|
-
header.onclick = (e) => {
|
|
2032
|
-
e.stopPropagation();
|
|
2033
|
-
const folderPath = header.dataset.folder;
|
|
2034
|
-
const folder = header.closest('.file-tree-folder');
|
|
2035
|
-
const contents = folder.querySelector('.file-tree-folder-contents');
|
|
2036
|
-
const chevron = header.querySelector('.file-tree-folder-icon');
|
|
2037
|
-
const folderIcon = header.querySelector('.codicon-folder, .codicon-folder-opened');
|
|
2038
|
-
|
|
2039
|
-
if (expandedFolders.has(folderPath)) {
|
|
2040
|
-
expandedFolders.delete(folderPath);
|
|
2041
|
-
contents.classList.remove('expanded');
|
|
2042
|
-
chevron.classList.remove('expanded');
|
|
2043
|
-
folderIcon.classList.remove('codicon-folder-opened');
|
|
2044
|
-
folderIcon.classList.add('codicon-folder');
|
|
2045
|
-
} else {
|
|
2046
|
-
expandedFolders.add(folderPath);
|
|
2047
|
-
contents.classList.add('expanded');
|
|
2048
|
-
chevron.classList.add('expanded');
|
|
2049
|
-
folderIcon.classList.remove('codicon-folder');
|
|
2050
|
-
folderIcon.classList.add('codicon-folder-opened');
|
|
2051
|
-
}
|
|
2052
|
-
};
|
|
2053
|
-
});
|
|
2054
|
-
|
|
2055
|
-
// Add click handlers for files
|
|
2056
|
-
fileTree.querySelectorAll('.file-tree-file').forEach(item => {
|
|
2057
|
-
item.onclick = () => {
|
|
2058
|
-
const path = item.dataset.path;
|
|
2059
|
-
closeFileTree();
|
|
2060
|
-
openFileInEditor(path);
|
|
2061
|
-
};
|
|
2062
|
-
});
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
// Setup file tree event listeners
|
|
2066
|
-
function setupFileTree() {
|
|
2067
|
-
const explorerBtn = document.getElementById('editorExplorerBtn');
|
|
2068
|
-
|
|
2069
|
-
// Explorer button click toggles file tree
|
|
2070
|
-
explorerBtn.onclick = (e) => {
|
|
2071
|
-
e.stopPropagation();
|
|
2072
|
-
toggleFileTree();
|
|
2073
|
-
};
|
|
2074
|
-
|
|
2075
|
-
// Close button
|
|
2076
|
-
fileTreeClose.onclick = (e) => {
|
|
2077
|
-
e.stopPropagation();
|
|
2078
|
-
closeFileTree();
|
|
2079
|
-
};
|
|
2080
|
-
|
|
2081
|
-
// Prevent clicks inside panel from closing it
|
|
2082
|
-
fileTreePanel.onclick = (e) => {
|
|
2083
|
-
e.stopPropagation();
|
|
2084
|
-
};
|
|
2085
|
-
|
|
2086
|
-
// Close on escape key
|
|
2087
|
-
document.addEventListener('keydown', (e) => {
|
|
2088
|
-
if (e.key === 'Escape' && fileTreeOpen) {
|
|
2089
|
-
closeFileTree();
|
|
2090
|
-
}
|
|
2091
|
-
});
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
// Initialize file tree
|
|
2095
|
-
setupFileTree();
|
|
2096
|
-
|
|
2097
|
-
// ==================== Editor Search ====================
|
|
2098
|
-
|
|
2099
|
-
function openEditorSearch() {
|
|
2100
|
-
editorSearchBar.classList.add('open');
|
|
2101
|
-
editorSearchInput.focus();
|
|
2102
|
-
editorSearchInput.select();
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
function closeEditorSearch() {
|
|
2106
|
-
editorSearchBar.classList.remove('open');
|
|
2107
|
-
editorSearchInput.value = '';
|
|
2108
|
-
searchMatches = [];
|
|
2109
|
-
currentMatchIndex = -1;
|
|
2110
|
-
editorSearchCount.textContent = '';
|
|
2111
|
-
clearSearchHighlights();
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
function clearSearchHighlights() {
|
|
2115
|
-
// Restore original HTML for lines that were modified
|
|
2116
|
-
const modifiedLines = panels.editor.content.querySelectorAll('.editor-line-code[data-original-html]');
|
|
2117
|
-
modifiedLines.forEach(el => {
|
|
2118
|
-
el.innerHTML = el.dataset.originalHtml;
|
|
2119
|
-
delete el.dataset.originalHtml;
|
|
2120
|
-
});
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
function performSearch(query) {
|
|
2124
|
-
clearSearchHighlights();
|
|
2125
|
-
searchMatches = [];
|
|
2126
|
-
currentMatchIndex = -1;
|
|
2127
|
-
|
|
2128
|
-
if (!query || query.length < 1) {
|
|
2129
|
-
editorSearchCount.textContent = '';
|
|
2130
|
-
return;
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
const codeElements = panels.editor.content.querySelectorAll('.editor-line-code');
|
|
2134
|
-
const queryLower = query.toLowerCase();
|
|
2135
|
-
|
|
2136
|
-
codeElements.forEach((lineEl, lineIndex) => {
|
|
2137
|
-
const text = lineEl.textContent;
|
|
2138
|
-
const textLower = text.toLowerCase();
|
|
2139
|
-
let startIndex = 0;
|
|
2140
|
-
let matchIndex;
|
|
2141
|
-
|
|
2142
|
-
while ((matchIndex = textLower.indexOf(queryLower, startIndex)) !== -1) {
|
|
2143
|
-
searchMatches.push({
|
|
2144
|
-
lineIndex,
|
|
2145
|
-
lineEl,
|
|
2146
|
-
startIndex: matchIndex,
|
|
2147
|
-
endIndex: matchIndex + query.length
|
|
2148
|
-
});
|
|
2149
|
-
startIndex = matchIndex + 1;
|
|
2150
|
-
}
|
|
2151
|
-
});
|
|
2152
|
-
|
|
2153
|
-
if (searchMatches.length > 0) {
|
|
2154
|
-
highlightMatches(query);
|
|
2155
|
-
currentMatchIndex = 0;
|
|
2156
|
-
scrollToMatch(0);
|
|
2157
|
-
updateSearchCount();
|
|
2158
|
-
} else {
|
|
2159
|
-
editorSearchCount.textContent = 'No results';
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
function highlightMatches(query) {
|
|
2164
|
-
const codeElements = panels.editor.content.querySelectorAll('.editor-line-code');
|
|
2165
|
-
const queryLower = query.toLowerCase();
|
|
2166
|
-
|
|
2167
|
-
codeElements.forEach(lineEl => {
|
|
2168
|
-
const text = lineEl.textContent;
|
|
2169
|
-
const textLower = text.toLowerCase();
|
|
2170
|
-
|
|
2171
|
-
if (textLower.includes(queryLower)) {
|
|
2172
|
-
// Store original HTML to restore later
|
|
2173
|
-
if (!lineEl.dataset.originalHtml) {
|
|
2174
|
-
lineEl.dataset.originalHtml = lineEl.innerHTML;
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
// Work with text content, find all match positions
|
|
2178
|
-
const matches = [];
|
|
2179
|
-
let idx = 0;
|
|
2180
|
-
while ((idx = textLower.indexOf(queryLower, idx)) !== -1) {
|
|
2181
|
-
matches.push({ start: idx, end: idx + query.length });
|
|
2182
|
-
idx++;
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
if (matches.length > 0) {
|
|
2186
|
-
// Rebuild the line with highlights
|
|
2187
|
-
let result = '';
|
|
2188
|
-
let lastEnd = 0;
|
|
2189
|
-
|
|
2190
|
-
matches.forEach(match => {
|
|
2191
|
-
// Add text before match (escaped)
|
|
2192
|
-
result += escapeHtml(text.slice(lastEnd, match.start));
|
|
2193
|
-
// Add highlighted match
|
|
2194
|
-
result += `<span class="search-highlight">${escapeHtml(text.slice(match.start, match.end))}</span>`;
|
|
2195
|
-
lastEnd = match.end;
|
|
2196
|
-
});
|
|
2197
|
-
|
|
2198
|
-
// Add remaining text
|
|
2199
|
-
result += escapeHtml(text.slice(lastEnd));
|
|
2200
|
-
|
|
2201
|
-
lineEl.innerHTML = result;
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
});
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
function scrollToMatch(index) {
|
|
2208
|
-
const highlights = panels.editor.content.querySelectorAll('.search-highlight');
|
|
2209
|
-
|
|
2210
|
-
// Remove current class from all
|
|
2211
|
-
highlights.forEach(el => el.classList.remove('current'));
|
|
2212
|
-
|
|
2213
|
-
if (highlights[index]) {
|
|
2214
|
-
highlights[index].classList.add('current');
|
|
2215
|
-
|
|
2216
|
-
// Get the scroll container and the highlight element
|
|
2217
|
-
const container = panels.editor.content;
|
|
2218
|
-
const element = highlights[index];
|
|
2219
|
-
|
|
2220
|
-
// Get the line element (parent of the highlight)
|
|
2221
|
-
const lineEl = element.closest('.editor-line');
|
|
2222
|
-
if (lineEl) {
|
|
2223
|
-
// Calculate scroll position to center the line
|
|
2224
|
-
const containerRect = container.getBoundingClientRect();
|
|
2225
|
-
const lineRect = lineEl.getBoundingClientRect();
|
|
2226
|
-
const scrollTop = container.scrollTop + (lineRect.top - containerRect.top) - (containerRect.height / 2) + (lineRect.height / 2);
|
|
2227
|
-
|
|
2228
|
-
container.scrollTo({
|
|
2229
|
-
top: Math.max(0, scrollTop),
|
|
2230
|
-
behavior: 'smooth'
|
|
2231
|
-
});
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
|
|
2236
|
-
function updateSearchCount() {
|
|
2237
|
-
if (searchMatches.length > 0) {
|
|
2238
|
-
editorSearchCount.textContent = `${currentMatchIndex + 1}/${searchMatches.length}`;
|
|
2239
|
-
} else {
|
|
2240
|
-
editorSearchCount.textContent = 'No results';
|
|
1846
|
+
showEmptyState('editor', 'codicon-file-code', 'Failed to load file.');
|
|
2241
1847
|
}
|
|
2242
1848
|
}
|
|
2243
1849
|
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
}
|
|
1850
|
+
// =============================================================================
|
|
1851
|
+
// Event Listeners Setup
|
|
1852
|
+
// =============================================================================
|
|
1853
|
+
$('editorExplorerBtn').onclick = (e) => { e.stopPropagation(); toggleFileTree(); };
|
|
1854
|
+
fileTreeDropdown.onclick = (e) => { e.stopPropagation(); };
|
|
2250
1855
|
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
updateSearchCount();
|
|
2256
|
-
}
|
|
1856
|
+
// Close dropdown when clicking outside
|
|
1857
|
+
document.addEventListener('click', (e) => {
|
|
1858
|
+
if (fileTreeOpen && !e.target.closest('.editor-explorer-wrapper')) closeFileTree();
|
|
1859
|
+
});
|
|
2257
1860
|
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
editorSearchClose.onclick = () => closeEditorSearch();
|
|
2262
|
-
editorSearchNext.onclick = () => goToNextMatch();
|
|
2263
|
-
editorSearchPrev.onclick = () => goToPrevMatch();
|
|
2264
|
-
|
|
2265
|
-
editorSearchInput.oninput = (e) => {
|
|
2266
|
-
performSearch(e.target.value);
|
|
2267
|
-
};
|
|
2268
|
-
|
|
2269
|
-
editorSearchInput.onkeydown = (e) => {
|
|
2270
|
-
if (e.key === 'Enter') {
|
|
2271
|
-
e.preventDefault();
|
|
2272
|
-
if (e.shiftKey) {
|
|
2273
|
-
goToPrevMatch();
|
|
2274
|
-
} else {
|
|
2275
|
-
goToNextMatch();
|
|
2276
|
-
}
|
|
2277
|
-
} else if (e.key === 'Escape') {
|
|
2278
|
-
closeEditorSearch();
|
|
2279
|
-
}
|
|
2280
|
-
};
|
|
2281
|
-
|
|
2282
|
-
// Ctrl+F / Cmd+F to open search
|
|
2283
|
-
document.addEventListener('keydown', (e) => {
|
|
2284
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'f' && activePanel === 'editor') {
|
|
2285
|
-
e.preventDefault();
|
|
2286
|
-
openEditorSearch();
|
|
2287
|
-
}
|
|
2288
|
-
});
|
|
2289
|
-
}
|
|
1861
|
+
document.addEventListener('keydown', (e) => {
|
|
1862
|
+
if (e.key === 'Escape' && fileTreeOpen) closeFileTree();
|
|
1863
|
+
});
|
|
2290
1864
|
|
|
2291
|
-
setupEditorSearch();
|
|
2292
|
-
|
|
2293
|
-
// Visibility change handler
|
|
2294
1865
|
document.addEventListener('visibilitychange', () => {
|
|
2295
|
-
if (document.visibilityState === 'visible' && (!ws || ws.readyState !== WebSocket.OPEN))
|
|
2296
|
-
connect();
|
|
2297
|
-
}
|
|
1866
|
+
if (document.visibilityState === 'visible' && (!ws || ws.readyState !== WebSocket.OPEN)) connect();
|
|
2298
1867
|
});
|
|
2299
|
-
|
|
1868
|
+
|
|
2300
1869
|
// Initialize
|
|
2301
1870
|
connect();
|
|
2302
1871
|
</script>
|