@autocode-cli/autocode 0.0.43 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -5
- package/LICENSE.fr.md +203 -0
- package/README.md +48 -15
- package/dist/cli/parser.d.ts +1 -1
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +5 -4
- package/dist/cli/parser.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/server/api.d.ts.map +1 -1
- package/dist/server/api.js +47 -7
- package/dist/server/api.js.map +1 -1
- package/dist/server/dashboard/index.d.ts +10 -0
- package/dist/server/dashboard/index.d.ts.map +1 -0
- package/dist/server/dashboard/index.js +16 -0
- package/dist/server/dashboard/index.js.map +1 -0
- package/dist/server/dashboard/pages/column-edit.d.ts +8 -0
- package/dist/server/dashboard/pages/column-edit.d.ts.map +1 -0
- package/dist/server/dashboard/pages/column-edit.js +303 -0
- package/dist/server/dashboard/pages/column-edit.js.map +1 -0
- package/dist/server/dashboard/pages/column-prompt.d.ts +8 -0
- package/dist/server/dashboard/pages/column-prompt.d.ts.map +1 -0
- package/dist/server/dashboard/pages/column-prompt.js +228 -0
- package/dist/server/dashboard/pages/column-prompt.js.map +1 -0
- package/dist/server/dashboard/pages/column-terminal.d.ts +8 -0
- package/dist/server/dashboard/pages/column-terminal.d.ts.map +1 -0
- package/dist/server/dashboard/pages/column-terminal.js +529 -0
- package/dist/server/dashboard/pages/column-terminal.js.map +1 -0
- package/dist/server/dashboard/pages/index.d.ts +12 -0
- package/dist/server/dashboard/pages/index.d.ts.map +1 -0
- package/dist/server/dashboard/pages/index.js +12 -0
- package/dist/server/dashboard/pages/index.js.map +1 -0
- package/dist/server/dashboard/pages/main-dashboard.d.ts +11 -0
- package/dist/server/dashboard/pages/main-dashboard.d.ts.map +1 -0
- package/dist/server/dashboard/pages/main-dashboard.js +209 -0
- package/dist/server/dashboard/pages/main-dashboard.js.map +1 -0
- package/dist/server/dashboard/pages/shared.d.ts +8 -0
- package/dist/server/dashboard/pages/shared.d.ts.map +1 -0
- package/dist/server/dashboard/pages/shared.js +58 -0
- package/dist/server/dashboard/pages/shared.js.map +1 -0
- package/dist/server/dashboard/pages/ticket-view.d.ts +8 -0
- package/dist/server/dashboard/pages/ticket-view.d.ts.map +1 -0
- package/dist/server/dashboard/pages/ticket-view.js +1364 -0
- package/dist/server/dashboard/pages/ticket-view.js.map +1 -0
- package/dist/server/dashboard/scripts/index.d.ts +11 -0
- package/dist/server/dashboard/scripts/index.d.ts.map +1 -0
- package/dist/server/dashboard/scripts/index.js +1325 -0
- package/dist/server/dashboard/scripts/index.js.map +1 -0
- package/dist/server/dashboard/styles/base.d.ts +5 -0
- package/dist/server/dashboard/styles/base.d.ts.map +1 -0
- package/dist/server/dashboard/styles/base.js +110 -0
- package/dist/server/dashboard/styles/base.js.map +1 -0
- package/dist/server/dashboard/styles/board.d.ts +5 -0
- package/dist/server/dashboard/styles/board.d.ts.map +1 -0
- package/dist/server/dashboard/styles/board.js +168 -0
- package/dist/server/dashboard/styles/board.js.map +1 -0
- package/dist/server/dashboard/styles/comments.d.ts +5 -0
- package/dist/server/dashboard/styles/comments.d.ts.map +1 -0
- package/dist/server/dashboard/styles/comments.js +249 -0
- package/dist/server/dashboard/styles/comments.js.map +1 -0
- package/dist/server/dashboard/styles/components.d.ts +5 -0
- package/dist/server/dashboard/styles/components.d.ts.map +1 -0
- package/dist/server/dashboard/styles/components.js +190 -0
- package/dist/server/dashboard/styles/components.js.map +1 -0
- package/dist/server/dashboard/styles/footer.d.ts +5 -0
- package/dist/server/dashboard/styles/footer.d.ts.map +1 -0
- package/dist/server/dashboard/styles/footer.js +32 -0
- package/dist/server/dashboard/styles/footer.js.map +1 -0
- package/dist/server/dashboard/styles/index.d.ts +8 -0
- package/dist/server/dashboard/styles/index.d.ts.map +1 -0
- package/dist/server/dashboard/styles/index.js +27 -0
- package/dist/server/dashboard/styles/index.js.map +1 -0
- package/dist/server/dashboard/styles/logs.d.ts +5 -0
- package/dist/server/dashboard/styles/logs.d.ts.map +1 -0
- package/dist/server/dashboard/styles/logs.js +89 -0
- package/dist/server/dashboard/styles/logs.js.map +1 -0
- package/dist/server/dashboard/styles/notifications.d.ts +5 -0
- package/dist/server/dashboard/styles/notifications.d.ts.map +1 -0
- package/dist/server/dashboard/styles/notifications.js +51 -0
- package/dist/server/dashboard/styles/notifications.js.map +1 -0
- package/dist/server/dashboard/styles/variables.d.ts +5 -0
- package/dist/server/dashboard/styles/variables.d.ts.map +1 -0
- package/dist/server/dashboard/styles/variables.js +29 -0
- package/dist/server/dashboard/styles/variables.js.map +1 -0
- package/dist/server/dashboard/utils.d.ts +8 -0
- package/dist/server/dashboard/utils.d.ts.map +1 -0
- package/dist/server/dashboard/utils.js +14 -0
- package/dist/server/dashboard/utils.js.map +1 -0
- package/dist/server/dashboard.d.ts +5 -21
- package/dist/server/dashboard.d.ts.map +1 -1
- package/dist/server/dashboard.js +5 -4843
- package/dist/server/dashboard.js.map +1 -1
- package/dist/server/websocket.d.ts +12 -0
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +19 -0
- package/dist/server/websocket.js.map +1 -1
- package/dist/services/claude.d.ts.map +1 -1
- package/dist/services/claude.js +4 -1
- package/dist/services/claude.js.map +1 -1
- package/dist/utils/config.d.ts +1 -1
- package/dist/utils/config.js +1 -1
- package/dist/utils/version-check.d.ts +26 -0
- package/dist/utils/version-check.d.ts.map +1 -0
- package/dist/utils/version-check.js +234 -0
- package/dist/utils/version-check.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,1364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticket view page generator
|
|
3
|
+
*/
|
|
4
|
+
import { getConfig } from '../../../utils/config.js';
|
|
5
|
+
import { getTicket } from '../../../core/ticket.js';
|
|
6
|
+
import { getColumns } from '../../../core/column.js';
|
|
7
|
+
import { escapeHtml } from '../utils.js';
|
|
8
|
+
import { generate404Page } from './shared.js';
|
|
9
|
+
/**
|
|
10
|
+
* Generate the ticket view page
|
|
11
|
+
*/
|
|
12
|
+
export function generateTicketViewPage(ticketKey, lang) {
|
|
13
|
+
const config = getConfig();
|
|
14
|
+
const ticket = getTicket(config.root, ticketKey);
|
|
15
|
+
const columns = getColumns();
|
|
16
|
+
if (!ticket) {
|
|
17
|
+
return generate404Page(ticketKey, lang);
|
|
18
|
+
}
|
|
19
|
+
const currentColumn = columns.find(c => c.slug === ticket.column_slug);
|
|
20
|
+
const ticketData = JSON.stringify(ticket);
|
|
21
|
+
const columnsData = JSON.stringify(columns);
|
|
22
|
+
return `<!DOCTYPE html>
|
|
23
|
+
<html lang="${lang}">
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="UTF-8">
|
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
27
|
+
<title>${escapeHtml(ticket.title)} - ${ticketKey} - AutoCode</title>
|
|
28
|
+
<style>
|
|
29
|
+
:root {
|
|
30
|
+
--bg: #0a0a0f;
|
|
31
|
+
--bg-secondary: #12121a;
|
|
32
|
+
--bg-tertiary: #1a1a24;
|
|
33
|
+
--text: #f1f5f9;
|
|
34
|
+
--muted: #94a3b8;
|
|
35
|
+
--border: #2a2a3a;
|
|
36
|
+
--accent: #6366f1;
|
|
37
|
+
--blue: #4dabf7;
|
|
38
|
+
--green: #4ade80;
|
|
39
|
+
--yellow: #facc15;
|
|
40
|
+
--orange: #fb923c;
|
|
41
|
+
--red: #f87171;
|
|
42
|
+
--purple: #a78bfa;
|
|
43
|
+
}
|
|
44
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
45
|
+
body {
|
|
46
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
47
|
+
background: var(--bg);
|
|
48
|
+
color: var(--text);
|
|
49
|
+
min-height: 100vh;
|
|
50
|
+
}
|
|
51
|
+
.header {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: 24px;
|
|
55
|
+
padding: 16px 24px;
|
|
56
|
+
background: var(--bg-secondary);
|
|
57
|
+
border-bottom: 1px solid var(--border);
|
|
58
|
+
position: sticky;
|
|
59
|
+
top: 0;
|
|
60
|
+
z-index: 100;
|
|
61
|
+
}
|
|
62
|
+
.back-btn {
|
|
63
|
+
color: var(--muted);
|
|
64
|
+
text-decoration: none;
|
|
65
|
+
font-size: 14px;
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
gap: 8px;
|
|
69
|
+
}
|
|
70
|
+
.back-btn:hover { color: var(--text); }
|
|
71
|
+
.ticket-header-info {
|
|
72
|
+
flex: 1;
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: 16px;
|
|
76
|
+
}
|
|
77
|
+
.ticket-key {
|
|
78
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
79
|
+
font-size: 12px;
|
|
80
|
+
color: var(--accent);
|
|
81
|
+
background: rgba(99, 102, 241, 0.15);
|
|
82
|
+
padding: 4px 10px;
|
|
83
|
+
border-radius: 4px;
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
}
|
|
86
|
+
.ticket-title {
|
|
87
|
+
font-size: 18px;
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
}
|
|
90
|
+
.lang-selector {
|
|
91
|
+
display: flex;
|
|
92
|
+
gap: 4px;
|
|
93
|
+
}
|
|
94
|
+
.lang-btn {
|
|
95
|
+
background: transparent;
|
|
96
|
+
border: 1px solid var(--border);
|
|
97
|
+
color: var(--muted);
|
|
98
|
+
padding: 6px 12px;
|
|
99
|
+
border-radius: 4px;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
font-size: 12px;
|
|
102
|
+
font-weight: 500;
|
|
103
|
+
}
|
|
104
|
+
.lang-btn:hover { border-color: var(--accent); color: var(--text); }
|
|
105
|
+
.lang-btn.active { background: var(--accent); border-color: var(--accent); color: white; }
|
|
106
|
+
.ticket-title-input {
|
|
107
|
+
flex: 1;
|
|
108
|
+
background: transparent;
|
|
109
|
+
border: 1px solid transparent;
|
|
110
|
+
color: var(--text);
|
|
111
|
+
font-size: 18px;
|
|
112
|
+
font-weight: 600;
|
|
113
|
+
padding: 4px 8px;
|
|
114
|
+
border-radius: 4px;
|
|
115
|
+
font-family: inherit;
|
|
116
|
+
}
|
|
117
|
+
.ticket-title-input:hover { border-color: var(--border); }
|
|
118
|
+
.ticket-title-input:focus {
|
|
119
|
+
outline: none;
|
|
120
|
+
border-color: var(--accent);
|
|
121
|
+
background: var(--bg-tertiary);
|
|
122
|
+
}
|
|
123
|
+
.section-title {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
justify-content: space-between;
|
|
127
|
+
}
|
|
128
|
+
.btn-edit-toggle {
|
|
129
|
+
background: transparent;
|
|
130
|
+
border: none;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
font-size: 14px;
|
|
133
|
+
opacity: 0.5;
|
|
134
|
+
transition: opacity 0.2s;
|
|
135
|
+
}
|
|
136
|
+
.btn-edit-toggle:hover { opacity: 1; }
|
|
137
|
+
.btn-edit-toggle.active { opacity: 1; }
|
|
138
|
+
.description-view {
|
|
139
|
+
line-height: 1.7;
|
|
140
|
+
color: var(--text);
|
|
141
|
+
}
|
|
142
|
+
.description-view p { margin-bottom: 12px; }
|
|
143
|
+
.description-view h1, .description-view h2, .description-view h3, .description-view h4 {
|
|
144
|
+
margin: 16px 0 8px;
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
}
|
|
147
|
+
.description-view code {
|
|
148
|
+
background: var(--bg-tertiary);
|
|
149
|
+
padding: 2px 6px;
|
|
150
|
+
border-radius: 4px;
|
|
151
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
152
|
+
font-size: 13px;
|
|
153
|
+
}
|
|
154
|
+
.description-view pre {
|
|
155
|
+
background: var(--bg-tertiary);
|
|
156
|
+
padding: 12px;
|
|
157
|
+
border-radius: 6px;
|
|
158
|
+
overflow-x: auto;
|
|
159
|
+
}
|
|
160
|
+
.description-view ul, .description-view ol {
|
|
161
|
+
margin: 8px 0;
|
|
162
|
+
padding-left: 24px;
|
|
163
|
+
}
|
|
164
|
+
.description-view a { color: var(--accent); }
|
|
165
|
+
.description-edit {
|
|
166
|
+
width: 100%;
|
|
167
|
+
min-height: 200px;
|
|
168
|
+
padding: 12px;
|
|
169
|
+
background: var(--bg-tertiary);
|
|
170
|
+
border: 1px solid var(--border);
|
|
171
|
+
border-radius: 8px;
|
|
172
|
+
color: var(--text);
|
|
173
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
174
|
+
font-size: 13px;
|
|
175
|
+
line-height: 1.6;
|
|
176
|
+
resize: vertical;
|
|
177
|
+
}
|
|
178
|
+
.description-edit:focus {
|
|
179
|
+
outline: none;
|
|
180
|
+
border-color: var(--accent);
|
|
181
|
+
}
|
|
182
|
+
.main-content {
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
gap: 24px;
|
|
186
|
+
padding: 24px 48px;
|
|
187
|
+
}
|
|
188
|
+
.ticket-details { display: flex; flex-direction: column; gap: 24px; }
|
|
189
|
+
.ticket-bottom {
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-direction: column;
|
|
192
|
+
gap: 24px;
|
|
193
|
+
}
|
|
194
|
+
.section {
|
|
195
|
+
background: var(--bg-secondary);
|
|
196
|
+
border: 1px solid var(--border);
|
|
197
|
+
border-radius: 12px;
|
|
198
|
+
padding: 20px;
|
|
199
|
+
}
|
|
200
|
+
.section-title {
|
|
201
|
+
font-size: 12px;
|
|
202
|
+
font-weight: 600;
|
|
203
|
+
text-transform: uppercase;
|
|
204
|
+
letter-spacing: 0.5px;
|
|
205
|
+
color: var(--muted);
|
|
206
|
+
margin-bottom: 16px;
|
|
207
|
+
}
|
|
208
|
+
.ticket-meta {
|
|
209
|
+
display: flex;
|
|
210
|
+
flex-wrap: wrap;
|
|
211
|
+
gap: 12px;
|
|
212
|
+
}
|
|
213
|
+
.meta-badge {
|
|
214
|
+
font-size: 11px;
|
|
215
|
+
font-weight: 600;
|
|
216
|
+
text-transform: uppercase;
|
|
217
|
+
letter-spacing: 0.5px;
|
|
218
|
+
padding: 5px 12px;
|
|
219
|
+
border-radius: 6px;
|
|
220
|
+
}
|
|
221
|
+
.priority-P0 { background: rgba(248, 113, 113, 0.2); color: var(--red); }
|
|
222
|
+
.priority-P1 { background: rgba(251, 146, 60, 0.2); color: var(--orange); }
|
|
223
|
+
.priority-P2 { background: rgba(250, 204, 21, 0.2); color: var(--yellow); }
|
|
224
|
+
.priority-P3 { background: rgba(148, 163, 184, 0.2); color: var(--muted); }
|
|
225
|
+
.column-badge { background: rgba(77, 171, 247, 0.15); color: var(--blue); }
|
|
226
|
+
.semver-badge { background: rgba(167, 139, 250, 0.15); color: var(--purple); }
|
|
227
|
+
.labels-list {
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-wrap: wrap;
|
|
230
|
+
gap: 8px;
|
|
231
|
+
}
|
|
232
|
+
.label-tag {
|
|
233
|
+
font-size: 11px;
|
|
234
|
+
padding: 4px 10px;
|
|
235
|
+
border-radius: 12px;
|
|
236
|
+
background: var(--bg-tertiary);
|
|
237
|
+
color: var(--text);
|
|
238
|
+
border: 1px solid var(--border);
|
|
239
|
+
}
|
|
240
|
+
.description-content {
|
|
241
|
+
line-height: 1.7;
|
|
242
|
+
color: var(--text);
|
|
243
|
+
}
|
|
244
|
+
.description-content p { margin-bottom: 12px; }
|
|
245
|
+
.description-content code {
|
|
246
|
+
background: var(--bg-tertiary);
|
|
247
|
+
padding: 2px 6px;
|
|
248
|
+
border-radius: 4px;
|
|
249
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
250
|
+
font-size: 13px;
|
|
251
|
+
}
|
|
252
|
+
.criteria-list { list-style: none; }
|
|
253
|
+
.criteria-item {
|
|
254
|
+
padding: 12px 16px;
|
|
255
|
+
background: var(--bg-tertiary);
|
|
256
|
+
border-radius: 8px;
|
|
257
|
+
margin-bottom: 8px;
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: flex-start;
|
|
260
|
+
gap: 12px;
|
|
261
|
+
}
|
|
262
|
+
.criteria-item::before {
|
|
263
|
+
content: '☐';
|
|
264
|
+
color: var(--muted);
|
|
265
|
+
}
|
|
266
|
+
.history-list { list-style: none; }
|
|
267
|
+
.history-item {
|
|
268
|
+
padding: 12px 0;
|
|
269
|
+
border-bottom: 1px solid var(--border);
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: 12px;
|
|
273
|
+
font-size: 13px;
|
|
274
|
+
}
|
|
275
|
+
.history-item:last-child { border-bottom: none; }
|
|
276
|
+
.history-action {
|
|
277
|
+
font-weight: 600;
|
|
278
|
+
text-transform: capitalize;
|
|
279
|
+
}
|
|
280
|
+
.history-from, .history-to {
|
|
281
|
+
padding: 2px 8px;
|
|
282
|
+
background: var(--bg-tertiary);
|
|
283
|
+
border-radius: 4px;
|
|
284
|
+
font-size: 11px;
|
|
285
|
+
}
|
|
286
|
+
.history-date { color: var(--muted); margin-left: auto; font-size: 12px; }
|
|
287
|
+
.btn-prompt {
|
|
288
|
+
background: none;
|
|
289
|
+
border: none;
|
|
290
|
+
cursor: pointer;
|
|
291
|
+
font-size: 14px;
|
|
292
|
+
padding: 2px 6px;
|
|
293
|
+
opacity: 0.6;
|
|
294
|
+
transition: opacity 0.2s;
|
|
295
|
+
}
|
|
296
|
+
.btn-prompt:hover { opacity: 1; }
|
|
297
|
+
.prompt-modal {
|
|
298
|
+
display: none;
|
|
299
|
+
position: fixed;
|
|
300
|
+
top: 0;
|
|
301
|
+
left: 0;
|
|
302
|
+
right: 0;
|
|
303
|
+
bottom: 0;
|
|
304
|
+
background: rgba(0, 0, 0, 0.7);
|
|
305
|
+
z-index: 1000;
|
|
306
|
+
align-items: center;
|
|
307
|
+
justify-content: center;
|
|
308
|
+
}
|
|
309
|
+
.prompt-modal.visible { display: flex; }
|
|
310
|
+
.prompt-modal-content {
|
|
311
|
+
background: var(--bg-primary);
|
|
312
|
+
border-radius: 12px;
|
|
313
|
+
max-width: 900px;
|
|
314
|
+
max-height: 80vh;
|
|
315
|
+
width: 90%;
|
|
316
|
+
display: flex;
|
|
317
|
+
flex-direction: column;
|
|
318
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
|
319
|
+
}
|
|
320
|
+
.prompt-modal-header {
|
|
321
|
+
display: flex;
|
|
322
|
+
justify-content: space-between;
|
|
323
|
+
align-items: center;
|
|
324
|
+
padding: 16px 20px;
|
|
325
|
+
border-bottom: 1px solid var(--border);
|
|
326
|
+
}
|
|
327
|
+
.prompt-modal-header h3 { margin: 0; }
|
|
328
|
+
.prompt-modal-close {
|
|
329
|
+
background: none;
|
|
330
|
+
border: none;
|
|
331
|
+
font-size: 24px;
|
|
332
|
+
cursor: pointer;
|
|
333
|
+
color: var(--muted);
|
|
334
|
+
}
|
|
335
|
+
.prompt-modal-close:hover { color: var(--text); }
|
|
336
|
+
.prompt-modal-body {
|
|
337
|
+
padding: 20px;
|
|
338
|
+
overflow-y: auto;
|
|
339
|
+
flex: 1;
|
|
340
|
+
}
|
|
341
|
+
.prompt-modal-body pre {
|
|
342
|
+
white-space: pre-wrap;
|
|
343
|
+
word-wrap: break-word;
|
|
344
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
345
|
+
font-size: 13px;
|
|
346
|
+
line-height: 1.5;
|
|
347
|
+
margin: 0;
|
|
348
|
+
background: var(--bg-secondary);
|
|
349
|
+
padding: 16px;
|
|
350
|
+
border-radius: 8px;
|
|
351
|
+
}
|
|
352
|
+
.log-container {
|
|
353
|
+
max-height: 70vh;
|
|
354
|
+
overflow-y: auto;
|
|
355
|
+
display: flex;
|
|
356
|
+
flex-direction: column;
|
|
357
|
+
gap: 8px;
|
|
358
|
+
}
|
|
359
|
+
.actions-bar {
|
|
360
|
+
display: flex;
|
|
361
|
+
gap: 12px;
|
|
362
|
+
padding: 16px 0;
|
|
363
|
+
border-top: 1px solid var(--border);
|
|
364
|
+
margin-top: 8px;
|
|
365
|
+
}
|
|
366
|
+
.btn {
|
|
367
|
+
padding: 12px 20px;
|
|
368
|
+
border-radius: 8px;
|
|
369
|
+
font-weight: 500;
|
|
370
|
+
font-size: 14px;
|
|
371
|
+
cursor: pointer;
|
|
372
|
+
border: none;
|
|
373
|
+
text-align: center;
|
|
374
|
+
text-decoration: none;
|
|
375
|
+
}
|
|
376
|
+
.btn-primary { background: var(--accent); color: white; }
|
|
377
|
+
.btn-primary:hover { opacity: 0.9; }
|
|
378
|
+
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
379
|
+
.btn-secondary { background: var(--bg-tertiary); color: var(--text); border: 1px solid var(--border); }
|
|
380
|
+
.btn-secondary:hover { border-color: var(--accent); }
|
|
381
|
+
.btn-danger { background: rgba(248, 113, 113, 0.15); color: var(--red); border: 1px solid transparent; }
|
|
382
|
+
.btn-danger:hover { border-color: var(--red); }
|
|
383
|
+
.comments-list {
|
|
384
|
+
display: flex;
|
|
385
|
+
flex-direction: column;
|
|
386
|
+
gap: 12px;
|
|
387
|
+
margin-bottom: 16px;
|
|
388
|
+
}
|
|
389
|
+
.comment {
|
|
390
|
+
padding: 16px;
|
|
391
|
+
background: var(--bg-tertiary);
|
|
392
|
+
border-radius: 8px;
|
|
393
|
+
border-left: 3px solid var(--border);
|
|
394
|
+
transition: all 0.2s ease;
|
|
395
|
+
}
|
|
396
|
+
.comment:hover {
|
|
397
|
+
border-left-color: var(--blue);
|
|
398
|
+
}
|
|
399
|
+
.comment-meta {
|
|
400
|
+
display: flex;
|
|
401
|
+
align-items: center;
|
|
402
|
+
flex-wrap: wrap;
|
|
403
|
+
gap: 8px;
|
|
404
|
+
cursor: pointer;
|
|
405
|
+
user-select: none;
|
|
406
|
+
}
|
|
407
|
+
.comment-meta::before {
|
|
408
|
+
content: '▶';
|
|
409
|
+
font-size: 10px;
|
|
410
|
+
color: var(--muted);
|
|
411
|
+
transition: transform 0.2s ease;
|
|
412
|
+
}
|
|
413
|
+
.comment.expanded .comment-meta::before {
|
|
414
|
+
transform: rotate(90deg);
|
|
415
|
+
}
|
|
416
|
+
.comment-source {
|
|
417
|
+
font-size: 10px;
|
|
418
|
+
padding: 3px 8px;
|
|
419
|
+
border-radius: 4px;
|
|
420
|
+
text-transform: uppercase;
|
|
421
|
+
font-weight: 600;
|
|
422
|
+
letter-spacing: 0.5px;
|
|
423
|
+
}
|
|
424
|
+
.comment-source.user { background: #3b82f6; color: white; }
|
|
425
|
+
.comment-source.claude { background: #8b5cf6; color: white; }
|
|
426
|
+
.comment-column {
|
|
427
|
+
font-size: 10px;
|
|
428
|
+
font-weight: 600;
|
|
429
|
+
text-transform: uppercase;
|
|
430
|
+
letter-spacing: 0.5px;
|
|
431
|
+
color: var(--blue);
|
|
432
|
+
padding: 3px 8px;
|
|
433
|
+
background: rgba(77,171,247,0.15);
|
|
434
|
+
border-radius: 4px;
|
|
435
|
+
}
|
|
436
|
+
.comment-date {
|
|
437
|
+
font-size: 11px;
|
|
438
|
+
color: var(--muted);
|
|
439
|
+
}
|
|
440
|
+
.comment-text {
|
|
441
|
+
font-size: 14px;
|
|
442
|
+
line-height: 1.6;
|
|
443
|
+
color: var(--text);
|
|
444
|
+
max-height: 0;
|
|
445
|
+
overflow: hidden;
|
|
446
|
+
transition: max-height 0.3s ease, margin-top 0.3s ease, padding-top 0.3s ease;
|
|
447
|
+
margin-top: 0;
|
|
448
|
+
padding-top: 0;
|
|
449
|
+
}
|
|
450
|
+
.comment.expanded .comment-text {
|
|
451
|
+
max-height: 500px;
|
|
452
|
+
margin-top: 12px;
|
|
453
|
+
padding-top: 12px;
|
|
454
|
+
border-top: 1px solid var(--border);
|
|
455
|
+
}
|
|
456
|
+
.comment-text code {
|
|
457
|
+
background: var(--bg);
|
|
458
|
+
padding: 2px 6px;
|
|
459
|
+
border-radius: 4px;
|
|
460
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
461
|
+
font-size: 12px;
|
|
462
|
+
}
|
|
463
|
+
.add-comment {
|
|
464
|
+
display: flex;
|
|
465
|
+
flex-direction: column;
|
|
466
|
+
gap: 8px;
|
|
467
|
+
}
|
|
468
|
+
.add-comment textarea {
|
|
469
|
+
width: 100%;
|
|
470
|
+
min-height: 80px;
|
|
471
|
+
padding: 12px;
|
|
472
|
+
background: var(--bg-tertiary);
|
|
473
|
+
border: 1px solid var(--border);
|
|
474
|
+
border-radius: 8px;
|
|
475
|
+
color: var(--text);
|
|
476
|
+
font-family: inherit;
|
|
477
|
+
font-size: 14px;
|
|
478
|
+
resize: vertical;
|
|
479
|
+
}
|
|
480
|
+
.add-comment textarea:focus {
|
|
481
|
+
outline: none;
|
|
482
|
+
border-color: var(--accent);
|
|
483
|
+
}
|
|
484
|
+
.btn-comment {
|
|
485
|
+
align-self: flex-end;
|
|
486
|
+
padding: 8px 16px;
|
|
487
|
+
background: var(--accent);
|
|
488
|
+
color: white;
|
|
489
|
+
border: none;
|
|
490
|
+
border-radius: 6px;
|
|
491
|
+
cursor: pointer;
|
|
492
|
+
font-size: 13px;
|
|
493
|
+
font-weight: 500;
|
|
494
|
+
}
|
|
495
|
+
.btn-comment:hover { opacity: 0.9; }
|
|
496
|
+
.btn-comment:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
497
|
+
.no-comments {
|
|
498
|
+
text-align: center;
|
|
499
|
+
color: var(--muted);
|
|
500
|
+
padding: 24px;
|
|
501
|
+
font-size: 14px;
|
|
502
|
+
}
|
|
503
|
+
.notification {
|
|
504
|
+
position: fixed;
|
|
505
|
+
bottom: 24px;
|
|
506
|
+
right: 24px;
|
|
507
|
+
padding: 12px 20px;
|
|
508
|
+
background: var(--green);
|
|
509
|
+
color: #000;
|
|
510
|
+
border-radius: 8px;
|
|
511
|
+
font-weight: 500;
|
|
512
|
+
transform: translateY(100px);
|
|
513
|
+
opacity: 0;
|
|
514
|
+
transition: all 0.3s ease;
|
|
515
|
+
z-index: 1000;
|
|
516
|
+
}
|
|
517
|
+
.notification.show { transform: translateY(0); opacity: 1; }
|
|
518
|
+
.notification.error { background: var(--red); color: white; }
|
|
519
|
+
|
|
520
|
+
/* Claude Terminal */
|
|
521
|
+
.claude-section .section-title {
|
|
522
|
+
display: flex;
|
|
523
|
+
align-items: center;
|
|
524
|
+
justify-content: space-between;
|
|
525
|
+
}
|
|
526
|
+
.claude-status {
|
|
527
|
+
font-size: 11px;
|
|
528
|
+
padding: 3px 10px;
|
|
529
|
+
border-radius: 12px;
|
|
530
|
+
background: var(--bg-tertiary);
|
|
531
|
+
color: var(--muted);
|
|
532
|
+
}
|
|
533
|
+
.claude-status.processing {
|
|
534
|
+
color: var(--yellow);
|
|
535
|
+
animation: pulse 1s infinite;
|
|
536
|
+
}
|
|
537
|
+
.claude-status.success { color: var(--green); }
|
|
538
|
+
.claude-status.error { color: var(--red); }
|
|
539
|
+
.claude-log {
|
|
540
|
+
background: #0d1117;
|
|
541
|
+
border: 1px solid var(--border);
|
|
542
|
+
border-radius: 8px;
|
|
543
|
+
padding: 16px;
|
|
544
|
+
max-height: 400px;
|
|
545
|
+
overflow-y: auto;
|
|
546
|
+
font-family: 'SF Mono', Monaco, 'Consolas', monospace;
|
|
547
|
+
font-size: 12px;
|
|
548
|
+
line-height: 1.6;
|
|
549
|
+
white-space: pre-wrap;
|
|
550
|
+
word-break: break-word;
|
|
551
|
+
color: var(--text);
|
|
552
|
+
margin: 0;
|
|
553
|
+
margin-top: 12px;
|
|
554
|
+
}
|
|
555
|
+
/* Formatted log entries */
|
|
556
|
+
.log-entry { margin-bottom: 8px; padding: 8px 12px; border-radius: 4px; border-left: 3px solid transparent; flex-shrink: 0; }
|
|
557
|
+
.log-entry.timestamp { color: #8b949e; font-size: 11px; border-left-color: #484f58; background: transparent; padding: 4px 12px; }
|
|
558
|
+
.log-entry.system { color: #8b949e; border-left-color: #484f58; background: rgba(139,148,158,0.1); }
|
|
559
|
+
.log-entry.user { color: #58a6ff; border-left-color: #58a6ff; background: rgba(88,166,255,0.1); }
|
|
560
|
+
.log-entry.assistant { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
|
|
561
|
+
.log-entry.tool-call { color: #d2a8ff; border-left-color: #d2a8ff; background: rgba(210,168,255,0.1); padding: 12px; }
|
|
562
|
+
.log-entry.tool-result { color: #ffa657; border-left-color: #ffa657; background: rgba(255,166,87,0.1); }
|
|
563
|
+
.log-entry.error { color: #f85149; border-left-color: #f85149; background: rgba(248,81,73,0.1); }
|
|
564
|
+
.log-entry.success { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
|
|
565
|
+
.log-label { font-weight: 600; font-size: 11px; text-transform: uppercase; margin-bottom: 4px; display: block; opacity: 0.8; }
|
|
566
|
+
.log-content { white-space: pre-wrap; word-break: break-word; }
|
|
567
|
+
|
|
568
|
+
/* Code blocks with line numbers */
|
|
569
|
+
.log-code-block { background: #161b22; border-radius: 6px; overflow: hidden; margin: 8px 0; border: 1px solid #30363d; }
|
|
570
|
+
.log-code-header { background: #21262d; padding: 8px 12px; font-size: 12px; color: #8b949e; border-bottom: 1px solid #30363d; display: flex; align-items: center; gap: 8px; }
|
|
571
|
+
.log-code-header::before { content: ''; display: inline-block; width: 12px; height: 12px; background: #ffa657; border-radius: 50%; }
|
|
572
|
+
.log-code-content { padding: 12px; overflow-x: auto; font-family: 'Fira Code', 'SF Mono', Monaco, monospace; font-size: 12px; line-height: 1.5; max-height: 400px; overflow-y: auto; }
|
|
573
|
+
.log-code-line { display: flex; min-height: 20px; }
|
|
574
|
+
.log-line-number { color: #484f58; text-align: right; padding-right: 16px; user-select: none; min-width: 40px; flex-shrink: 0; }
|
|
575
|
+
.log-line-content { color: #c9d1d9; white-space: pre; }
|
|
576
|
+
|
|
577
|
+
/* Message cards */
|
|
578
|
+
.log-message-card { background: #161b22; border-radius: 8px; margin: 12px 0; overflow: hidden; border: 1px solid #30363d; flex-shrink: 0; }
|
|
579
|
+
.log-message-header { padding: 10px 14px; display: flex; align-items: center; gap: 8px; font-weight: 500; font-size: 13px; }
|
|
580
|
+
.log-message-header.assistant-header { background: linear-gradient(135deg, #238636 0%, #2ea043 100%); color: white; }
|
|
581
|
+
.log-message-header.user-header { background: linear-gradient(135deg, #1f6feb 0%, #388bfd 100%); color: white; }
|
|
582
|
+
.log-message-header.tool-header { background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%); color: white; }
|
|
583
|
+
.log-message-header.result-header { background: linear-gradient(135deg, #f97316 0%, #fb923c 100%); color: white; }
|
|
584
|
+
.log-message-body { padding: 14px; border-top: 1px solid #30363d; color: #c9d1d9; white-space: pre-wrap; word-break: break-word; }
|
|
585
|
+
|
|
586
|
+
/* Tool badges */
|
|
587
|
+
.log-tool-badge { display: inline-flex; align-items: center; gap: 6px; background: rgba(139,92,246,0.2); color: #a78bfa; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
|
|
588
|
+
.log-tool-input { background: #0d1117; border-radius: 4px; padding: 8px 12px; margin-top: 8px; font-family: 'Fira Code', monospace; font-size: 11px; color: #8b949e; max-height: 150px; overflow: auto; white-space: pre-wrap; }
|
|
589
|
+
|
|
590
|
+
/* Collapsible raw JSON */
|
|
591
|
+
.log-raw-details { margin: 8px 0; }
|
|
592
|
+
.log-raw-summary { cursor: pointer; color: #8b949e; font-size: 12px; padding: 8px; background: rgba(139,148,158,0.1); border-radius: 4px; }
|
|
593
|
+
.log-raw-summary:hover { background: rgba(139,148,158,0.2); }
|
|
594
|
+
.log-raw-json { background: #0d1117; border-radius: 4px; padding: 12px; margin-top: 8px; font-size: 11px; color: #8b949e; max-height: 200px; overflow: auto; white-space: pre-wrap; }
|
|
595
|
+
@keyframes pulse {
|
|
596
|
+
0%, 100% { opacity: 1; }
|
|
597
|
+
50% { opacity: 0.5; }
|
|
598
|
+
}
|
|
599
|
+
</style>
|
|
600
|
+
</head>
|
|
601
|
+
<body>
|
|
602
|
+
<header class="header">
|
|
603
|
+
<a href="/" class="back-btn">← Dashboard</a>
|
|
604
|
+
<div class="ticket-header-info">
|
|
605
|
+
<span class="ticket-key">${escapeHtml(ticketKey)}</span>
|
|
606
|
+
<input type="text" class="ticket-title-input" id="ticket-title" value="${escapeHtml(ticket.title)}" />
|
|
607
|
+
</div>
|
|
608
|
+
<div class="lang-selector" id="lang-selector">
|
|
609
|
+
<button class="lang-btn" data-lang="en">EN</button>
|
|
610
|
+
<button class="lang-btn" data-lang="fr">FR</button>
|
|
611
|
+
</div>
|
|
612
|
+
</header>
|
|
613
|
+
|
|
614
|
+
<main class="main-content">
|
|
615
|
+
<div class="ticket-details">
|
|
616
|
+
<!-- Meta info -->
|
|
617
|
+
<div class="section">
|
|
618
|
+
<div class="section-title" data-i18n="ticketView.meta">Meta</div>
|
|
619
|
+
<div class="ticket-meta">
|
|
620
|
+
<span class="meta-badge priority-${ticket.priority}">${ticket.priority}</span>
|
|
621
|
+
<span class="meta-badge column-badge">${escapeHtml(currentColumn?.name || ticket.column_slug)}</span>
|
|
622
|
+
<span class="meta-badge semver-badge">${ticket.semver}</span>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
|
|
626
|
+
<!-- Labels -->
|
|
627
|
+
${ticket.labels && ticket.labels.length > 0 ? `
|
|
628
|
+
<div class="section">
|
|
629
|
+
<div class="section-title" data-i18n="ticketView.labels">Labels</div>
|
|
630
|
+
<div class="labels-list">
|
|
631
|
+
${ticket.labels.map((label) => `<span class="label-tag">${escapeHtml(label)}</span>`).join('')}
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
` : ''}
|
|
635
|
+
|
|
636
|
+
<!-- Description -->
|
|
637
|
+
<div class="section">
|
|
638
|
+
<div class="section-title">
|
|
639
|
+
<span data-i18n="ticketView.description">Description</span>
|
|
640
|
+
<button class="btn-edit-toggle" id="btn-edit-description" onclick="toggleDescriptionEdit()">✏️</button>
|
|
641
|
+
</div>
|
|
642
|
+
<div class="description-view" id="description-view"></div>
|
|
643
|
+
<textarea class="description-edit" id="description-edit" style="display:none" placeholder="Description (Markdown)">${escapeHtml(ticket.description || '')}</textarea>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<!-- Acceptance Criteria -->
|
|
647
|
+
${ticket.acceptance_criteria && ticket.acceptance_criteria.length > 0 ? `
|
|
648
|
+
<div class="section">
|
|
649
|
+
<div class="section-title" data-i18n="ticketView.criteria">Acceptance Criteria</div>
|
|
650
|
+
<ul class="criteria-list">
|
|
651
|
+
${ticket.acceptance_criteria.map((c) => `<li class="criteria-item">${escapeHtml(c)}</li>`).join('')}
|
|
652
|
+
</ul>
|
|
653
|
+
</div>
|
|
654
|
+
` : ''}
|
|
655
|
+
|
|
656
|
+
<!-- History -->
|
|
657
|
+
${ticket.history && ticket.history.length > 0 ? `
|
|
658
|
+
<div class="section">
|
|
659
|
+
<div class="section-title" data-i18n="ticketView.history">History</div>
|
|
660
|
+
<ul class="history-list">
|
|
661
|
+
${ticket.history.map((h) => {
|
|
662
|
+
const date = new Date(h.at);
|
|
663
|
+
const dateStr = date.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
664
|
+
const showPromptBtn = h.to && h.action !== 'created' ? `<a href="/ticket/${ticketKey}/${escapeHtml(h.to)}/prompt" class="btn-prompt" title="View prompt">📜</a>` : '';
|
|
665
|
+
const showLogBtn = h.to && h.action !== 'created' ? `<a href="/ticket/${ticketKey}/${escapeHtml(h.to)}/terminal" class="btn-prompt" title="View terminal log">🖥️</a>` : '';
|
|
666
|
+
return `<li class="history-item">
|
|
667
|
+
<span class="history-action">${h.action}</span>
|
|
668
|
+
${h.from ? `<span class="history-from">${escapeHtml(h.from)}</span> →` : ''}
|
|
669
|
+
<span class="history-to">${escapeHtml(h.to)}</span>
|
|
670
|
+
${showPromptBtn}
|
|
671
|
+
${showLogBtn}
|
|
672
|
+
<span class="history-date">${dateStr}</span>
|
|
673
|
+
</li>`;
|
|
674
|
+
}).join('')}
|
|
675
|
+
</ul>
|
|
676
|
+
</div>
|
|
677
|
+
` : ''}
|
|
678
|
+
|
|
679
|
+
<!-- Actions bar -->
|
|
680
|
+
<div class="actions-bar">
|
|
681
|
+
<button class="btn btn-primary" id="btn-save" onclick="saveTicket()" data-i18n="ticketView.save">Save</button>
|
|
682
|
+
<button class="btn btn-secondary" onclick="advanceTicket()" data-i18n="ticketView.moveNext">Move to next column</button>
|
|
683
|
+
<button class="btn btn-danger" onclick="archiveTicket()" data-i18n="ticketView.archive">Archive</button>
|
|
684
|
+
</div>
|
|
685
|
+
</div>
|
|
686
|
+
|
|
687
|
+
<!-- Bottom section: Comments & Claude Terminal -->
|
|
688
|
+
<div class="ticket-bottom">
|
|
689
|
+
<!-- Comments -->
|
|
690
|
+
<div class="section comments-section">
|
|
691
|
+
<div class="section-title"><span data-i18n="ticketView.comments">Comments</span> (<span id="comments-count">${ticket.comments?.length || 0}</span>)</div>
|
|
692
|
+
<div class="comments-list" id="comments-list"></div>
|
|
693
|
+
<div class="add-comment">
|
|
694
|
+
<textarea id="new-comment" placeholder="Add a comment..." data-i18n-placeholder="ticketView.addComment"></textarea>
|
|
695
|
+
<button class="btn-comment" onclick="addComment()" data-i18n="btn.add">Add</button>
|
|
696
|
+
</div>
|
|
697
|
+
</div>
|
|
698
|
+
|
|
699
|
+
<!-- Claude Terminal -->
|
|
700
|
+
<div class="section claude-section" id="claude-section">
|
|
701
|
+
<div class="section-title">
|
|
702
|
+
<span data-i18n="ticketView.claudeTerminal">Claude Terminal</span>
|
|
703
|
+
<span class="claude-status" id="claude-status" data-i18n="status.waiting">Waiting</span>
|
|
704
|
+
</div>
|
|
705
|
+
<pre class="claude-log" id="claude-log"></pre>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
</main>
|
|
709
|
+
|
|
710
|
+
<div class="notification" id="notification"></div>
|
|
711
|
+
|
|
712
|
+
<!-- Prompt Modal -->
|
|
713
|
+
<div class="prompt-modal" id="prompt-modal" onclick="closePromptModal(event)">
|
|
714
|
+
<div class="prompt-modal-content" onclick="event.stopPropagation()">
|
|
715
|
+
<div class="prompt-modal-header">
|
|
716
|
+
<h3 id="prompt-modal-title">Prompt</h3>
|
|
717
|
+
<button class="prompt-modal-close" onclick="closePromptModal()">×</button>
|
|
718
|
+
</div>
|
|
719
|
+
<div class="prompt-modal-body">
|
|
720
|
+
<div id="prompt-modal-content" class="log-container"></div>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
|
|
725
|
+
<script>
|
|
726
|
+
const TICKET_KEY = '${ticketKey}';
|
|
727
|
+
const TICKET = ${ticketData};
|
|
728
|
+
const COLUMNS = ${columnsData};
|
|
729
|
+
const STORAGE_KEY = 'autocode-lang';
|
|
730
|
+
|
|
731
|
+
let currentLang = localStorage.getItem(STORAGE_KEY) || 'fr';
|
|
732
|
+
let currentComments = TICKET.comments || [];
|
|
733
|
+
|
|
734
|
+
const translations = {
|
|
735
|
+
en: {
|
|
736
|
+
'ticketView.meta': 'Meta',
|
|
737
|
+
'ticketView.labels': 'Labels',
|
|
738
|
+
'ticketView.description': 'Description',
|
|
739
|
+
'ticketView.criteria': 'Acceptance Criteria',
|
|
740
|
+
'ticketView.history': 'History',
|
|
741
|
+
'ticketView.actions': 'Actions',
|
|
742
|
+
'ticketView.save': 'Save',
|
|
743
|
+
'ticketView.moveNext': 'Move to next column',
|
|
744
|
+
'ticketView.archive': 'Archive',
|
|
745
|
+
'ticketView.confirmMove': 'Move this ticket to the next column?',
|
|
746
|
+
'ticketView.confirmArchive': 'Archive this ticket?',
|
|
747
|
+
'ticketView.comments': 'Comments',
|
|
748
|
+
'ticketView.addComment': 'Add a comment...',
|
|
749
|
+
'ticketView.noComments': 'No comments yet',
|
|
750
|
+
'ticketView.claudeTerminal': 'Claude Terminal',
|
|
751
|
+
'ticketView.noDescription': 'No description',
|
|
752
|
+
'ticketView.noLog': 'No log yet. Waiting for Claude processing...',
|
|
753
|
+
'ticketView.loadingPrompt': 'Loading prompt...',
|
|
754
|
+
'ticketView.promptError': 'Error',
|
|
755
|
+
'btn.add': 'Add',
|
|
756
|
+
'btn.sending': 'Sending...',
|
|
757
|
+
'btn.saving': 'Saving...',
|
|
758
|
+
'status.waiting': 'Waiting',
|
|
759
|
+
'status.processing': 'Processing...',
|
|
760
|
+
'status.completed': 'Completed',
|
|
761
|
+
'status.failed': 'Failed',
|
|
762
|
+
'notify.commentAdded': 'Comment added',
|
|
763
|
+
'notify.ticketAdvanced': 'Ticket advanced',
|
|
764
|
+
'notify.ticketArchived': 'Ticket archived',
|
|
765
|
+
'notify.ticketSaved': 'Ticket saved',
|
|
766
|
+
'notify.error': 'Error'
|
|
767
|
+
},
|
|
768
|
+
fr: {
|
|
769
|
+
'ticketView.meta': 'Méta',
|
|
770
|
+
'ticketView.labels': 'Labels',
|
|
771
|
+
'ticketView.description': 'Description',
|
|
772
|
+
'ticketView.criteria': 'Critères d\\'acceptation',
|
|
773
|
+
'ticketView.history': 'Historique',
|
|
774
|
+
'ticketView.actions': 'Actions',
|
|
775
|
+
'ticketView.save': 'Sauvegarder',
|
|
776
|
+
'ticketView.moveNext': 'Déplacer vers la colonne suivante',
|
|
777
|
+
'ticketView.archive': 'Archiver',
|
|
778
|
+
'ticketView.confirmMove': 'Déplacer ce ticket vers la colonne suivante ?',
|
|
779
|
+
'ticketView.confirmArchive': 'Archiver ce ticket ?',
|
|
780
|
+
'ticketView.comments': 'Commentaires',
|
|
781
|
+
'ticketView.addComment': 'Ajouter un commentaire...',
|
|
782
|
+
'ticketView.noComments': 'Aucun commentaire',
|
|
783
|
+
'ticketView.claudeTerminal': 'Terminal Claude',
|
|
784
|
+
'ticketView.noDescription': 'Aucune description',
|
|
785
|
+
'ticketView.noLog': 'Aucun log. En attente du traitement Claude...',
|
|
786
|
+
'ticketView.loadingPrompt': 'Chargement du prompt...',
|
|
787
|
+
'ticketView.promptError': 'Erreur',
|
|
788
|
+
'btn.add': 'Ajouter',
|
|
789
|
+
'btn.sending': 'Envoi...',
|
|
790
|
+
'btn.saving': 'Sauvegarde...',
|
|
791
|
+
'status.waiting': 'En attente',
|
|
792
|
+
'status.processing': 'En cours...',
|
|
793
|
+
'status.completed': 'Terminé',
|
|
794
|
+
'status.failed': 'Échec',
|
|
795
|
+
'notify.commentAdded': 'Commentaire ajouté',
|
|
796
|
+
'notify.ticketAdvanced': 'Ticket avancé',
|
|
797
|
+
'notify.ticketArchived': 'Ticket archivé',
|
|
798
|
+
'notify.ticketSaved': 'Ticket sauvegardé',
|
|
799
|
+
'notify.error': 'Erreur'
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
function t(key) {
|
|
804
|
+
return translations[currentLang]?.[key] || translations['en'][key] || key;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function escapeHtml(text) {
|
|
808
|
+
if (!text) return '';
|
|
809
|
+
const div = document.createElement('div');
|
|
810
|
+
div.textContent = text;
|
|
811
|
+
return div.innerHTML;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function renderMarkdown(text) {
|
|
815
|
+
if (!text) return '';
|
|
816
|
+
let html = escapeHtml(text);
|
|
817
|
+
html = html.replace(/^### (.+)$/gm, '<h4>$1</h4>');
|
|
818
|
+
html = html.replace(/^## (.+)$/gm, '<h3>$1</h3>');
|
|
819
|
+
html = html.replace(/^# (.+)$/gm, '<h2>$1</h2>');
|
|
820
|
+
html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
|
|
821
|
+
html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
|
|
822
|
+
html = html.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
|
|
823
|
+
html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
|
824
|
+
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
|
|
825
|
+
html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');
|
|
826
|
+
html = html.replace(/\\n/g, '<br>');
|
|
827
|
+
return html;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function updateLangUI() {
|
|
831
|
+
document.querySelectorAll('.lang-btn').forEach(btn => {
|
|
832
|
+
btn.classList.toggle('active', btn.dataset.lang === currentLang);
|
|
833
|
+
});
|
|
834
|
+
document.querySelectorAll('[data-i18n]').forEach(el => {
|
|
835
|
+
el.textContent = t(el.dataset.i18n);
|
|
836
|
+
});
|
|
837
|
+
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
|
|
838
|
+
el.placeholder = t(el.dataset.i18nPlaceholder);
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function renderComments() {
|
|
843
|
+
const list = document.getElementById('comments-list');
|
|
844
|
+
const count = document.getElementById('comments-count');
|
|
845
|
+
count.textContent = currentComments.length;
|
|
846
|
+
|
|
847
|
+
if (currentComments.length === 0) {
|
|
848
|
+
list.innerHTML = '<div class="no-comments">' + t('ticketView.noComments') + '</div>';
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const sorted = [...currentComments].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
|
853
|
+
list.innerHTML = sorted.map((comment, index) => {
|
|
854
|
+
const date = new Date(comment.created_at);
|
|
855
|
+
const dateStr = date.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
856
|
+
const source = comment.source || 'user';
|
|
857
|
+
const sourceBadge = source === 'claude'
|
|
858
|
+
? '<span class="comment-source claude">Claude</span>'
|
|
859
|
+
: '<span class="comment-source user">User</span>';
|
|
860
|
+
return '<div class="comment" id="comment-' + index + '">' +
|
|
861
|
+
'<div class="comment-meta" onclick="toggleComment(' + index + ')">' + sourceBadge + '<span class="comment-column">' + (comment.column || 'N/A') + '</span>' +
|
|
862
|
+
'<span class="comment-date">' + dateStr + '</span></div>' +
|
|
863
|
+
'<div class="comment-text">' + renderMarkdown(comment.text) + '</div></div>';
|
|
864
|
+
}).join('');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function toggleComment(index) {
|
|
868
|
+
const comments = document.querySelectorAll('.comment');
|
|
869
|
+
comments.forEach((comment, i) => {
|
|
870
|
+
if (i === index) {
|
|
871
|
+
comment.classList.toggle('expanded');
|
|
872
|
+
} else {
|
|
873
|
+
comment.classList.remove('expanded');
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
async function addComment() {
|
|
879
|
+
const textarea = document.getElementById('new-comment');
|
|
880
|
+
const text = textarea.value.trim();
|
|
881
|
+
if (!text) return;
|
|
882
|
+
|
|
883
|
+
const btn = document.querySelector('.btn-comment');
|
|
884
|
+
btn.disabled = true;
|
|
885
|
+
btn.textContent = t('btn.sending');
|
|
886
|
+
|
|
887
|
+
try {
|
|
888
|
+
const res = await fetch('/api/tickets/' + TICKET_KEY + '/comments', {
|
|
889
|
+
method: 'POST',
|
|
890
|
+
headers: { 'Content-Type': 'application/json' },
|
|
891
|
+
body: JSON.stringify({ text, source: 'user' })
|
|
892
|
+
});
|
|
893
|
+
const result = await res.json();
|
|
894
|
+
textarea.value = '';
|
|
895
|
+
if (result.success && result.data && result.data.comments) {
|
|
896
|
+
currentComments = result.data.comments;
|
|
897
|
+
renderComments();
|
|
898
|
+
}
|
|
899
|
+
showNotification(t('notify.commentAdded'));
|
|
900
|
+
} catch (e) {
|
|
901
|
+
showNotification(t('notify.error') + ': ' + e.message, true);
|
|
902
|
+
} finally {
|
|
903
|
+
btn.disabled = false;
|
|
904
|
+
btn.textContent = t('btn.add');
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
let isEditingDescription = false;
|
|
909
|
+
|
|
910
|
+
function renderDescription() {
|
|
911
|
+
const view = document.getElementById('description-view');
|
|
912
|
+
const edit = document.getElementById('description-edit');
|
|
913
|
+
const description = edit.value || '';
|
|
914
|
+
if (description) {
|
|
915
|
+
view.innerHTML = renderMarkdown(description);
|
|
916
|
+
} else {
|
|
917
|
+
view.innerHTML = '<span style="color: var(--muted)">' + t('ticketView.noDescription') + '</span>';
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function toggleDescriptionEdit() {
|
|
922
|
+
isEditingDescription = !isEditingDescription;
|
|
923
|
+
const view = document.getElementById('description-view');
|
|
924
|
+
const edit = document.getElementById('description-edit');
|
|
925
|
+
const btn = document.getElementById('btn-edit-description');
|
|
926
|
+
|
|
927
|
+
if (isEditingDescription) {
|
|
928
|
+
view.style.display = 'none';
|
|
929
|
+
edit.style.display = 'block';
|
|
930
|
+
btn.classList.add('active');
|
|
931
|
+
btn.textContent = '✓';
|
|
932
|
+
edit.focus();
|
|
933
|
+
} else {
|
|
934
|
+
view.style.display = 'block';
|
|
935
|
+
edit.style.display = 'none';
|
|
936
|
+
btn.classList.remove('active');
|
|
937
|
+
btn.textContent = '✏️';
|
|
938
|
+
renderDescription();
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
async function saveTicket() {
|
|
943
|
+
const btn = document.getElementById('btn-save');
|
|
944
|
+
btn.disabled = true;
|
|
945
|
+
btn.textContent = t('btn.saving');
|
|
946
|
+
|
|
947
|
+
try {
|
|
948
|
+
const res = await fetch('/api/tickets/' + TICKET_KEY, {
|
|
949
|
+
method: 'PATCH',
|
|
950
|
+
headers: { 'Content-Type': 'application/json' },
|
|
951
|
+
body: JSON.stringify({
|
|
952
|
+
title: document.getElementById('ticket-title').value,
|
|
953
|
+
description: document.getElementById('description-edit').value
|
|
954
|
+
})
|
|
955
|
+
});
|
|
956
|
+
const result = await res.json();
|
|
957
|
+
if (result.success) {
|
|
958
|
+
showNotification(t('notify.ticketSaved'));
|
|
959
|
+
if (isEditingDescription) {
|
|
960
|
+
toggleDescriptionEdit();
|
|
961
|
+
}
|
|
962
|
+
} else {
|
|
963
|
+
showNotification(t('notify.error') + ': ' + result.error, true);
|
|
964
|
+
}
|
|
965
|
+
} catch (e) {
|
|
966
|
+
showNotification(t('notify.error') + ': ' + e.message, true);
|
|
967
|
+
} finally {
|
|
968
|
+
btn.disabled = false;
|
|
969
|
+
btn.textContent = t('ticketView.save');
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
async function advanceTicket() {
|
|
974
|
+
if (!confirm(t('ticketView.confirmMove'))) return;
|
|
975
|
+
try {
|
|
976
|
+
const res = await fetch('/api/tickets/' + TICKET_KEY + '/next', {
|
|
977
|
+
method: 'POST',
|
|
978
|
+
headers: { 'Content-Type': 'application/json' },
|
|
979
|
+
body: JSON.stringify({ lang: currentLang })
|
|
980
|
+
});
|
|
981
|
+
const result = await res.json();
|
|
982
|
+
if (result.success) {
|
|
983
|
+
showNotification(t('notify.ticketAdvanced'));
|
|
984
|
+
setTimeout(() => location.reload(), 1000);
|
|
985
|
+
} else {
|
|
986
|
+
showNotification(t('notify.error') + ': ' + result.error, true);
|
|
987
|
+
}
|
|
988
|
+
} catch (e) {
|
|
989
|
+
showNotification(t('notify.error') + ': ' + e.message, true);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
async function archiveTicket() {
|
|
994
|
+
if (!confirm(t('ticketView.confirmArchive'))) return;
|
|
995
|
+
try {
|
|
996
|
+
const res = await fetch('/api/tickets/' + TICKET_KEY + '/archive', {
|
|
997
|
+
method: 'POST',
|
|
998
|
+
headers: { 'Content-Type': 'application/json' },
|
|
999
|
+
body: JSON.stringify({ lang: currentLang })
|
|
1000
|
+
});
|
|
1001
|
+
const result = await res.json();
|
|
1002
|
+
if (result.success) {
|
|
1003
|
+
showNotification(t('notify.ticketArchived'));
|
|
1004
|
+
setTimeout(() => location.href = '/', 1000);
|
|
1005
|
+
} else {
|
|
1006
|
+
showNotification(t('notify.error') + ': ' + result.error, true);
|
|
1007
|
+
}
|
|
1008
|
+
} catch (e) {
|
|
1009
|
+
showNotification(t('notify.error') + ': ' + e.message, true);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function showNotification(msg, isError) {
|
|
1014
|
+
const notification = document.getElementById('notification');
|
|
1015
|
+
notification.textContent = msg;
|
|
1016
|
+
notification.className = 'notification show' + (isError ? ' error' : '');
|
|
1017
|
+
setTimeout(() => notification.className = 'notification', 3000);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Language switcher
|
|
1021
|
+
document.querySelectorAll('.lang-btn').forEach(btn => {
|
|
1022
|
+
btn.addEventListener('click', () => {
|
|
1023
|
+
const newLang = btn.dataset.lang;
|
|
1024
|
+
if (newLang !== currentLang) {
|
|
1025
|
+
currentLang = newLang;
|
|
1026
|
+
localStorage.setItem(STORAGE_KEY, newLang);
|
|
1027
|
+
updateLangUI();
|
|
1028
|
+
renderComments();
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
// ========================================
|
|
1034
|
+
// WEBSOCKET & CLAUDE LOG
|
|
1035
|
+
// ========================================
|
|
1036
|
+
let ws;
|
|
1037
|
+
let logPollingInterval = null;
|
|
1038
|
+
|
|
1039
|
+
function connectWebSocket() {
|
|
1040
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1041
|
+
ws = new WebSocket(protocol + '//' + location.host + '/ws');
|
|
1042
|
+
|
|
1043
|
+
ws.onmessage = (event) => {
|
|
1044
|
+
try {
|
|
1045
|
+
const data = JSON.parse(event.data);
|
|
1046
|
+
switch (data.type) {
|
|
1047
|
+
case 'ticket_updated':
|
|
1048
|
+
if (data.key === TICKET_KEY) {
|
|
1049
|
+
location.reload();
|
|
1050
|
+
}
|
|
1051
|
+
break;
|
|
1052
|
+
case 'claude_start':
|
|
1053
|
+
if (data.ticket === TICKET_KEY) {
|
|
1054
|
+
onClaudeStart();
|
|
1055
|
+
}
|
|
1056
|
+
break;
|
|
1057
|
+
case 'claude_stream':
|
|
1058
|
+
if (data.ticket === TICKET_KEY) {
|
|
1059
|
+
fetchLog();
|
|
1060
|
+
}
|
|
1061
|
+
break;
|
|
1062
|
+
case 'claude_end':
|
|
1063
|
+
if (data.ticket === TICKET_KEY) {
|
|
1064
|
+
onClaudeEnd(data.success, data.duration);
|
|
1065
|
+
}
|
|
1066
|
+
break;
|
|
1067
|
+
}
|
|
1068
|
+
} catch (e) {
|
|
1069
|
+
console.error('WebSocket message error:', e);
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
ws.onclose = () => {
|
|
1074
|
+
setTimeout(connectWebSocket, 3000);
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function escapeHtml(str) {
|
|
1079
|
+
if (typeof str !== 'string') return '';
|
|
1080
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function formatCodeBlock(content, filename) {
|
|
1084
|
+
const lines = content.split(/\\\\n|\\n/);
|
|
1085
|
+
// Détecter le langage depuis le nom de fichier
|
|
1086
|
+
let lang = 'plaintext';
|
|
1087
|
+
if (filename) {
|
|
1088
|
+
const ext = filename.split('.').pop().toLowerCase();
|
|
1089
|
+
const langMap = {
|
|
1090
|
+
'js': 'javascript', 'ts': 'typescript', 'vue': 'html', 'jsx': 'javascript',
|
|
1091
|
+
'tsx': 'typescript', 'py': 'python', 'rb': 'ruby', 'java': 'java',
|
|
1092
|
+
'go': 'go', 'rs': 'rust', 'cpp': 'cpp', 'c': 'c', 'h': 'c',
|
|
1093
|
+
'css': 'css', 'scss': 'scss', 'html': 'html', 'json': 'json',
|
|
1094
|
+
'md': 'markdown', 'sh': 'bash', 'yml': 'yaml', 'yaml': 'yaml'
|
|
1095
|
+
};
|
|
1096
|
+
lang = langMap[ext] || 'plaintext';
|
|
1097
|
+
}
|
|
1098
|
+
// Extraire le code sans les numéros de ligne pour highlight.js
|
|
1099
|
+
let codeLines = [];
|
|
1100
|
+
for (const line of lines) {
|
|
1101
|
+
const match = line.match(/^\\s*\\d+[→|](.*)$/);
|
|
1102
|
+
if (match) {
|
|
1103
|
+
codeLines.push(match[1]);
|
|
1104
|
+
} else if (line.trim()) {
|
|
1105
|
+
codeLines.push(line);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const codeContent = codeLines.join('\\n');
|
|
1109
|
+
const lineCount = codeLines.length;
|
|
1110
|
+
const preview = filename || (lineCount + ' lignes');
|
|
1111
|
+
|
|
1112
|
+
let html = '<details class="log-code-block">';
|
|
1113
|
+
html += '<summary class="log-code-header"><span class="code-lang">' + lang + '</span> ' + escapeHtml(preview) + '</summary>';
|
|
1114
|
+
html += '<pre><code class="language-' + lang + '">' + escapeHtml(codeContent) + '</code></pre>';
|
|
1115
|
+
html += '</details>';
|
|
1116
|
+
return html;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function formatLogContent(rawContent) {
|
|
1120
|
+
if (!rawContent) return '';
|
|
1121
|
+
// Supprimer les balises <system-reminder> et leur contenu
|
|
1122
|
+
let cleanedContent = rawContent.replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/gi, '');
|
|
1123
|
+
const lines = cleanedContent.split('\\n');
|
|
1124
|
+
const entries = [];
|
|
1125
|
+
let textBuffer = [];
|
|
1126
|
+
|
|
1127
|
+
function flushTextBuffer() {
|
|
1128
|
+
if (textBuffer.length > 0) {
|
|
1129
|
+
const text = textBuffer.join('\\n');
|
|
1130
|
+
entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(text) + '</div></div>');
|
|
1131
|
+
textBuffer = [];
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
for (const line of lines) {
|
|
1136
|
+
// Garder les lignes vides dans le buffer pour les paragraphes markdown
|
|
1137
|
+
if (!line.trim()) {
|
|
1138
|
+
if (textBuffer.length > 0) textBuffer.push('');
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
const timestampMatch = line.match(/^\\[(\\d{4}-\\d{2}-\\d{2}T[^\\]]+)\\]\\s*(.*)$/);
|
|
1143
|
+
if (timestampMatch) {
|
|
1144
|
+
flushTextBuffer();
|
|
1145
|
+
const date = new Date(timestampMatch[1]);
|
|
1146
|
+
const timeStr = date.toLocaleTimeString();
|
|
1147
|
+
entries.push('<div class="log-entry timestamp">' + timeStr + ' - ' + escapeHtml(timestampMatch[2]) + '</div>');
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// [RAW] - Parse avec regex (pas JSON.parse car les lignes sont coupées)
|
|
1152
|
+
if (line.startsWith('[RAW] ')) {
|
|
1153
|
+
const raw = line.slice(6);
|
|
1154
|
+
|
|
1155
|
+
// Ignorer les lignes de continuation (ne commencent pas par {)
|
|
1156
|
+
if (!raw.startsWith('{')) {
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Extraire tool_result content
|
|
1161
|
+
if (raw.includes('"type":"tool_result"')) {
|
|
1162
|
+
const contentStart = raw.indexOf('"content":"');
|
|
1163
|
+
if (contentStart !== -1) {
|
|
1164
|
+
// Extraire le contenu après "content":"
|
|
1165
|
+
let content = raw.slice(contentStart + 11);
|
|
1166
|
+
// Enlever le reste du JSON (approximatif car tronqué)
|
|
1167
|
+
const endQuote = content.lastIndexOf('"');
|
|
1168
|
+
if (endQuote > 0) content = content.slice(0, endQuote);
|
|
1169
|
+
// Décoder les échappements
|
|
1170
|
+
const decoded = content.replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"').replace(/\\\\t/g, '\\t');
|
|
1171
|
+
// Vérifier si c'est du code avec numéros de ligne
|
|
1172
|
+
if (/^\\s*\\d+[→|]/.test(decoded)) {
|
|
1173
|
+
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body">' + formatCodeBlock(decoded) + '</div></div>');
|
|
1174
|
+
} else {
|
|
1175
|
+
// Résultat texte simple (liste de fichiers, etc.)
|
|
1176
|
+
const lines = decoded.split('\\n').slice(0, 20); // Limiter à 20 lignes
|
|
1177
|
+
const truncated = decoded.split('\\n').length > 20 ? '<div style="color:#8b949e;font-style:italic">... (truncated)</div>' : '';
|
|
1178
|
+
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body" style="font-family:monospace;font-size:12px;white-space:pre-wrap;max-height:200px;overflow-y:auto">' + escapeHtml(lines.join('\\n')) + truncated + '</div></div>');
|
|
1179
|
+
}
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Extraire message assistant texte
|
|
1185
|
+
const textMatch = raw.match(/"type":"text","text":"([^"]+)"/);
|
|
1186
|
+
if (textMatch) {
|
|
1187
|
+
const decoded = textMatch[1].replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"');
|
|
1188
|
+
entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(decoded) + '</div></div>');
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Extraire tool_use
|
|
1193
|
+
const toolMatch = raw.match(/"type":"tool_use"[^}]*"name":"([^"]+)"/);
|
|
1194
|
+
if (toolMatch) {
|
|
1195
|
+
entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(toolMatch[1]) + '</span></div>');
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Ignorer les autres RAW
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (line.startsWith('[SYSTEM]')) {
|
|
1204
|
+
flushTextBuffer();
|
|
1205
|
+
entries.push('<div class="log-entry system"><span class="log-label">System</span><div class="log-content">' + escapeHtml(line.slice(9)) + '</div></div>');
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
if (line.startsWith('[ASSISTANT]')) {
|
|
1209
|
+
flushTextBuffer();
|
|
1210
|
+
entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(line.slice(12)) + '</div></div>');
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
if (line.startsWith('[TOOL]')) {
|
|
1214
|
+
flushTextBuffer();
|
|
1215
|
+
entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(line.slice(7)) + '</span></div>');
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (line.startsWith('[RESULT]')) {
|
|
1219
|
+
flushTextBuffer();
|
|
1220
|
+
const content = line.slice(9);
|
|
1221
|
+
if (/^\\s*\\d+[→|]/.test(content)) {
|
|
1222
|
+
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + formatCodeBlock(content) + '</div></div>');
|
|
1223
|
+
} else {
|
|
1224
|
+
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + escapeHtml(content.slice(0, 1000)) + (content.length > 1000 ? '...' : '') + '</div></div>');
|
|
1225
|
+
}
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
if (line.startsWith('[ERROR]')) {
|
|
1229
|
+
flushTextBuffer();
|
|
1230
|
+
entries.push('<div class="log-entry error"><span class="log-label">Error</span><div class="log-content">' + escapeHtml(line.slice(8)) + '</div></div>');
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Ligne de texte brut - ajouter au buffer pour regrouper
|
|
1235
|
+
textBuffer.push(line);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// Flush le buffer restant
|
|
1239
|
+
flushTextBuffer();
|
|
1240
|
+
return entries.join('');
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function startLogPolling() {
|
|
1244
|
+
stopLogPolling();
|
|
1245
|
+
logPollingInterval = setInterval(fetchLog, 1000);
|
|
1246
|
+
fetchLog();
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function stopLogPolling() {
|
|
1250
|
+
if (logPollingInterval) {
|
|
1251
|
+
clearInterval(logPollingInterval);
|
|
1252
|
+
logPollingInterval = null;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
async function fetchLog() {
|
|
1257
|
+
try {
|
|
1258
|
+
const res = await fetch('/api/tickets/' + TICKET_KEY + '/log');
|
|
1259
|
+
const json = await res.json();
|
|
1260
|
+
const log = document.getElementById('claude-log');
|
|
1261
|
+
|
|
1262
|
+
if (json.success && json.data && (json.data.exists || json.data.content)) {
|
|
1263
|
+
log.innerHTML = formatLogContent(json.data.content || '');
|
|
1264
|
+
log.scrollTop = log.scrollHeight;
|
|
1265
|
+
} else {
|
|
1266
|
+
log.innerHTML = '<div class="log-entry system">' + t('ticketView.noLog') + '</div>';
|
|
1267
|
+
}
|
|
1268
|
+
} catch (e) {
|
|
1269
|
+
console.error('Log fetch error:', e);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function onClaudeStart() {
|
|
1274
|
+
const status = document.getElementById('claude-status');
|
|
1275
|
+
status.className = 'claude-status processing';
|
|
1276
|
+
status.textContent = t('status.processing');
|
|
1277
|
+
startLogPolling();
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function onClaudeEnd(success, duration) {
|
|
1281
|
+
stopLogPolling();
|
|
1282
|
+
fetchLog();
|
|
1283
|
+
const status = document.getElementById('claude-status');
|
|
1284
|
+
if (success) {
|
|
1285
|
+
status.className = 'claude-status success';
|
|
1286
|
+
status.textContent = t('status.completed') + ' (' + (duration / 1000).toFixed(1) + 's)';
|
|
1287
|
+
} else {
|
|
1288
|
+
status.className = 'claude-status error';
|
|
1289
|
+
status.textContent = t('status.failed');
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// Prompt Modal
|
|
1294
|
+
async function showPrompt(columnSlug) {
|
|
1295
|
+
const modal = document.getElementById('prompt-modal');
|
|
1296
|
+
const title = document.getElementById('prompt-modal-title');
|
|
1297
|
+
const content = document.getElementById('prompt-modal-content');
|
|
1298
|
+
|
|
1299
|
+
title.textContent = t('ticketView.loadingPrompt');
|
|
1300
|
+
content.textContent = '';
|
|
1301
|
+
modal.classList.add('visible');
|
|
1302
|
+
|
|
1303
|
+
try {
|
|
1304
|
+
const res = await fetch('/api/tickets/' + TICKET_KEY + '/prompt/' + columnSlug);
|
|
1305
|
+
const json = await res.json();
|
|
1306
|
+
if (json.success) {
|
|
1307
|
+
title.textContent = 'Prompt → ' + json.data.column;
|
|
1308
|
+
content.textContent = json.data.prompt;
|
|
1309
|
+
} else {
|
|
1310
|
+
title.textContent = t('ticketView.promptError');
|
|
1311
|
+
content.textContent = json.error || 'Error loading prompt';
|
|
1312
|
+
}
|
|
1313
|
+
} catch (e) {
|
|
1314
|
+
title.textContent = t('ticketView.promptError');
|
|
1315
|
+
content.textContent = e.message;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
function closePromptModal(event) {
|
|
1320
|
+
if (event && event.target !== event.currentTarget) return;
|
|
1321
|
+
document.getElementById('prompt-modal').classList.remove('visible');
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// Log Modal (reuses prompt modal)
|
|
1325
|
+
async function showLog(columnSlug) {
|
|
1326
|
+
const modal = document.getElementById('prompt-modal');
|
|
1327
|
+
const title = document.getElementById('prompt-modal-title');
|
|
1328
|
+
const content = document.getElementById('prompt-modal-content');
|
|
1329
|
+
|
|
1330
|
+
title.textContent = t('ticketView.loadingLog') || 'Loading log...';
|
|
1331
|
+
content.textContent = '';
|
|
1332
|
+
modal.classList.add('visible');
|
|
1333
|
+
|
|
1334
|
+
try {
|
|
1335
|
+
const res = await fetch('/api/tickets/' + TICKET_KEY + '/log/' + columnSlug);
|
|
1336
|
+
const json = await res.json();
|
|
1337
|
+
if (json.success) {
|
|
1338
|
+
title.textContent = 'Terminal → ' + columnSlug;
|
|
1339
|
+
if (json.data.content) {
|
|
1340
|
+
content.innerHTML = formatLogContent(json.data.content);
|
|
1341
|
+
} else {
|
|
1342
|
+
content.textContent = t('ticketView.noLog') || 'No log available';
|
|
1343
|
+
}
|
|
1344
|
+
} else {
|
|
1345
|
+
title.textContent = t('ticketView.logError') || 'Log Error';
|
|
1346
|
+
content.textContent = json.error || 'Error loading log';
|
|
1347
|
+
}
|
|
1348
|
+
} catch (e) {
|
|
1349
|
+
title.textContent = t('ticketView.logError') || 'Log Error';
|
|
1350
|
+
content.textContent = e.message;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Init
|
|
1355
|
+
updateLangUI();
|
|
1356
|
+
renderDescription();
|
|
1357
|
+
renderComments();
|
|
1358
|
+
connectWebSocket();
|
|
1359
|
+
fetchLog();
|
|
1360
|
+
</script>
|
|
1361
|
+
</body>
|
|
1362
|
+
</html>`;
|
|
1363
|
+
}
|
|
1364
|
+
//# sourceMappingURL=ticket-view.js.map
|