ng-directive-zero 1.0.5

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.
@@ -0,0 +1,2475 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, EventEmitter, Output, Input, Component, HostListener, HostBinding, isDevMode, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i2 from '@angular/forms';
6
+ import { FormsModule } from '@angular/forms';
7
+ import * as i1$1 from '@angular/common/http';
8
+ import { HttpClientModule } from '@angular/common/http';
9
+ import { BehaviorSubject, firstValueFrom, timer, of } from 'rxjs';
10
+ import { switchMap, catchError } from 'rxjs/operators';
11
+ import { v4 } from 'uuid';
12
+
13
+ /**
14
+ * Angular Agentation - Component Node Interface
15
+ * 定義從 DOM 元素解析出的 Angular 組件資訊結構
16
+ */
17
+ /**
18
+ * 需要提取的關鍵 CSS 屬性列表
19
+ */
20
+ const KEY_COMPUTED_STYLES = [
21
+ 'display',
22
+ 'position',
23
+ 'width',
24
+ 'height',
25
+ 'padding',
26
+ 'margin',
27
+ 'background-color',
28
+ 'color',
29
+ 'font-size',
30
+ 'font-family',
31
+ 'border',
32
+ 'border-radius',
33
+ 'opacity',
34
+ 'cursor',
35
+ 'z-index',
36
+ ];
37
+ /**
38
+ * 標記顏色對應 HEX 值
39
+ */
40
+ const MARKER_COLORS = {
41
+ purple: '#a855f7',
42
+ blue: '#3b82f6',
43
+ cyan: '#06b6d4',
44
+ green: '#22c55e',
45
+ yellow: '#eab308',
46
+ orange: '#f97316',
47
+ red: '#ef4444',
48
+ };
49
+ /**
50
+ * 預設設定
51
+ */
52
+ const DEFAULT_SETTINGS = {
53
+ isDarkMode: false,
54
+ outputDetail: 'forensic',
55
+ showAngularComponents: true,
56
+ markerColor: 'blue',
57
+ clearOnCopy: false,
58
+ blockPageInteractions: false,
59
+ };
60
+
61
+ /**
62
+ * ComponentWalkerService
63
+ *
64
+ * 核心服務:從 DOM 元素獲取 Angular 組件資訊
65
+ * 使用 Angular 的 ng.getComponent() 等開發模式 API
66
+ *
67
+ * 效能要求:執行時間 < 16ms (1 frame)
68
+ */
69
+ class ComponentWalkerService {
70
+ uidCounter = 0;
71
+ /**
72
+ * 檢查是否在開發模式下且 ng API 可用
73
+ */
74
+ isAvailable() {
75
+ return typeof window.ng?.getComponent === 'function';
76
+ }
77
+ /**
78
+ * 從 DOM 元素獲取 Angular 組件節點資訊
79
+ *
80
+ * @param element - 目標 DOM 元素
81
+ * @returns ComponentNode 或 null(如果不是 Angular 組件)
82
+ */
83
+ getComponentNode(element) {
84
+ if (!this.isAvailable()) {
85
+ console.warn('[ng-directive-zero] Angular debug API not available. Overlay disabled.');
86
+ return null;
87
+ }
88
+ const ng = window.ng;
89
+ const startTime = performance.now();
90
+ try {
91
+ // 嘗試獲取組件實例
92
+ let component = ng.getComponent(element);
93
+ let targetElement = element;
94
+ let isDomNode = false;
95
+ // 如果當前元素不是組件,向上查找最近的組件
96
+ if (!component) {
97
+ const owningComponent = ng.getOwningComponent(element);
98
+ if (!owningComponent) {
99
+ return null;
100
+ }
101
+ // 檢查是否為 Root Component (沒有父組件)
102
+ const componentHost = this.findComponentHost(element, owningComponent);
103
+ // const parentOfOwning = this.getParentInfo(componentHost || element, ng);
104
+ // 如果點擊的不是 host 本身,則將其視為 DOM 節點
105
+ // (不再限制只能是 Root Component 的子元素)
106
+ if (componentHost !== element) {
107
+ component = owningComponent;
108
+ targetElement = element;
109
+ isDomNode = true;
110
+ }
111
+ else {
112
+ component = owningComponent;
113
+ // 找到擁有該組件的元素
114
+ targetElement = componentHost ?? element;
115
+ }
116
+ }
117
+ if (isDomNode) {
118
+ return this.createDomNode(targetElement, component);
119
+ }
120
+ // 獲取組件定義
121
+ const componentDef = this.getComponentDef(component);
122
+ const displayName = component.constructor.name;
123
+ const selector = this.extractSelector(componentDef);
124
+ // 提取 @Input 值
125
+ const inputs = this.extractInputValues(component, componentDef);
126
+ // 提取 @Output 名稱
127
+ const outputs = this.extractOutputNames(componentDef);
128
+ // 提取公開屬性
129
+ const publicProperties = this.extractPublicProperties(component, componentDef);
130
+ // 獲取應用的指令
131
+ const directives = ng.getDirectives(targetElement)
132
+ .map((d) => d.constructor.name)
133
+ .filter((name) => name !== 'Object');
134
+ // 獲取父組件資訊
135
+ const parent = this.getParentInfo(targetElement, ng);
136
+ // 計算 DOM 路徑
137
+ const domPath = this.computeDomPath(targetElement);
138
+ // 獲取 computed styles
139
+ const computedStyles = this.extractComputedStyles(targetElement);
140
+ const node = {
141
+ uid: this.generateUid(),
142
+ displayName,
143
+ selector,
144
+ filePath: null, // MVP 階段不支援
145
+ domPath,
146
+ inputs,
147
+ outputs,
148
+ publicProperties,
149
+ domElement: targetElement,
150
+ rect: targetElement.getBoundingClientRect(),
151
+ computedStyles,
152
+ directives,
153
+ parent,
154
+ };
155
+ // 效能監控
156
+ const elapsed = performance.now() - startTime;
157
+ if (elapsed > 16) {
158
+ console.warn(`[ng-directive-zero] ComponentWalker took ${elapsed.toFixed(2)}ms (> 16ms frame budget)`);
159
+ }
160
+ return node;
161
+ }
162
+ catch (error) {
163
+ console.error('[ng-directive-zero] Error walking component:', error);
164
+ return null;
165
+ }
166
+ }
167
+ /**
168
+ * 創建 DOM 節點組件資訊 (用於 Root Component 內的普通元素)
169
+ */
170
+ createDomNode(element, owningComponent) {
171
+ const componentDef = this.getComponentDef(owningComponent);
172
+ // 父組件是 Root Component
173
+ const parentInfo = {
174
+ displayName: owningComponent.constructor.name,
175
+ selector: this.extractSelector(componentDef),
176
+ };
177
+ const domPath = this.computeDomPath(element);
178
+ const computedStyles = this.extractComputedStyles(element);
179
+ // 使用 tag name 作為名稱
180
+ const tagName = element.tagName.toLowerCase();
181
+ // 加上 id 或 class 以便識別
182
+ let displayName = tagName;
183
+ if (element.id) {
184
+ displayName += `#${element.id}`;
185
+ }
186
+ else if (element.classList.length > 0) {
187
+ displayName += `.${element.classList[0]}`;
188
+ }
189
+ return {
190
+ uid: this.generateUid(),
191
+ displayName: displayName,
192
+ selector: tagName,
193
+ filePath: null,
194
+ domPath,
195
+ inputs: {},
196
+ outputs: [],
197
+ publicProperties: {},
198
+ domElement: element,
199
+ rect: element.getBoundingClientRect(),
200
+ computedStyles,
201
+ directives: [],
202
+ parent: parentInfo,
203
+ };
204
+ }
205
+ /**
206
+ * 獲取組件定義 (ɵcmp)
207
+ */
208
+ getComponentDef(component) {
209
+ const constructor = component.constructor;
210
+ return constructor.ɵcmp ?? null;
211
+ }
212
+ /**
213
+ * 從組件定義提取 selector
214
+ */
215
+ extractSelector(componentDef) {
216
+ if (!componentDef?.selectors?.[0]) {
217
+ return 'unknown';
218
+ }
219
+ // selectors 是嵌套陣列,第一個元素通常是 tag selector
220
+ return componentDef.selectors[0].filter(Boolean).join('') || 'unknown';
221
+ }
222
+ /**
223
+ * 提取 @Input 綁定的當前值
224
+ */
225
+ extractInputValues(component, componentDef) {
226
+ const inputs = {};
227
+ const inputDefs = componentDef?.inputs ?? {};
228
+ for (const [publicName, propertyName] of Object.entries(inputDefs)) {
229
+ const value = component[propertyName];
230
+ // 過濾掉 undefined 值
231
+ if (value !== undefined) {
232
+ inputs[publicName] = this.sanitizeValue(value);
233
+ }
234
+ }
235
+ return inputs;
236
+ }
237
+ /**
238
+ * 提取 @Output 事件名稱
239
+ */
240
+ extractOutputNames(componentDef) {
241
+ const outputDefs = componentDef?.outputs ?? {};
242
+ return Object.keys(outputDefs);
243
+ }
244
+ /**
245
+ * 提取非 Input/Output 的公開屬性
246
+ */
247
+ extractPublicProperties(component, componentDef) {
248
+ const properties = {};
249
+ const inputProps = new Set(Object.values(componentDef?.inputs ?? {}));
250
+ const outputProps = new Set(Object.values(componentDef?.outputs ?? {}));
251
+ // 獲取組件實例上的所有可枚舉屬性
252
+ for (const key of Object.keys(component)) {
253
+ // 跳過 Angular 內部屬性
254
+ if (key.startsWith('_') || key.startsWith('ɵ') || key.startsWith('ng')) {
255
+ continue;
256
+ }
257
+ // 跳過 Input/Output 屬性
258
+ if (inputProps.has(key) || outputProps.has(key)) {
259
+ continue;
260
+ }
261
+ const value = component[key];
262
+ // 跳過函數
263
+ if (typeof value === 'function') {
264
+ continue;
265
+ }
266
+ properties[key] = this.sanitizeValue(value);
267
+ }
268
+ return properties;
269
+ }
270
+ /**
271
+ * 清理值以便序列化
272
+ */
273
+ sanitizeValue(value) {
274
+ if (value === null || value === undefined) {
275
+ return value;
276
+ }
277
+ // 處理函數
278
+ if (typeof value === 'function') {
279
+ return `[Function: ${value.name || 'anonymous'}]`;
280
+ }
281
+ // 處理 Observable/Subject
282
+ if (this.isObservable(value)) {
283
+ return `[Observable]`;
284
+ }
285
+ // 處理 DOM 元素
286
+ if (value instanceof HTMLElement) {
287
+ return `[HTMLElement: ${value.tagName.toLowerCase()}]`;
288
+ }
289
+ // 處理大型字串 (可能是 Base64)
290
+ if (typeof value === 'string' && value.length > 1024) {
291
+ return `[String: ${value.length} chars]`;
292
+ }
293
+ // 處理陣列
294
+ if (Array.isArray(value)) {
295
+ if (value.length > 10) {
296
+ return `[Array: ${value.length} items]`;
297
+ }
298
+ return value.map((v) => this.sanitizeValue(v));
299
+ }
300
+ // 處理物件
301
+ if (typeof value === 'object') {
302
+ // 避免循環引用,只展開一層
303
+ const sanitized = {};
304
+ for (const [k, v] of Object.entries(value)) {
305
+ if (typeof v !== 'object' || v === null) {
306
+ sanitized[k] = this.sanitizeValue(v);
307
+ }
308
+ else {
309
+ sanitized[k] = `[Object: ${v.constructor.name}]`;
310
+ }
311
+ }
312
+ return sanitized;
313
+ }
314
+ return value;
315
+ }
316
+ /**
317
+ * 檢查是否為 RxJS Observable
318
+ */
319
+ isObservable(value) {
320
+ return (value !== null &&
321
+ typeof value === 'object' &&
322
+ typeof value.subscribe === 'function');
323
+ }
324
+ /**
325
+ * 獲取父組件資訊
326
+ */
327
+ getParentInfo(element, ng) {
328
+ let parent = element.parentElement;
329
+ while (parent) {
330
+ const parentComponent = ng.getComponent(parent);
331
+ if (parentComponent) {
332
+ const parentDef = this.getComponentDef(parentComponent);
333
+ return {
334
+ displayName: parentComponent.constructor.name,
335
+ selector: this.extractSelector(parentDef),
336
+ };
337
+ }
338
+ parent = parent.parentElement;
339
+ }
340
+ return undefined;
341
+ }
342
+ /**
343
+ * 找到組件的 host 元素
344
+ */
345
+ findComponentHost(startElement, component) {
346
+ const ng = window.ng;
347
+ let current = startElement;
348
+ while (current) {
349
+ if (ng.getComponent(current) === component) {
350
+ return current;
351
+ }
352
+ current = current.parentElement;
353
+ }
354
+ return null;
355
+ }
356
+ /**
357
+ * 計算 DOM 路徑
358
+ */
359
+ computeDomPath(element) {
360
+ const parts = [];
361
+ let current = element;
362
+ while (current && current !== document.body) {
363
+ let selector = current.tagName.toLowerCase();
364
+ // 加上 id(如果有)
365
+ if (current.id) {
366
+ selector += `#${current.id}`;
367
+ }
368
+ // 或加上第一個 class
369
+ else if (current.classList.length > 0) {
370
+ selector += `.${current.classList[0]}`;
371
+ }
372
+ parts.unshift(selector);
373
+ current = current.parentElement;
374
+ }
375
+ parts.unshift('body');
376
+ return parts.join(' > ');
377
+ }
378
+ /**
379
+ * 提取關鍵 computed styles
380
+ */
381
+ extractComputedStyles(element) {
382
+ const styles = {};
383
+ const computed = window.getComputedStyle(element);
384
+ for (const prop of KEY_COMPUTED_STYLES) {
385
+ styles[prop] = computed.getPropertyValue(prop);
386
+ }
387
+ return styles;
388
+ }
389
+ /**
390
+ * 生成唯一 ID
391
+ */
392
+ generateUid() {
393
+ this.uidCounter++;
394
+ return `ag-${Date.now()}-${this.uidCounter}`;
395
+ }
396
+ /**
397
+ * 獲取元素的祖先鏈(從當前元素到根)
398
+ * 用於層級麵包屑導航
399
+ *
400
+ * @param element - 起始 DOM 元素
401
+ * @param maxDepth - 最大深度(預設 10,避免過長)
402
+ * @returns ComponentNode 陣列,索引 0 為當前元素,依次向上
403
+ */
404
+ getAncestorChain(element, maxDepth = 10) {
405
+ if (!this.isAvailable()) {
406
+ return [];
407
+ }
408
+ const chain = [];
409
+ let current = element;
410
+ let depth = 0;
411
+ while (current && current !== document.body && depth < maxDepth) {
412
+ const node = this.getComponentNode(current);
413
+ if (node) {
414
+ chain.push(node);
415
+ }
416
+ // 尋找下一個有效的父元素
417
+ current = current.parentElement;
418
+ depth++;
419
+ }
420
+ return chain;
421
+ }
422
+ /**
423
+ * 簡化版:僅獲取祖先元素的基本資訊(用於麵包屑顯示)
424
+ * 效能更佳,不需要完整的 ComponentNode
425
+ *
426
+ * @param element - 起始 DOM 元素
427
+ * @param maxDepth - 最大深度
428
+ * @returns 簡化的祖先資訊陣列
429
+ */
430
+ getAncestorBreadcrumbs(element, maxDepth = 10) {
431
+ if (!this.isAvailable()) {
432
+ return [];
433
+ }
434
+ const ng = window.ng;
435
+ const breadcrumbs = [];
436
+ let current = element;
437
+ let depth = 0;
438
+ while (current && current !== document.body && depth < maxDepth) {
439
+ const component = ng.getComponent(current);
440
+ const owningComponent = ng.getOwningComponent(current);
441
+ let label;
442
+ let isComponent = false;
443
+ if (component) {
444
+ // 這是一個 Angular 組件的 host 元素
445
+ const componentDef = this.getComponentDef(component);
446
+ label = this.extractSelector(componentDef);
447
+ isComponent = true;
448
+ }
449
+ else if (owningComponent) {
450
+ // 這是組件內的普通 DOM 元素
451
+ const tagName = current.tagName.toLowerCase();
452
+ if (current.id) {
453
+ label = `${tagName}#${current.id}`;
454
+ }
455
+ else if (current.classList.length > 0) {
456
+ label = `${tagName}.${current.classList[0]}`;
457
+ }
458
+ else {
459
+ label = tagName;
460
+ }
461
+ }
462
+ else {
463
+ // 不屬於任何 Angular 組件
464
+ current = current.parentElement;
465
+ depth++;
466
+ continue;
467
+ }
468
+ breadcrumbs.push({
469
+ label,
470
+ element: current,
471
+ isComponent,
472
+ depth,
473
+ });
474
+ current = current.parentElement;
475
+ depth++;
476
+ }
477
+ return breadcrumbs;
478
+ }
479
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ComponentWalkerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
480
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ComponentWalkerService, providedIn: 'root' });
481
+ }
482
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ComponentWalkerService, decorators: [{
483
+ type: Injectable,
484
+ args: [{
485
+ providedIn: 'root',
486
+ }]
487
+ }] });
488
+
489
+ /**
490
+ * InlineEditorComponent
491
+ *
492
+ * 內嵌編輯器:用於編輯已標記元素的 intent
493
+ * 參考 React Agentation 的設計
494
+ */
495
+ class InlineEditorComponent {
496
+ /** 正在編輯的標記 */
497
+ marker = null;
498
+ /** 編輯器位置 */
499
+ position = { top: 0, left: 0 };
500
+ /** 儲存時觸發 */
501
+ save = new EventEmitter();
502
+ /** 刪除時觸發 */
503
+ delete = new EventEmitter();
504
+ /** 取消時觸發 */
505
+ cancel = new EventEmitter();
506
+ /** 暫存的 intent 值 */
507
+ tempIntent = '';
508
+ /** 樣式面板是否展開 */
509
+ isStyleExpanded = false;
510
+ ngOnChanges() {
511
+ if (this.marker) {
512
+ this.tempIntent = this.marker.intent || '';
513
+ }
514
+ }
515
+ /** 儲存 */
516
+ onSave() {
517
+ if (this.marker) {
518
+ this.save.emit({
519
+ index: this.marker.index,
520
+ intent: this.tempIntent,
521
+ });
522
+ }
523
+ }
524
+ /** 刪除 */
525
+ onDelete() {
526
+ if (this.marker) {
527
+ this.delete.emit(this.marker.index);
528
+ }
529
+ }
530
+ /** 取消 */
531
+ onCancel() {
532
+ this.cancel.emit();
533
+ }
534
+ /** 獲取編輯器樣式 */
535
+ getEditorStyle() {
536
+ return {
537
+ position: 'absolute',
538
+ top: `${this.position.top}px`,
539
+ left: `${this.position.left}px`,
540
+ zIndex: '999999',
541
+ };
542
+ }
543
+ /** 獲取元素描述 */
544
+ getElementDescription() {
545
+ if (!this.marker)
546
+ return '';
547
+ const target = this.marker.target;
548
+ return `${target.selector || target.displayName}`;
549
+ }
550
+ /** 切換樣式面板 */
551
+ toggleStylePanel() {
552
+ this.isStyleExpanded = !this.isStyleExpanded;
553
+ }
554
+ /** 獲取樣式條目 */
555
+ getStyleEntries() {
556
+ if (!this.marker)
557
+ return [];
558
+ return Object.entries(this.marker.target.computedStyles)
559
+ .filter(([, value]) => value && value !== 'none' && value !== 'normal')
560
+ .map(([key, value]) => ({ key, value }));
561
+ }
562
+ /** 格式化值為顯示字串 */
563
+ formatValue(value) {
564
+ if (!value)
565
+ return '';
566
+ // 如果值太長,截斷它
567
+ return value.length > 50 ? value.substring(0, 50) + '...' : value;
568
+ }
569
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: InlineEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
570
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: InlineEditorComponent, isStandalone: false, selector: "ag-inline-editor", inputs: { marker: "marker", position: "position" }, outputs: { save: "save", delete: "delete", cancel: "cancel" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"ag-inline-editor\" [ngStyle]=\"getEditorStyle()\">\n <!-- \u6A19\u984C -->\n <div class=\"ag-editor-header\">\n <button class=\"ag-editor-title\" (click)=\"toggleStylePanel()\" title=\"Toggle styles\">\n <svg \n viewBox=\"0 0 24 24\" \n fill=\"none\" \n stroke=\"currentColor\" \n stroke-width=\"2\"\n [class.expanded]=\"isStyleExpanded\"\n >\n <polyline points=\"6 9 12 15 18 9\"></polyline>\n </svg>\n {{ getElementDescription() }}\n </button>\n </div>\n\n <!-- \u8F38\u5165\u6846 -->\n <div class=\"ag-editor-body\">\n <textarea\n class=\"ag-editor-input\"\n [(ngModel)]=\"tempIntent\"\n placeholder=\"Enter your feedback or intent...\"\n rows=\"4\"\n (keydown.ctrl.enter)=\"onSave()\"\n (keydown.esc)=\"onCancel()\"\n autofocus\n ></textarea>\n </div>\n\n <!-- \u6A23\u5F0F\u8A73\u60C5\u5340\u584A -->\n <div class=\"ag-editor-styles\" *ngIf=\"isStyleExpanded\">\n <div class=\"ag-styles-header\">Computed Styles</div>\n <div class=\"ag-styles-list\">\n <div class=\"ag-style-item\" *ngFor=\"let style of getStyleEntries()\">\n <span class=\"ag-style-key\">{{ style.key }}:</span>\n <span class=\"ag-style-value\">{{ formatValue(style.value) }}</span>\n </div>\n <div class=\"ag-styles-empty\" *ngIf=\"getStyleEntries().length === 0\">\n No significant styles found\n </div>\n </div>\n </div>\n\n <!-- \u64CD\u4F5C\u6309\u9215 -->\n <div class=\"ag-editor-footer\">\n <button class=\"ag-btn ag-btn-delete\" (click)=\"onDelete()\" title=\"Delete marker\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n </button>\n <div class=\"ag-btn-group\">\n <button class=\"ag-btn ag-btn-cancel\" (click)=\"onCancel()\">Cancel</button>\n <button class=\"ag-btn ag-btn-save\" (click)=\"onSave()\">Save</button>\n </div>\n </div>\n</div>\n", styles: [".ag-inline-editor{width:420px;background:#12121a;border:1px solid #2a2a3a;clip-path:polygon(0 10px,10px 0,calc(100% - 10px) 0,100% 10px,100% calc(100% - 10px),calc(100% - 10px) 100%,10px 100%,0 calc(100% - 10px));font-family:JetBrains Mono,monospace;box-shadow:0 0 20px #00ff880f,0 10px 40px #000000b3;position:relative}.ag-inline-editor:before{content:\"\";position:absolute;top:0;left:10px;right:10px;height:1px;background:linear-gradient(90deg,transparent,#00ff88,transparent);pointer-events:none;z-index:1}.ag-editor-header{padding:12px 16px;border-bottom:1px solid #2a2a3a;background:#0a0a0f}.ag-editor-title{display:flex;align-items:center;gap:8px;width:100%;padding:0;border:none;background:transparent;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#6b7280;text-align:left;cursor:pointer;transition:color .15s}.ag-editor-title:before{content:\">\";color:#00ff8880;font-family:Share Tech Mono,monospace}.ag-editor-title svg{width:12px;height:12px;stroke:currentColor;stroke-width:1.5;transform:rotate(-90deg);transition:transform .2s ease,stroke .15s}.ag-editor-title svg.expanded{transform:rotate(0)}.ag-editor-title:hover{color:#e0e0e0}.ag-editor-title:hover svg{stroke:#e0e0e0}.ag-editor-body{padding:14px 16px}.ag-editor-input{width:100%;padding:10px 12px;font-family:JetBrains Mono,monospace;font-size:13px;letter-spacing:.03em;color:#0f8;background:#0a0a0f;border:1px solid #2a2a3a;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));resize:vertical;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.ag-editor-input::placeholder{color:#6b728080}.ag-editor-input:focus{outline:none;border-color:#00ff8880;box-shadow:0 0 8px #00ff881a,inset 0 0 4px #00ff880a}.ag-editor-footer{display:flex;align-items:center;justify-content:space-between;padding:10px 16px 14px}.ag-btn{display:flex;align-items:center;justify-content:center;padding:7px 14px;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.15em;text-transform:uppercase;border:none;background:transparent;cursor:pointer;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-btn:active{transform:scale(.96)}.ag-btn-delete{width:34px;height:34px;padding:0;border:1px solid rgba(255,51,102,.25);color:#ff336680}.ag-btn-delete svg{width:16px;height:16px;stroke-width:1.5}.ag-btn-delete:hover{background:#ff33661f;border-color:#ff336680;color:#f36;box-shadow:0 0 8px #f363}.ag-btn-group{display:flex;gap:8px}.ag-btn-cancel{border:1px solid #2a2a3a;color:#6b7280}.ag-btn-cancel:hover{border-color:#6b728066;color:#e0e0e0;background:#6b72800d}.ag-btn-save{border:1px solid #00ff88;color:#0f8;text-shadow:0 0 5px rgba(0,255,136,.3)}.ag-btn-save:hover{background:#0f8;color:#0a0a0f;text-shadow:none;box-shadow:0 0 10px #0f86}.ag-editor-styles{border-top:1px solid #2a2a3a;max-height:250px;overflow:hidden;animation:slideDown .15s ease-out}@keyframes slideDown{0%{max-height:0;opacity:0}to{max-height:250px;opacity:1}}.ag-styles-header{padding:10px 16px;font-family:Share Tech Mono,monospace;font-size:10px;font-weight:400;letter-spacing:.18em;text-transform:uppercase;color:#6b7280;background:#0a0a0f;border-bottom:1px solid #2a2a3a}.ag-styles-header:before{content:\"// \";color:#0f86}.ag-styles-list{padding:8px 16px 12px;max-height:200px;overflow-y:auto}.ag-styles-list::-webkit-scrollbar{width:4px}.ag-styles-list::-webkit-scrollbar-track{background:#0a0a0f}.ag-styles-list::-webkit-scrollbar-thumb{background:#2a2a3a}.ag-style-item{display:flex;gap:8px;padding:4px 0;border-bottom:1px solid rgba(42,42,58,.4)}.ag-style-item:last-child{border-bottom:none}.ag-style-key{font-family:Share Tech Mono,monospace;font-size:11px;color:#6b7280;min-width:120px;flex-shrink:0}.ag-style-value{font-family:Share Tech Mono,monospace;font-size:11px;color:#f0f;word-break:break-word;opacity:.8}.ag-styles-empty{padding:20px 0;text-align:center;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;color:#6b728080}.ag-styles-empty:before{content:\"// \";color:#00ff884d}@media (max-width: 768px){.ag-inline-editor{position:fixed!important;inset:auto 0 0!important;width:100%!important;max-width:100vw!important;clip-path:polygon(0 10px,10px 0,calc(100% - 10px) 0,100% 10px,100% 100%,0 100%)!important;transform:none!important;margin:0!important;max-height:80vh;overflow-y:auto;box-shadow:0 -4px 20px #00000080;z-index:9999999!important}}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
571
+ }
572
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: InlineEditorComponent, decorators: [{
573
+ type: Component,
574
+ args: [{ selector: 'ag-inline-editor', standalone: false, template: "<div class=\"ag-inline-editor\" [ngStyle]=\"getEditorStyle()\">\n <!-- \u6A19\u984C -->\n <div class=\"ag-editor-header\">\n <button class=\"ag-editor-title\" (click)=\"toggleStylePanel()\" title=\"Toggle styles\">\n <svg \n viewBox=\"0 0 24 24\" \n fill=\"none\" \n stroke=\"currentColor\" \n stroke-width=\"2\"\n [class.expanded]=\"isStyleExpanded\"\n >\n <polyline points=\"6 9 12 15 18 9\"></polyline>\n </svg>\n {{ getElementDescription() }}\n </button>\n </div>\n\n <!-- \u8F38\u5165\u6846 -->\n <div class=\"ag-editor-body\">\n <textarea\n class=\"ag-editor-input\"\n [(ngModel)]=\"tempIntent\"\n placeholder=\"Enter your feedback or intent...\"\n rows=\"4\"\n (keydown.ctrl.enter)=\"onSave()\"\n (keydown.esc)=\"onCancel()\"\n autofocus\n ></textarea>\n </div>\n\n <!-- \u6A23\u5F0F\u8A73\u60C5\u5340\u584A -->\n <div class=\"ag-editor-styles\" *ngIf=\"isStyleExpanded\">\n <div class=\"ag-styles-header\">Computed Styles</div>\n <div class=\"ag-styles-list\">\n <div class=\"ag-style-item\" *ngFor=\"let style of getStyleEntries()\">\n <span class=\"ag-style-key\">{{ style.key }}:</span>\n <span class=\"ag-style-value\">{{ formatValue(style.value) }}</span>\n </div>\n <div class=\"ag-styles-empty\" *ngIf=\"getStyleEntries().length === 0\">\n No significant styles found\n </div>\n </div>\n </div>\n\n <!-- \u64CD\u4F5C\u6309\u9215 -->\n <div class=\"ag-editor-footer\">\n <button class=\"ag-btn ag-btn-delete\" (click)=\"onDelete()\" title=\"Delete marker\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n </button>\n <div class=\"ag-btn-group\">\n <button class=\"ag-btn ag-btn-cancel\" (click)=\"onCancel()\">Cancel</button>\n <button class=\"ag-btn ag-btn-save\" (click)=\"onSave()\">Save</button>\n </div>\n </div>\n</div>\n", styles: [".ag-inline-editor{width:420px;background:#12121a;border:1px solid #2a2a3a;clip-path:polygon(0 10px,10px 0,calc(100% - 10px) 0,100% 10px,100% calc(100% - 10px),calc(100% - 10px) 100%,10px 100%,0 calc(100% - 10px));font-family:JetBrains Mono,monospace;box-shadow:0 0 20px #00ff880f,0 10px 40px #000000b3;position:relative}.ag-inline-editor:before{content:\"\";position:absolute;top:0;left:10px;right:10px;height:1px;background:linear-gradient(90deg,transparent,#00ff88,transparent);pointer-events:none;z-index:1}.ag-editor-header{padding:12px 16px;border-bottom:1px solid #2a2a3a;background:#0a0a0f}.ag-editor-title{display:flex;align-items:center;gap:8px;width:100%;padding:0;border:none;background:transparent;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#6b7280;text-align:left;cursor:pointer;transition:color .15s}.ag-editor-title:before{content:\">\";color:#00ff8880;font-family:Share Tech Mono,monospace}.ag-editor-title svg{width:12px;height:12px;stroke:currentColor;stroke-width:1.5;transform:rotate(-90deg);transition:transform .2s ease,stroke .15s}.ag-editor-title svg.expanded{transform:rotate(0)}.ag-editor-title:hover{color:#e0e0e0}.ag-editor-title:hover svg{stroke:#e0e0e0}.ag-editor-body{padding:14px 16px}.ag-editor-input{width:100%;padding:10px 12px;font-family:JetBrains Mono,monospace;font-size:13px;letter-spacing:.03em;color:#0f8;background:#0a0a0f;border:1px solid #2a2a3a;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));resize:vertical;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.ag-editor-input::placeholder{color:#6b728080}.ag-editor-input:focus{outline:none;border-color:#00ff8880;box-shadow:0 0 8px #00ff881a,inset 0 0 4px #00ff880a}.ag-editor-footer{display:flex;align-items:center;justify-content:space-between;padding:10px 16px 14px}.ag-btn{display:flex;align-items:center;justify-content:center;padding:7px 14px;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.15em;text-transform:uppercase;border:none;background:transparent;cursor:pointer;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-btn:active{transform:scale(.96)}.ag-btn-delete{width:34px;height:34px;padding:0;border:1px solid rgba(255,51,102,.25);color:#ff336680}.ag-btn-delete svg{width:16px;height:16px;stroke-width:1.5}.ag-btn-delete:hover{background:#ff33661f;border-color:#ff336680;color:#f36;box-shadow:0 0 8px #f363}.ag-btn-group{display:flex;gap:8px}.ag-btn-cancel{border:1px solid #2a2a3a;color:#6b7280}.ag-btn-cancel:hover{border-color:#6b728066;color:#e0e0e0;background:#6b72800d}.ag-btn-save{border:1px solid #00ff88;color:#0f8;text-shadow:0 0 5px rgba(0,255,136,.3)}.ag-btn-save:hover{background:#0f8;color:#0a0a0f;text-shadow:none;box-shadow:0 0 10px #0f86}.ag-editor-styles{border-top:1px solid #2a2a3a;max-height:250px;overflow:hidden;animation:slideDown .15s ease-out}@keyframes slideDown{0%{max-height:0;opacity:0}to{max-height:250px;opacity:1}}.ag-styles-header{padding:10px 16px;font-family:Share Tech Mono,monospace;font-size:10px;font-weight:400;letter-spacing:.18em;text-transform:uppercase;color:#6b7280;background:#0a0a0f;border-bottom:1px solid #2a2a3a}.ag-styles-header:before{content:\"// \";color:#0f86}.ag-styles-list{padding:8px 16px 12px;max-height:200px;overflow-y:auto}.ag-styles-list::-webkit-scrollbar{width:4px}.ag-styles-list::-webkit-scrollbar-track{background:#0a0a0f}.ag-styles-list::-webkit-scrollbar-thumb{background:#2a2a3a}.ag-style-item{display:flex;gap:8px;padding:4px 0;border-bottom:1px solid rgba(42,42,58,.4)}.ag-style-item:last-child{border-bottom:none}.ag-style-key{font-family:Share Tech Mono,monospace;font-size:11px;color:#6b7280;min-width:120px;flex-shrink:0}.ag-style-value{font-family:Share Tech Mono,monospace;font-size:11px;color:#f0f;word-break:break-word;opacity:.8}.ag-styles-empty{padding:20px 0;text-align:center;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;color:#6b728080}.ag-styles-empty:before{content:\"// \";color:#00ff884d}@media (max-width: 768px){.ag-inline-editor{position:fixed!important;inset:auto 0 0!important;width:100%!important;max-width:100vw!important;clip-path:polygon(0 10px,10px 0,calc(100% - 10px) 0,100% 10px,100% 100%,0 100%)!important;transform:none!important;margin:0!important;max-height:80vh;overflow-y:auto;box-shadow:0 -4px 20px #00000080;z-index:9999999!important}}\n"] }]
575
+ }], propDecorators: { marker: [{
576
+ type: Input
577
+ }], position: [{
578
+ type: Input
579
+ }], save: [{
580
+ type: Output
581
+ }], delete: [{
582
+ type: Output
583
+ }], cancel: [{
584
+ type: Output
585
+ }] } });
586
+
587
+ /**
588
+ * OverlayComponent (v2)
589
+ *
590
+ * 支援多選標記的視覺化 DOM 檢查器
591
+ */
592
+ class OverlayComponent {
593
+ componentWalker;
594
+ /** 已有的標記列表 */
595
+ markers = [];
596
+ /** 當前設定 */
597
+ settings = DEFAULT_SETTINGS;
598
+ /** 是否處於錄製模式 */
599
+ isRecording = false;
600
+ /** 工具列是否最小化 */
601
+ isMinimized = false;
602
+ /** 新增標記時觸發(多選模式) */
603
+ markerAdded = new EventEmitter();
604
+ /** 選中組件時觸發(兼容舊版) */
605
+ componentSelected = new EventEmitter();
606
+ /** 懸停組件變化時觸發 */
607
+ componentHovered = new EventEmitter();
608
+ /** 錄製模式變化時觸發 */
609
+ recordingChanged = new EventEmitter();
610
+ /** 標記被刪除時觸發 */
611
+ markerDeleted = new EventEmitter();
612
+ /** 高亮框樣式 */
613
+ highlightStyle = { display: 'none' };
614
+ /** Tooltip 內容 */
615
+ tooltipContent = '';
616
+ /** Tooltip 位置 */
617
+ tooltipStyle = {};
618
+ /** 是否顯示 tooltip */
619
+ showTooltip = false;
620
+ /** 當前懸停的節點 */
621
+ hoveredNode = null;
622
+ /** 顏色對應的 HEX 值 */
623
+ colorHex = MARKER_COLORS;
624
+ /** 綁定的 click handler(用於移除監聽器) */
625
+ boundClickHandler = null;
626
+ /** 正在編輯的標記 */
627
+ editingMarker = null;
628
+ /** 編輯器位置 */
629
+ editorPosition = { top: 0, left: 0 };
630
+ /** 祖先麵包屑列表 */
631
+ ancestorBreadcrumbs = [];
632
+ /** 麵包屑位置 */
633
+ breadcrumbStyle = { display: 'none' };
634
+ /** 當前選中的麵包屑索引 */
635
+ selectedBreadcrumbIndex = 0;
636
+ /** 是否顯示麵包屑 */
637
+ showBreadcrumb = false;
638
+ /** 是否鎖定當前選取(Click-to-lock) */
639
+ isLocked = false;
640
+ /** 鎖定的節點 */
641
+ lockedNode = null;
642
+ constructor(componentWalker) {
643
+ this.componentWalker = componentWalker;
644
+ }
645
+ ngOnInit() {
646
+ if (!this.componentWalker.isAvailable()) {
647
+ console.warn('[ng-directive-zero] Angular debug API not available. Overlay disabled.');
648
+ }
649
+ // 使用 capture phase 攔截點擊事件
650
+ this.boundClickHandler = this.onDocumentClick.bind(this);
651
+ document.addEventListener('click', this.boundClickHandler, true);
652
+ }
653
+ ngOnDestroy() {
654
+ this.stopRecording();
655
+ if (this.boundClickHandler) {
656
+ document.removeEventListener('click', this.boundClickHandler, true);
657
+ }
658
+ }
659
+ ngOnChanges(changes) {
660
+ if (changes['isRecording']) {
661
+ if (this.isRecording) {
662
+ if (this.componentWalker.isAvailable()) {
663
+ document.body.style.cursor = 'crosshair';
664
+ }
665
+ }
666
+ else {
667
+ this.cleanupRecording();
668
+ }
669
+ }
670
+ // 當工具列最小化時,隱藏所有 overlay 元素
671
+ if (changes['isMinimized'] && this.isMinimized) {
672
+ this.clearHighlight();
673
+ }
674
+ }
675
+ /**
676
+ * 開始錄製模式
677
+ */
678
+ startRecording() {
679
+ if (!this.componentWalker.isAvailable()) {
680
+ console.error('[ng-directive-zero] Cannot start recording: Angular debug API not available.');
681
+ return;
682
+ }
683
+ this.isRecording = true;
684
+ this.recordingChanged.emit(true);
685
+ document.body.style.cursor = 'crosshair';
686
+ }
687
+ /**
688
+ * 停止錄製模式
689
+ */
690
+ stopRecording() {
691
+ this.isRecording = false;
692
+ this.cleanupRecording();
693
+ this.recordingChanged.emit(false);
694
+ }
695
+ cleanupRecording() {
696
+ this.hoveredNode = null;
697
+ this.highlightStyle = { display: 'none' }; // 明確隱藏高亮框
698
+ this.showTooltip = false;
699
+ this.showBreadcrumb = false; // 隱藏麵包屑
700
+ this.ancestorBreadcrumbs = [];
701
+ this.showBreadcrumb = false; // 隱藏麵包屑
702
+ this.ancestorBreadcrumbs = [];
703
+ this.selectedBreadcrumbIndex = 0;
704
+ this.isLocked = false;
705
+ this.lockedNode = null;
706
+ document.body.style.cursor = '';
707
+ }
708
+ /**
709
+ * 切換錄製模式
710
+ */
711
+ toggleRecording() {
712
+ if (this.isRecording) {
713
+ this.stopRecording();
714
+ }
715
+ else {
716
+ this.startRecording();
717
+ }
718
+ }
719
+ /**
720
+ * 處理滑鼠移動
721
+ */
722
+ onMouseMove(event) {
723
+ if (!this.isRecording)
724
+ return;
725
+ if (this.isLocked)
726
+ return; // 鎖定時停止更新懸停狀態
727
+ let target = event.target;
728
+ // 特殊處理:如果是我們的點擊捕捉層 (處理 disabled 元素)
729
+ if (target.classList.contains('ag-click-overlay')) {
730
+ target.style.display = 'none'; // 暫時隱藏
731
+ const underlying = document.elementFromPoint(event.clientX, event.clientY);
732
+ target.style.display = 'block'; // 恢復顯示
733
+ if (underlying && underlying instanceof HTMLElement) {
734
+ target = underlying;
735
+ }
736
+ }
737
+ if (this.isOverlayElement(target))
738
+ return;
739
+ const node = this.componentWalker.getComponentNode(target);
740
+ if (node) {
741
+ this.hoveredNode = node;
742
+ this.updateHighlight(node, this.settings.markerColor);
743
+ this.updateTooltip(node, event);
744
+ this.updateBreadcrumbs(target, event);
745
+ this.componentHovered.emit(node);
746
+ }
747
+ else {
748
+ this.clearHighlight();
749
+ }
750
+ }
751
+ /**
752
+ * 處理點擊(capture phase,優先攔截)
753
+ */
754
+ /**
755
+ * 處理點擊(capture phase,優先攔截)
756
+ */
757
+ onDocumentClick(event) {
758
+ if (!this.isRecording)
759
+ return;
760
+ let target = event.target;
761
+ // 特殊處理:如果是我們的點擊捕捉層
762
+ if (target.classList.contains('ag-click-overlay')) {
763
+ target.style.display = 'none'; // 暫時隱藏
764
+ const underlying = document.elementFromPoint(event.clientX, event.clientY);
765
+ target.style.display = 'block'; // 恢復顯示
766
+ if (underlying && underlying instanceof HTMLElement) {
767
+ target = underlying;
768
+ }
769
+ }
770
+ if (this.isOverlayElement(target))
771
+ return;
772
+ // 立即阻止事件傳播,防止觸發按鈕等元素的功能
773
+ event.preventDefault();
774
+ event.stopPropagation();
775
+ event.stopImmediatePropagation();
776
+ const node = this.componentWalker.getComponentNode(target);
777
+ // 如果處於鎖定狀態
778
+ if (this.isLocked) {
779
+ // 如果點擊的是當前鎖定的節點(或其高亮範圍內),則確認標記
780
+ // 直覺上:第二次點擊 = 確認
781
+ if (node && this.lockedNode && this.isSameNode(node, this.lockedNode)) {
782
+ this.confirmMarker(this.lockedNode);
783
+ this.unlock();
784
+ }
785
+ else {
786
+ // 點擊其他地方 -> 解鎖
787
+ // 如果點擊了另一個有效節點,則立即鎖定該新節點(流暢體驗)
788
+ this.unlock();
789
+ if (node) {
790
+ this.lockNode(node, event);
791
+ }
792
+ }
793
+ }
794
+ else {
795
+ // 未鎖定狀態 -> 第一次點擊 -> 鎖定
796
+ if (node) {
797
+ this.lockNode(node, event);
798
+ }
799
+ }
800
+ }
801
+ /**
802
+ * 鎖定節點
803
+ */
804
+ lockNode(node, event) {
805
+ this.isLocked = true;
806
+ this.lockedNode = node;
807
+ this.hoveredNode = node;
808
+ this.updateHighlight(node, this.settings.markerColor);
809
+ this.updateTooltip(node, event);
810
+ this.updateBreadcrumbs(node.domElement, event);
811
+ this.componentHovered.emit(node);
812
+ }
813
+ /**
814
+ * 解鎖
815
+ */
816
+ unlock() {
817
+ this.isLocked = false;
818
+ this.lockedNode = null;
819
+ this.clearHighlight();
820
+ }
821
+ /**
822
+ * 確認新增標記
823
+ */
824
+ confirmMarker(node) {
825
+ // 檢查是否已存在標記
826
+ const existingMarker = this.markers.find(m => m.target.domElement === node.domElement);
827
+ if (existingMarker) {
828
+ // 顯示編輯器
829
+ const rect = node.domElement.getBoundingClientRect();
830
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
831
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
832
+ this.editorPosition = {
833
+ top: rect.bottom + scrollTop + 10,
834
+ left: rect.left + scrollLeft
835
+ };
836
+ this.editingMarker = existingMarker;
837
+ }
838
+ else {
839
+ // 新增標記
840
+ this.markerAdded.emit(node);
841
+ this.componentSelected.emit(node);
842
+ }
843
+ }
844
+ /**
845
+ * 比較兩個節點是否相同
846
+ */
847
+ isSameNode(a, b) {
848
+ return a.domElement === b.domElement;
849
+ }
850
+ /**
851
+ * 處理編輯器儲存
852
+ */
853
+ onEditorSave(event) {
854
+ const marker = this.markers.find(m => m.index === event.index);
855
+ if (marker) {
856
+ marker.intent = event.intent;
857
+ }
858
+ this.editingMarker = null;
859
+ }
860
+ /**
861
+ * 處理編輯器刪除
862
+ */
863
+ onEditorDelete(index) {
864
+ this.editingMarker = null;
865
+ this.markerDeleted.emit(index);
866
+ }
867
+ /**
868
+ * 處理編輯器取消
869
+ */
870
+ onEditorCancel() {
871
+ this.editingMarker = null;
872
+ }
873
+ /**
874
+ * 處理 Escape 鍵
875
+ */
876
+ onEscape() {
877
+ if (this.isRecording) {
878
+ this.stopRecording();
879
+ }
880
+ }
881
+ /**
882
+ * 處理快捷鍵 Ctrl+Shift+I
883
+ */
884
+ onKeyDown(event) {
885
+ if (event.ctrlKey && event.shiftKey && event.key === 'I') {
886
+ event.preventDefault();
887
+ this.toggleRecording();
888
+ }
889
+ }
890
+ /**
891
+ * 處理點擊標記編號
892
+ */
893
+ onMarkerClick(marker, event) {
894
+ event.preventDefault();
895
+ event.stopPropagation();
896
+ if (!this.isRecording)
897
+ return;
898
+ const rect = marker.target.domElement.getBoundingClientRect();
899
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
900
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
901
+ this.editorPosition = {
902
+ top: rect.bottom + scrollTop + 10,
903
+ left: rect.left + scrollLeft
904
+ };
905
+ this.editingMarker = marker;
906
+ }
907
+ /**
908
+ * 獲取標記的位置樣式
909
+ */
910
+ getMarkerStyle(marker) {
911
+ const element = marker.target.domElement;
912
+ const rect = element.getBoundingClientRect();
913
+ // 計算相對於 document 的絕對位置
914
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
915
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
916
+ return {
917
+ position: 'absolute',
918
+ top: `${rect.top + scrollTop + rect.height / 2 - 14}px`,
919
+ left: `${rect.left + scrollLeft + rect.width - 14}px`,
920
+ backgroundColor: this.colorHex[marker.color],
921
+ };
922
+ }
923
+ /**
924
+ * 更新高亮框
925
+ */
926
+ updateHighlight(node, color) {
927
+ const rect = node.rect;
928
+ const hex = this.colorHex[color];
929
+ this.highlightStyle = {
930
+ display: 'block',
931
+ position: 'absolute',
932
+ top: `${rect.top + window.scrollY}px`,
933
+ left: `${rect.left + window.scrollX}px`,
934
+ width: `${rect.width}px`,
935
+ height: `${rect.height}px`,
936
+ backgroundColor: `${hex}33`,
937
+ border: `2px solid ${hex}`,
938
+ pointerEvents: 'none',
939
+ zIndex: '999998',
940
+ transition: 'all 0.1s ease-out',
941
+ };
942
+ }
943
+ /**
944
+ * 更新 tooltip
945
+ */
946
+ updateTooltip(node, event) {
947
+ this.tooltipContent = `<${node.selector}> ${node.displayName}`;
948
+ const tooltipX = event.clientX + 12;
949
+ const tooltipY = event.clientY + 12;
950
+ this.tooltipStyle = {
951
+ display: 'block',
952
+ position: 'fixed',
953
+ top: `${tooltipY}px`,
954
+ left: `${tooltipX}px`,
955
+ zIndex: '999999',
956
+ };
957
+ this.showTooltip = true;
958
+ }
959
+ /**
960
+ * 清除高亮
961
+ */
962
+ clearHighlight() {
963
+ this.highlightStyle = { display: 'none' };
964
+ this.showTooltip = false;
965
+ this.showBreadcrumb = false;
966
+ this.hoveredNode = null;
967
+ this.ancestorBreadcrumbs = [];
968
+ this.selectedBreadcrumbIndex = 0;
969
+ this.componentHovered.emit(null);
970
+ }
971
+ /**
972
+ * 更新祖先麵包屑
973
+ */
974
+ updateBreadcrumbs(element, event) {
975
+ const breadcrumbs = this.componentWalker.getAncestorBreadcrumbs(element);
976
+ // 只有超過 1 個層級時才顯示麵包屑
977
+ if (breadcrumbs.length > 1) {
978
+ this.ancestorBreadcrumbs = breadcrumbs;
979
+ this.selectedBreadcrumbIndex = 0;
980
+ this.showBreadcrumb = true;
981
+ // 固定在畫面頂部中間(錄製提示下方)
982
+ const viewportWidth = window.innerWidth;
983
+ this.breadcrumbStyle = {
984
+ display: 'flex',
985
+ position: 'fixed',
986
+ top: '60px', // 在錄製提示下方
987
+ left: '50%',
988
+ transform: 'translateX(-50%)',
989
+ maxWidth: `${viewportWidth - 32}px`,
990
+ zIndex: '999999',
991
+ };
992
+ }
993
+ else {
994
+ this.showBreadcrumb = false;
995
+ this.ancestorBreadcrumbs = [];
996
+ }
997
+ }
998
+ /**
999
+ * 處理麵包屑項目點擊
1000
+ */
1001
+ onBreadcrumbClick(breadcrumb, index, event) {
1002
+ event.preventDefault();
1003
+ event.stopPropagation();
1004
+ const node = this.componentWalker.getComponentNode(breadcrumb.element);
1005
+ if (!node)
1006
+ return;
1007
+ // 手機版優化:如果是點擊當前已選中的麵包屑 -> 確認標記
1008
+ if (this.selectedBreadcrumbIndex === index) {
1009
+ this.confirmMarker(node);
1010
+ this.unlock();
1011
+ return;
1012
+ }
1013
+ this.selectedBreadcrumbIndex = index;
1014
+ // 更新鎖定狀態到新的祖先節點
1015
+ this.isLocked = true;
1016
+ this.lockedNode = node;
1017
+ this.hoveredNode = node;
1018
+ this.updateHighlight(node, this.settings.markerColor);
1019
+ this.componentHovered.emit(node);
1020
+ }
1021
+ /**
1022
+ * 處理麵包屑項目雙擊(選取該元素)
1023
+ */
1024
+ /**
1025
+ * 處理麵包屑項目雙擊(直接選取)
1026
+ */
1027
+ onBreadcrumbDoubleClick(breadcrumb, event) {
1028
+ event.preventDefault();
1029
+ event.stopPropagation();
1030
+ const node = this.componentWalker.getComponentNode(breadcrumb.element);
1031
+ if (node) {
1032
+ this.confirmMarker(node);
1033
+ this.unlock();
1034
+ }
1035
+ }
1036
+ /**
1037
+ * 檢查是否為 overlay 元素
1038
+ */
1039
+ isOverlayElement(element) {
1040
+ return element.closest('ag-overlay') !== null ||
1041
+ element.closest('ag-annotation-panel') !== null ||
1042
+ element.closest('ag-toolbar') !== null ||
1043
+ element.closest('ag-settings-panel') !== null ||
1044
+ element.closest('ag-markers-panel') !== null;
1045
+ }
1046
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: OverlayComponent, deps: [{ token: ComponentWalkerService }], target: i0.ɵɵFactoryTarget.Component });
1047
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: OverlayComponent, isStandalone: false, selector: "ag-overlay", inputs: { markers: "markers", settings: "settings", isRecording: "isRecording", isMinimized: "isMinimized" }, outputs: { markerAdded: "markerAdded", componentSelected: "componentSelected", componentHovered: "componentHovered", recordingChanged: "recordingChanged", markerDeleted: "markerDeleted" }, host: { listeners: { "document:mousemove": "onMouseMove($event)", "document:keydown.escape": "onEscape()", "document:keydown": "onKeyDown($event)" } }, usesOnChanges: true, ngImport: i0, template: "<!-- \u4E92\u52D5\u6355\u6349\u5C64 (\u7528\u65BC\u8655\u7406 disabled \u5143\u7D20) -->\n<div class=\"ag-click-overlay\" *ngIf=\"isRecording && !isMinimized\"></div>\n\n<!-- \u9AD8\u4EAE\u6846 -->\n<div class=\"ag-highlight\" *ngIf=\"isRecording && !isMinimized && hoveredNode\" [ngStyle]=\"highlightStyle\"></div>\n\n<!-- Tooltip -->\n<div class=\"ag-tooltip\" *ngIf=\"showTooltip && !isMinimized\" [ngStyle]=\"tooltipStyle\">\n {{ tooltipContent }}\n</div>\n\n<!-- \u7956\u5148\u9EB5\u5305\u5C51\u5C0E\u822A -->\n<div class=\"ag-breadcrumb\" *ngIf=\"showBreadcrumb && !isMinimized && ancestorBreadcrumbs.length > 1\"\n [ngStyle]=\"breadcrumbStyle\">\n <span class=\"ag-breadcrumb-hint\">\u5C64\u7D1A:</span>\n <ng-container *ngFor=\"let crumb of ancestorBreadcrumbs; let i = index; let last = last\">\n <button class=\"ag-breadcrumb-item\" [class.ag-breadcrumb-active]=\"i === selectedBreadcrumbIndex\"\n [class.ag-breadcrumb-component]=\"crumb.isComponent\" (click)=\"onBreadcrumbClick(crumb, i, $event)\"\n (dblclick)=\"onBreadcrumbDoubleClick(crumb, $event)\" [title]=\"'\u55AE\u64CA\u5207\u63DB / \u518D\u6B21\u55AE\u64CA\u78BA\u8A8D: ' + crumb.label\">\n {{ crumb.label }}\n </button>\n <span class=\"ag-breadcrumb-separator\" *ngIf=\"!last\">\u203A</span>\n </ng-container>\n <button class=\"ag-breadcrumb-close\" (click)=\"unlock()\" title=\"\u53D6\u6D88\u9396\u5B9A\">\u2715</button>\n</div>\n\n\n<!-- \u6A19\u8A18\u7DE8\u865F\u6C23\u6CE1 -->\n<div *ngFor=\"let marker of markers\" class=\"ag-marker-bubble\" [ngStyle]=\"getMarkerStyle(marker)\"\n (click)=\"onMarkerClick(marker, $event)\" title=\"\u9EDE\u64CA\u7DE8\u8F2F\">\n {{ marker.index }}\n</div>\n\n<!-- \u9304\u88FD\u72C0\u614B\u63D0\u793A -->\n<div class=\"ag-recording-hint\" *ngIf=\"isRecording && !isMinimized\">\n <div class=\"ag-hint-content\">\n <span class=\"ag-hint-icon\">\uD83D\uDD34</span>\n <span class=\"ag-hint-text\">Click to lock \u2022 Click again to mark \u2022 Use breadcrumb to select parent \u2022 <kbd>Esc</kbd> to\n stop</span>\n </div>\n</div>\n\n<!-- \u5167\u5D4C\u7DE8\u8F2F\u5668 -->\n<ag-inline-editor *ngIf=\"editingMarker && !isMinimized\" [marker]=\"editingMarker\" [position]=\"editorPosition\"\n (save)=\"onEditorSave($event)\" (delete)=\"onEditorDelete($event)\" (cancel)=\"onEditorCancel()\"></ag-inline-editor>", styles: [".ag-click-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:999990;cursor:crosshair;background:transparent;pointer-events:auto}.ag-highlight{pointer-events:none;border:1px solid #00ff88;background:#00ff880a;box-shadow:0 0 0 1px #00ff884d,0 0 10px #00ff8826,inset 0 0 10px #00ff880a}.ag-tooltip{background:#12121a;border:1px solid rgba(0,255,136,.25);color:#0f8;padding:5px 10px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;white-space:nowrap;pointer-events:none;box-shadow:0 0 8px #00ff8826,0 4px 16px #00000080;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-tooltip:before{content:\"> \";color:#00ff8880}.ag-breadcrumb{display:flex;align-items:center;gap:4px;background:#12121af5;border:1px solid #2a2a3a;backdrop-filter:blur(8px);padding:5px 10px;font-family:Share Tech Mono,monospace;font-size:11px;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));box-shadow:0 4px 20px #0009,0 0 12px #00ff880f;pointer-events:auto;max-width:90vw;overflow-x:auto;flex-wrap:nowrap}.ag-breadcrumb::-webkit-scrollbar{height:3px}.ag-breadcrumb::-webkit-scrollbar-thumb{background:#0f83}.ag-breadcrumb-hint{font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:#6b728099;margin-right:4px;white-space:nowrap}.ag-breadcrumb-item{background:#6b72800f;border:1px solid rgba(107,114,128,.15);color:#6b7280;padding:3px 8px;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.06em;cursor:pointer;transition:all .15s ease;white-space:nowrap;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-breadcrumb-item:hover{background:#6b72801f;border-color:#6b72804d;color:#e0e0e0}.ag-breadcrumb-item.ag-breadcrumb-active{background:#00d4ff1a;border-color:#00d4ff66;color:#00d4ff;box-shadow:0 0 6px #00d4ff33}.ag-breadcrumb-item.ag-breadcrumb-component{color:#ff00ffb3;border-color:#ff00ff26}.ag-breadcrumb-item.ag-breadcrumb-component.ag-breadcrumb-active{background:#ff00ff1a;border-color:#f0f6;color:#f0f;box-shadow:0 0 6px #f0f3}.ag-breadcrumb-separator{color:#6b72804d;font-size:12px;-webkit-user-select:none;user-select:none;font-family:Share Tech Mono,monospace}.ag-breadcrumb-close{background:transparent;border:1px solid rgba(255,51,102,.3);color:#f369;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:11px;padding:0;line-height:1;transition:all .15s ease;margin-left:6px;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-breadcrumb-close:hover{background:#ff336626;border-color:#f36;color:#f36;box-shadow:0 0 6px #ff33664d}.ag-marker-bubble{display:flex;align-items:center;justify-content:center;width:26px;height:26px;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));color:#0a0a0f;font-family:Orbitron,monospace;font-size:10px;font-weight:700;box-shadow:0 0 8px #00000080,0 0 6px currentColor;pointer-events:auto;cursor:pointer;z-index:999997;transform:translate(-50%,-50%);animation:markerPop .2s ease-out;transition:filter .15s ease,transform .15s ease}.ag-marker-bubble:hover{transform:translate(-50%,-50%) scale(1.15);filter:brightness(1.2)}@keyframes markerPop{0%{transform:translate(-50%,-50%) scale(0) rotate(-10deg);opacity:0}60%{transform:translate(-50%,-50%) scale(1.2) rotate(3deg)}to{transform:translate(-50%,-50%) scale(1) rotate(0);opacity:1}}.ag-recording-hint{position:fixed;top:16px;left:50%;transform:translate(-50%);z-index:999999;pointer-events:none;display:none!important}@media (max-width: 768px){.ag-recording-hint{display:none!important}}.ag-hint-content{display:flex;align-items:center;gap:10px;background:#12121a;border:1px solid rgba(255,51,102,.4);color:#6b7280;padding:8px 16px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));box-shadow:0 0 12px #ff336626,0 4px 20px #00000080}.ag-hint-icon{animation:hintBlink 1s step-end infinite;filter:drop-shadow(0 0 4px #ff3366)}@keyframes hintBlink{0%,to{opacity:1}50%{opacity:.3}}.ag-hint-text kbd{display:inline-block;background:#00ff8814;border:1px solid rgba(0,255,136,.3);padding:1px 5px;font-family:Share Tech Mono,monospace;font-size:10px;color:#0f8;clip-path:polygon(0 2px,2px 0,calc(100% - 2px) 0,100% 2px,100% calc(100% - 2px),calc(100% - 2px) 100%,2px 100%,0 calc(100% - 2px))}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: InlineEditorComponent, selector: "ag-inline-editor", inputs: ["marker", "position"], outputs: ["save", "delete", "cancel"] }] });
1048
+ }
1049
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: OverlayComponent, decorators: [{
1050
+ type: Component,
1051
+ args: [{ selector: 'ag-overlay', standalone: false, template: "<!-- \u4E92\u52D5\u6355\u6349\u5C64 (\u7528\u65BC\u8655\u7406 disabled \u5143\u7D20) -->\n<div class=\"ag-click-overlay\" *ngIf=\"isRecording && !isMinimized\"></div>\n\n<!-- \u9AD8\u4EAE\u6846 -->\n<div class=\"ag-highlight\" *ngIf=\"isRecording && !isMinimized && hoveredNode\" [ngStyle]=\"highlightStyle\"></div>\n\n<!-- Tooltip -->\n<div class=\"ag-tooltip\" *ngIf=\"showTooltip && !isMinimized\" [ngStyle]=\"tooltipStyle\">\n {{ tooltipContent }}\n</div>\n\n<!-- \u7956\u5148\u9EB5\u5305\u5C51\u5C0E\u822A -->\n<div class=\"ag-breadcrumb\" *ngIf=\"showBreadcrumb && !isMinimized && ancestorBreadcrumbs.length > 1\"\n [ngStyle]=\"breadcrumbStyle\">\n <span class=\"ag-breadcrumb-hint\">\u5C64\u7D1A:</span>\n <ng-container *ngFor=\"let crumb of ancestorBreadcrumbs; let i = index; let last = last\">\n <button class=\"ag-breadcrumb-item\" [class.ag-breadcrumb-active]=\"i === selectedBreadcrumbIndex\"\n [class.ag-breadcrumb-component]=\"crumb.isComponent\" (click)=\"onBreadcrumbClick(crumb, i, $event)\"\n (dblclick)=\"onBreadcrumbDoubleClick(crumb, $event)\" [title]=\"'\u55AE\u64CA\u5207\u63DB / \u518D\u6B21\u55AE\u64CA\u78BA\u8A8D: ' + crumb.label\">\n {{ crumb.label }}\n </button>\n <span class=\"ag-breadcrumb-separator\" *ngIf=\"!last\">\u203A</span>\n </ng-container>\n <button class=\"ag-breadcrumb-close\" (click)=\"unlock()\" title=\"\u53D6\u6D88\u9396\u5B9A\">\u2715</button>\n</div>\n\n\n<!-- \u6A19\u8A18\u7DE8\u865F\u6C23\u6CE1 -->\n<div *ngFor=\"let marker of markers\" class=\"ag-marker-bubble\" [ngStyle]=\"getMarkerStyle(marker)\"\n (click)=\"onMarkerClick(marker, $event)\" title=\"\u9EDE\u64CA\u7DE8\u8F2F\">\n {{ marker.index }}\n</div>\n\n<!-- \u9304\u88FD\u72C0\u614B\u63D0\u793A -->\n<div class=\"ag-recording-hint\" *ngIf=\"isRecording && !isMinimized\">\n <div class=\"ag-hint-content\">\n <span class=\"ag-hint-icon\">\uD83D\uDD34</span>\n <span class=\"ag-hint-text\">Click to lock \u2022 Click again to mark \u2022 Use breadcrumb to select parent \u2022 <kbd>Esc</kbd> to\n stop</span>\n </div>\n</div>\n\n<!-- \u5167\u5D4C\u7DE8\u8F2F\u5668 -->\n<ag-inline-editor *ngIf=\"editingMarker && !isMinimized\" [marker]=\"editingMarker\" [position]=\"editorPosition\"\n (save)=\"onEditorSave($event)\" (delete)=\"onEditorDelete($event)\" (cancel)=\"onEditorCancel()\"></ag-inline-editor>", styles: [".ag-click-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:999990;cursor:crosshair;background:transparent;pointer-events:auto}.ag-highlight{pointer-events:none;border:1px solid #00ff88;background:#00ff880a;box-shadow:0 0 0 1px #00ff884d,0 0 10px #00ff8826,inset 0 0 10px #00ff880a}.ag-tooltip{background:#12121a;border:1px solid rgba(0,255,136,.25);color:#0f8;padding:5px 10px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;white-space:nowrap;pointer-events:none;box-shadow:0 0 8px #00ff8826,0 4px 16px #00000080;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-tooltip:before{content:\"> \";color:#00ff8880}.ag-breadcrumb{display:flex;align-items:center;gap:4px;background:#12121af5;border:1px solid #2a2a3a;backdrop-filter:blur(8px);padding:5px 10px;font-family:Share Tech Mono,monospace;font-size:11px;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));box-shadow:0 4px 20px #0009,0 0 12px #00ff880f;pointer-events:auto;max-width:90vw;overflow-x:auto;flex-wrap:nowrap}.ag-breadcrumb::-webkit-scrollbar{height:3px}.ag-breadcrumb::-webkit-scrollbar-thumb{background:#0f83}.ag-breadcrumb-hint{font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:#6b728099;margin-right:4px;white-space:nowrap}.ag-breadcrumb-item{background:#6b72800f;border:1px solid rgba(107,114,128,.15);color:#6b7280;padding:3px 8px;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.06em;cursor:pointer;transition:all .15s ease;white-space:nowrap;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-breadcrumb-item:hover{background:#6b72801f;border-color:#6b72804d;color:#e0e0e0}.ag-breadcrumb-item.ag-breadcrumb-active{background:#00d4ff1a;border-color:#00d4ff66;color:#00d4ff;box-shadow:0 0 6px #00d4ff33}.ag-breadcrumb-item.ag-breadcrumb-component{color:#ff00ffb3;border-color:#ff00ff26}.ag-breadcrumb-item.ag-breadcrumb-component.ag-breadcrumb-active{background:#ff00ff1a;border-color:#f0f6;color:#f0f;box-shadow:0 0 6px #f0f3}.ag-breadcrumb-separator{color:#6b72804d;font-size:12px;-webkit-user-select:none;user-select:none;font-family:Share Tech Mono,monospace}.ag-breadcrumb-close{background:transparent;border:1px solid rgba(255,51,102,.3);color:#f369;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:11px;padding:0;line-height:1;transition:all .15s ease;margin-left:6px;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-breadcrumb-close:hover{background:#ff336626;border-color:#f36;color:#f36;box-shadow:0 0 6px #ff33664d}.ag-marker-bubble{display:flex;align-items:center;justify-content:center;width:26px;height:26px;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));color:#0a0a0f;font-family:Orbitron,monospace;font-size:10px;font-weight:700;box-shadow:0 0 8px #00000080,0 0 6px currentColor;pointer-events:auto;cursor:pointer;z-index:999997;transform:translate(-50%,-50%);animation:markerPop .2s ease-out;transition:filter .15s ease,transform .15s ease}.ag-marker-bubble:hover{transform:translate(-50%,-50%) scale(1.15);filter:brightness(1.2)}@keyframes markerPop{0%{transform:translate(-50%,-50%) scale(0) rotate(-10deg);opacity:0}60%{transform:translate(-50%,-50%) scale(1.2) rotate(3deg)}to{transform:translate(-50%,-50%) scale(1) rotate(0);opacity:1}}.ag-recording-hint{position:fixed;top:16px;left:50%;transform:translate(-50%);z-index:999999;pointer-events:none;display:none!important}@media (max-width: 768px){.ag-recording-hint{display:none!important}}.ag-hint-content{display:flex;align-items:center;gap:10px;background:#12121a;border:1px solid rgba(255,51,102,.4);color:#6b7280;padding:8px 16px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));box-shadow:0 0 12px #ff336626,0 4px 20px #00000080}.ag-hint-icon{animation:hintBlink 1s step-end infinite;filter:drop-shadow(0 0 4px #ff3366)}@keyframes hintBlink{0%,to{opacity:1}50%{opacity:.3}}.ag-hint-text kbd{display:inline-block;background:#00ff8814;border:1px solid rgba(0,255,136,.3);padding:1px 5px;font-family:Share Tech Mono,monospace;font-size:10px;color:#0f8;clip-path:polygon(0 2px,2px 0,calc(100% - 2px) 0,100% 2px,100% calc(100% - 2px),calc(100% - 2px) 100%,2px 100%,0 calc(100% - 2px))}\n"] }]
1052
+ }], ctorParameters: () => [{ type: ComponentWalkerService }], propDecorators: { markers: [{
1053
+ type: Input
1054
+ }], settings: [{
1055
+ type: Input
1056
+ }], isRecording: [{
1057
+ type: Input
1058
+ }], isMinimized: [{
1059
+ type: Input
1060
+ }], markerAdded: [{
1061
+ type: Output
1062
+ }], componentSelected: [{
1063
+ type: Output
1064
+ }], componentHovered: [{
1065
+ type: Output
1066
+ }], recordingChanged: [{
1067
+ type: Output
1068
+ }], markerDeleted: [{
1069
+ type: Output
1070
+ }], onMouseMove: [{
1071
+ type: HostListener,
1072
+ args: ['document:mousemove', ['$event']]
1073
+ }], onEscape: [{
1074
+ type: HostListener,
1075
+ args: ['document:keydown.escape']
1076
+ }], onKeyDown: [{
1077
+ type: HostListener,
1078
+ args: ['document:keydown', ['$event']]
1079
+ }] } });
1080
+
1081
+ /**
1082
+ * DataSanitizerService
1083
+ *
1084
+ * 資料清洗服務:將組件資料轉換為 AI 友好的格式
1085
+ *
1086
+ * 清洗規則:
1087
+ * 1. 移除 Angular 內部屬性 (__ngContext__, ɵcmp 等)
1088
+ * 2. 將 Function 類型轉為描述字串
1089
+ * 3. 過濾 Observable/Subject(只保留類型名稱)
1090
+ * 4. 過濾大型資料(Base64 > 1KB)
1091
+ * 5. 避免循環引用
1092
+ */
1093
+ class DataSanitizerService {
1094
+ /** Angular 內部屬性前綴 */
1095
+ INTERNAL_PREFIXES = ['__', 'ɵ', 'ng'];
1096
+ /** 最大字串長度 */
1097
+ MAX_STRING_LENGTH = 1024;
1098
+ /** 最大陣列長度 */
1099
+ MAX_ARRAY_LENGTH = 20;
1100
+ /** 最大物件深度 */
1101
+ MAX_DEPTH = 3;
1102
+ /**
1103
+ * 清洗單一值
1104
+ */
1105
+ sanitize(value, depth = 0) {
1106
+ // 深度限制
1107
+ if (depth > this.MAX_DEPTH) {
1108
+ return '[MaxDepthReached]';
1109
+ }
1110
+ // 處理 null/undefined
1111
+ if (value === null)
1112
+ return null;
1113
+ if (value === undefined)
1114
+ return undefined;
1115
+ // 處理原始類型
1116
+ if (typeof value === 'boolean' || typeof value === 'number') {
1117
+ return value;
1118
+ }
1119
+ // 處理字串
1120
+ if (typeof value === 'string') {
1121
+ return this.sanitizeString(value);
1122
+ }
1123
+ // 處理函數
1124
+ if (typeof value === 'function') {
1125
+ return this.sanitizeFunction(value);
1126
+ }
1127
+ // 處理 Symbol
1128
+ if (typeof value === 'symbol') {
1129
+ return `[Symbol: ${value.description ?? 'unknown'}]`;
1130
+ }
1131
+ // 處理 BigInt
1132
+ if (typeof value === 'bigint') {
1133
+ return value.toString();
1134
+ }
1135
+ // 處理陣列
1136
+ if (Array.isArray(value)) {
1137
+ return this.sanitizeArray(value, depth);
1138
+ }
1139
+ // 處理物件
1140
+ if (typeof value === 'object') {
1141
+ return this.sanitizeObject(value, depth);
1142
+ }
1143
+ return String(value);
1144
+ }
1145
+ /**
1146
+ * 清洗字串
1147
+ */
1148
+ sanitizeString(value) {
1149
+ // 檢測 Base64 圖片
1150
+ if (value.startsWith('data:image/')) {
1151
+ return '[Base64Image]';
1152
+ }
1153
+ // 檢測過長字串
1154
+ if (value.length > this.MAX_STRING_LENGTH) {
1155
+ return `[String: ${value.length} chars, truncated: "${value.substring(0, 100)}..."]`;
1156
+ }
1157
+ return value;
1158
+ }
1159
+ /**
1160
+ * 清洗函數
1161
+ */
1162
+ sanitizeFunction(fn) {
1163
+ const name = fn.name || 'anonymous';
1164
+ // 嘗試取得函數簽名
1165
+ const fnString = fn.toString();
1166
+ const argsMatch = fnString.match(/\(([^)]*)\)/);
1167
+ const args = argsMatch ? argsMatch[1] : '';
1168
+ return `[Function: ${name}(${args})]`;
1169
+ }
1170
+ /**
1171
+ * 清洗陣列
1172
+ */
1173
+ sanitizeArray(arr, depth) {
1174
+ if (arr.length === 0) {
1175
+ return [];
1176
+ }
1177
+ if (arr.length > this.MAX_ARRAY_LENGTH) {
1178
+ const sample = arr.slice(0, 5).map((item) => this.sanitize(item, depth + 1));
1179
+ return {
1180
+ __type: 'TruncatedArray',
1181
+ length: arr.length,
1182
+ sample,
1183
+ };
1184
+ }
1185
+ return arr.map((item) => this.sanitize(item, depth + 1));
1186
+ }
1187
+ /**
1188
+ * 清洗物件
1189
+ */
1190
+ sanitizeObject(obj, depth) {
1191
+ // 處理特殊物件類型
1192
+ if (this.isObservable(obj)) {
1193
+ return '[Observable]';
1194
+ }
1195
+ if (this.isSubject(obj)) {
1196
+ return '[Subject]';
1197
+ }
1198
+ if (this.isPromise(obj)) {
1199
+ return '[Promise]';
1200
+ }
1201
+ if (obj instanceof Date) {
1202
+ return obj.toISOString();
1203
+ }
1204
+ if (obj instanceof RegExp) {
1205
+ return obj.toString();
1206
+ }
1207
+ if (obj instanceof Error) {
1208
+ return `[Error: ${obj.message}]`;
1209
+ }
1210
+ if (obj instanceof HTMLElement) {
1211
+ return `[HTMLElement: <${obj.tagName.toLowerCase()}>]`;
1212
+ }
1213
+ if (obj instanceof Event) {
1214
+ return `[Event: ${obj.type}]`;
1215
+ }
1216
+ // 處理一般物件
1217
+ const result = {};
1218
+ const keys = Object.keys(obj);
1219
+ for (const key of keys) {
1220
+ // 跳過內部屬性
1221
+ if (this.isInternalProperty(key)) {
1222
+ continue;
1223
+ }
1224
+ try {
1225
+ const value = obj[key];
1226
+ result[key] = this.sanitize(value, depth + 1);
1227
+ }
1228
+ catch {
1229
+ result[key] = '[AccessDenied]';
1230
+ }
1231
+ }
1232
+ // 如果物件為空,返回類型名稱
1233
+ if (Object.keys(result).length === 0) {
1234
+ return `[Object: ${obj.constructor.name}]`;
1235
+ }
1236
+ return result;
1237
+ }
1238
+ /**
1239
+ * 檢查是否為內部屬性
1240
+ */
1241
+ isInternalProperty(key) {
1242
+ return this.INTERNAL_PREFIXES.some((prefix) => key.startsWith(prefix));
1243
+ }
1244
+ /**
1245
+ * 檢查是否為 RxJS Observable
1246
+ */
1247
+ isObservable(obj) {
1248
+ return (obj !== null &&
1249
+ typeof obj === 'object' &&
1250
+ typeof obj.subscribe === 'function' &&
1251
+ !this.isSubject(obj));
1252
+ }
1253
+ /**
1254
+ * 檢查是否為 RxJS Subject
1255
+ */
1256
+ isSubject(obj) {
1257
+ return (obj !== null &&
1258
+ typeof obj === 'object' &&
1259
+ typeof obj.next === 'function' &&
1260
+ typeof obj.subscribe === 'function');
1261
+ }
1262
+ /**
1263
+ * 檢查是否為 Promise
1264
+ */
1265
+ isPromise(obj) {
1266
+ return (obj !== null &&
1267
+ typeof obj === 'object' &&
1268
+ typeof obj.then === 'function');
1269
+ }
1270
+ /**
1271
+ * 批量清洗 Record
1272
+ */
1273
+ sanitizeRecord(record) {
1274
+ const result = {};
1275
+ for (const [key, value] of Object.entries(record)) {
1276
+ if (!this.isInternalProperty(key)) {
1277
+ result[key] = this.sanitize(value);
1278
+ }
1279
+ }
1280
+ return result;
1281
+ }
1282
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: DataSanitizerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1283
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: DataSanitizerService, providedIn: 'root' });
1284
+ }
1285
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: DataSanitizerService, decorators: [{
1286
+ type: Injectable,
1287
+ args: [{
1288
+ providedIn: 'root',
1289
+ }]
1290
+ }] });
1291
+
1292
+ /**
1293
+ * PromptGeneratorService
1294
+ *
1295
+ * 提示生成服務:根據 OutputDetail 模式生成不同詳細程度的輸出
1296
+ *
1297
+ * Modes:
1298
+ * - compact: 最簡潔,只有元素類型和 feedback
1299
+ * - standard: 標準,包含 DOM Path、Position、基本 Styles
1300
+ * - detailed: 詳細,包含所有屬性和上下文
1301
+ * - forensic: 完整,包含所有可用資訊(舊版相容)
1302
+ */
1303
+ class PromptGeneratorService {
1304
+ dataSanitizer;
1305
+ constructor(dataSanitizer) {
1306
+ this.dataSanitizer = dataSanitizer;
1307
+ }
1308
+ /**
1309
+ * 生成 Page Feedback 輸出(多標記)
1310
+ */
1311
+ generatePageFeedback(markers, options) {
1312
+ const lines = [];
1313
+ // 環境資訊(standard 及以上)
1314
+ if (options.outputDetail !== 'compact') {
1315
+ lines.push(`## Page Feedback: ${this.getPathFromUrl(options.pageUrl)}`);
1316
+ lines.push('');
1317
+ lines.push('**Environment:**');
1318
+ if (options.viewport) {
1319
+ lines.push(`- Viewport: ${options.viewport.width}×${options.viewport.height}`);
1320
+ }
1321
+ if (options.pageUrl) {
1322
+ lines.push(`- URL: ${options.pageUrl}`);
1323
+ }
1324
+ if (options.userAgent && options.outputDetail !== 'standard') {
1325
+ lines.push(`- User Agent: ${options.userAgent}`);
1326
+ }
1327
+ if (options.timestamp) {
1328
+ lines.push(`- Timestamp: ${new Date(options.timestamp).toISOString()}`);
1329
+ }
1330
+ lines.push('');
1331
+ lines.push('---');
1332
+ lines.push('');
1333
+ }
1334
+ else {
1335
+ lines.push(`## Page Feedback`);
1336
+ lines.push('');
1337
+ }
1338
+ // 各個標記
1339
+ markers.forEach((marker, index) => {
1340
+ const markerOutput = this.generateMarkerOutput(marker.target, marker.intent, index + 1, options.outputDetail);
1341
+ lines.push(markerOutput);
1342
+ lines.push('');
1343
+ });
1344
+ return lines.join('\n');
1345
+ }
1346
+ /**
1347
+ * 生成單個標記的輸出
1348
+ */
1349
+ generateMarkerOutput(node, intent, index, outputDetail) {
1350
+ switch (outputDetail) {
1351
+ case 'compact':
1352
+ return this.generateCompact(node, intent, index);
1353
+ case 'standard':
1354
+ return this.generateStandard(node, intent, index);
1355
+ case 'detailed':
1356
+ return this.generateDetailed(node, intent, index);
1357
+ case 'forensic':
1358
+ default:
1359
+ return this.generateForensic(node, intent, index);
1360
+ }
1361
+ }
1362
+ /**
1363
+ * Compact 模式:最簡潔
1364
+ */
1365
+ generateCompact(node, intent, index) {
1366
+ const elementType = this.getElementType(node);
1367
+ const lines = [
1368
+ `### ${index}. ${elementType}`,
1369
+ ];
1370
+ if (node.selector) {
1371
+ lines.push(`**Selector:** \`<${node.selector}>\``);
1372
+ }
1373
+ if (intent) {
1374
+ lines.push(`**Feedback:** ${intent}`);
1375
+ }
1376
+ return lines.join('\n');
1377
+ }
1378
+ /**
1379
+ * Standard 模式:標準
1380
+ */
1381
+ generateStandard(node, intent, index) {
1382
+ const elementType = this.getElementType(node);
1383
+ const lines = [
1384
+ `### ${index}. ${elementType}`,
1385
+ `**Full DOM Path:** ${this.formatDomPath(node.domPath)}`,
1386
+ ];
1387
+ // CSS Classes
1388
+ const cssClasses = this.extractCssClasses(node);
1389
+ if (cssClasses.length > 0) {
1390
+ lines.push(`**CSS Classes:** ${cssClasses.join(', ')}`);
1391
+ }
1392
+ // Position
1393
+ lines.push(`**Position:** x:${Math.round(node.rect.x)}, y:${Math.round(node.rect.y)} (${Math.round(node.rect.width)}×${Math.round(node.rect.height)}px)`);
1394
+ // 基本 Styles(只顯示重要的)
1395
+ const keyStyles = this.extractKeyStyles(node.computedStyles);
1396
+ if (keyStyles) {
1397
+ lines.push(`**Computed Styles:** ${keyStyles}`);
1398
+ }
1399
+ if (intent) {
1400
+ lines.push(`**Feedback:** ${intent}`);
1401
+ }
1402
+ return lines.join('\n');
1403
+ }
1404
+ /**
1405
+ * Detailed 模式:詳細
1406
+ */
1407
+ generateDetailed(node, intent, index) {
1408
+ const elementType = this.getElementType(node);
1409
+ const lines = [
1410
+ `### ${index}. ${elementType}`,
1411
+ `**Full DOM Path:** ${this.formatDomPath(node.domPath)}`,
1412
+ ];
1413
+ // CSS Classes
1414
+ const cssClasses = this.extractCssClasses(node);
1415
+ if (cssClasses.length > 0) {
1416
+ lines.push(`**CSS Classes:** ${cssClasses.join(', ')}`);
1417
+ }
1418
+ // Position
1419
+ lines.push(`**Position:** x:${Math.round(node.rect.x)}, y:${Math.round(node.rect.y)} (${Math.round(node.rect.width)}×${Math.round(node.rect.height)}px)`);
1420
+ // Annotation position
1421
+ const annotationX = ((node.rect.x + node.rect.width / 2) / window.innerWidth * 100).toFixed(1);
1422
+ const annotationY = Math.round(node.rect.y + node.rect.height / 2);
1423
+ lines.push(`**Annotation at:** ${annotationX}% from left, ${annotationY}px from top`);
1424
+ // Context (text content)
1425
+ const textContent = this.getTextContent(node);
1426
+ if (textContent) {
1427
+ lines.push(`**Context:** ${textContent}`);
1428
+ }
1429
+ // Computed Styles
1430
+ const allStyles = this.formatAllStyles(node.computedStyles);
1431
+ if (allStyles) {
1432
+ lines.push(`**Computed Styles:** ${allStyles}`);
1433
+ }
1434
+ // Accessibility
1435
+ const accessibility = this.getAccessibility(node);
1436
+ if (accessibility) {
1437
+ lines.push(`**Accessibility:** ${accessibility}`);
1438
+ }
1439
+ // Nearby Elements
1440
+ const nearbyElements = this.getNearbyElements(node);
1441
+ if (nearbyElements) {
1442
+ lines.push(`**Nearby Elements:** ${nearbyElements}`);
1443
+ }
1444
+ if (intent) {
1445
+ lines.push(`**Feedback:** ${intent}`);
1446
+ }
1447
+ return lines.join('\n');
1448
+ }
1449
+ /**
1450
+ * Forensic 模式:完整(舊版相容格式)
1451
+ */
1452
+ generateForensic(node, intent, index) {
1453
+ const elementType = this.getElementType(node);
1454
+ const lines = [
1455
+ `### ${index}. ${elementType}`,
1456
+ `**Full DOM Path:** ${this.formatDomPath(node.domPath)}`,
1457
+ ];
1458
+ // CSS Classes
1459
+ const cssClasses = this.extractCssClasses(node);
1460
+ if (cssClasses.length > 0) {
1461
+ lines.push(`**CSS Classes:** ${cssClasses.join(', ')}`);
1462
+ }
1463
+ // Position
1464
+ lines.push(`**Position:** x:${Math.round(node.rect.x)}, y:${Math.round(node.rect.y)} (${Math.round(node.rect.width)}×${Math.round(node.rect.height)}px)`);
1465
+ // Annotation position
1466
+ const annotationX = ((node.rect.x + node.rect.width / 2) / window.innerWidth * 100).toFixed(1);
1467
+ const annotationY = Math.round(node.rect.y + node.rect.height / 2);
1468
+ lines.push(`**Annotation at:** ${annotationX}% from left, ${annotationY}px from top`);
1469
+ // Selected text / Context
1470
+ const textContent = this.getTextContent(node);
1471
+ if (textContent) {
1472
+ if (textContent.length > 100) {
1473
+ lines.push(`**Selected text:** "${textContent}"`);
1474
+ }
1475
+ else {
1476
+ lines.push(`**Context:** ${textContent}`);
1477
+ }
1478
+ }
1479
+ // Full Computed Styles
1480
+ const allStyles = this.formatAllStyles(node.computedStyles);
1481
+ if (allStyles) {
1482
+ lines.push(`**Computed Styles:** ${allStyles}`);
1483
+ }
1484
+ // Accessibility
1485
+ const accessibility = this.getAccessibility(node);
1486
+ if (accessibility) {
1487
+ lines.push(`**Accessibility:** ${accessibility}`);
1488
+ }
1489
+ // Nearby Elements
1490
+ const nearbyElements = this.getNearbyElements(node);
1491
+ if (nearbyElements) {
1492
+ lines.push(`**Nearby Elements:** ${nearbyElements}`);
1493
+ }
1494
+ if (intent) {
1495
+ lines.push(`**Feedback:** ${intent}`);
1496
+ }
1497
+ return lines.join('\n');
1498
+ }
1499
+ // ==================== 輔助方法 ====================
1500
+ /**
1501
+ * 生成完整的 AI Prompt(舊版兼容)
1502
+ */
1503
+ generatePrompt(annotation) {
1504
+ return this.generateForensic(annotation.target, annotation.intent, 1);
1505
+ }
1506
+ /**
1507
+ * 僅生成組件資訊(舊版兼容)
1508
+ */
1509
+ generateComponentInfo(node) {
1510
+ return this.generateForensic(node, '', 1);
1511
+ }
1512
+ getPathFromUrl(url) {
1513
+ if (!url)
1514
+ return '/';
1515
+ try {
1516
+ const urlObj = new URL(url);
1517
+ return urlObj.pathname || '/';
1518
+ }
1519
+ catch {
1520
+ return '/';
1521
+ }
1522
+ }
1523
+ getElementType(node) {
1524
+ // 嘗試從 DOM 元素獲取更詳細的類型描述
1525
+ const tag = node.domElement.tagName.toLowerCase();
1526
+ const role = node.domElement.getAttribute('role');
1527
+ const ariaLabel = node.domElement.getAttribute('aria-label');
1528
+ const textContent = node.domElement.textContent?.trim().substring(0, 30);
1529
+ if (node.displayName && node.displayName !== tag) {
1530
+ // Angular 組件
1531
+ return `${node.displayName}`;
1532
+ }
1533
+ if (role) {
1534
+ return `${role}: "${textContent || tag}"`;
1535
+ }
1536
+ if (tag === 'button') {
1537
+ return `button "${textContent || '(no text)'}"`;
1538
+ }
1539
+ if (tag === 'a') {
1540
+ return `link "${textContent || ariaLabel || '(no text)'}"`;
1541
+ }
1542
+ if (tag === 'input') {
1543
+ const type = node.domElement.getAttribute('type') || 'text';
1544
+ return `input[${type}]`;
1545
+ }
1546
+ if (tag === 'p') {
1547
+ return `paragraph: "${textContent ? textContent.substring(0, 40) + '...' : ''}"`;
1548
+ }
1549
+ if (tag === 'section' || tag === 'div') {
1550
+ const className = node.domElement.className;
1551
+ if (className && typeof className === 'string') {
1552
+ const mainClass = className.split(' ')[0];
1553
+ return `${tag}.${mainClass}`;
1554
+ }
1555
+ }
1556
+ return node.selector ? `<${node.selector}>` : tag;
1557
+ }
1558
+ formatDomPath(domPath) {
1559
+ return domPath;
1560
+ }
1561
+ extractCssClasses(node) {
1562
+ const className = node.domElement.className;
1563
+ if (!className || typeof className !== 'string')
1564
+ return [];
1565
+ return className.split(' ').filter(c => c.trim());
1566
+ }
1567
+ extractKeyStyles(styles) {
1568
+ const keyProps = ['color', 'background-color', 'font-size', 'font-weight', 'display', 'position'];
1569
+ const parts = [];
1570
+ for (const prop of keyProps) {
1571
+ if (styles[prop] && styles[prop] !== 'none' && styles[prop] !== 'normal') {
1572
+ parts.push(`${prop}: ${styles[prop]}`);
1573
+ }
1574
+ }
1575
+ return parts.join('; ');
1576
+ }
1577
+ formatAllStyles(styles) {
1578
+ const parts = [];
1579
+ for (const [prop, value] of Object.entries(styles)) {
1580
+ if (value && value !== 'none' && value !== 'normal' && value !== 'auto') {
1581
+ parts.push(`${prop}: ${value}`);
1582
+ }
1583
+ }
1584
+ return parts.join('; ');
1585
+ }
1586
+ getTextContent(node) {
1587
+ const text = node.domElement.textContent?.trim() || '';
1588
+ if (text.length > 200) {
1589
+ return text.substring(0, 200) + '...';
1590
+ }
1591
+ return text;
1592
+ }
1593
+ getAccessibility(node) {
1594
+ const parts = [];
1595
+ if (node.domElement.getAttribute('tabindex') !== null) {
1596
+ parts.push('focusable');
1597
+ }
1598
+ const ariaLabel = node.domElement.getAttribute('aria-label');
1599
+ if (ariaLabel) {
1600
+ parts.push(`aria-label: "${ariaLabel}"`);
1601
+ }
1602
+ const role = node.domElement.getAttribute('role');
1603
+ if (role) {
1604
+ parts.push(`role: ${role}`);
1605
+ }
1606
+ return parts.join(', ');
1607
+ }
1608
+ getNearbyElements(node) {
1609
+ const parent = node.domElement.parentElement;
1610
+ if (!parent)
1611
+ return '';
1612
+ const siblings = Array.from(parent.children);
1613
+ const nearby = [];
1614
+ siblings.forEach((sibling) => {
1615
+ if (sibling !== node.domElement) {
1616
+ const tag = sibling.tagName.toLowerCase();
1617
+ const className = sibling.className;
1618
+ const text = sibling.textContent?.trim().substring(0, 20);
1619
+ const classStr = className && typeof className === 'string' ? `.${className.split(' ')[0]}` : '';
1620
+ nearby.push(`${tag}${classStr}${text ? ` "${text}"` : ''}`);
1621
+ }
1622
+ });
1623
+ if (nearby.length > 5) {
1624
+ return nearby.slice(0, 3).join(', ') + ` (${siblings.length} total in .${parent.className?.split(' ')[0] || 'parent'})`;
1625
+ }
1626
+ return nearby.join(', ');
1627
+ }
1628
+ escapeMarkdown(text) {
1629
+ return text
1630
+ .replace(/\|/g, '\\|')
1631
+ .replace(/`/g, '\\`')
1632
+ .replace(/\*/g, '\\*')
1633
+ .replace(/\n/g, ' ');
1634
+ }
1635
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: PromptGeneratorService, deps: [{ token: DataSanitizerService }], target: i0.ɵɵFactoryTarget.Injectable });
1636
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: PromptGeneratorService, providedIn: 'root' });
1637
+ }
1638
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: PromptGeneratorService, decorators: [{
1639
+ type: Injectable,
1640
+ args: [{
1641
+ providedIn: 'root',
1642
+ }]
1643
+ }], ctorParameters: () => [{ type: DataSanitizerService }] });
1644
+
1645
+ class McpService {
1646
+ http;
1647
+ API_URL = 'http://localhost:4747';
1648
+ _status = new BehaviorSubject({ connected: false });
1649
+ status$ = this._status.asObservable();
1650
+ // Store annotations state locally for UI updates
1651
+ _annotations = new BehaviorSubject([]);
1652
+ annotations$ = this._annotations.asObservable();
1653
+ constructor(http) {
1654
+ this.http = http;
1655
+ // Attempt initial connection
1656
+ this.checkConnection();
1657
+ }
1658
+ /**
1659
+ * Check if the MCP server is reachable
1660
+ */
1661
+ async checkConnection() {
1662
+ try {
1663
+ await firstValueFrom(this.http.get(`${this.API_URL}/status`));
1664
+ this.updateStatus({ connected: true, lastError: undefined });
1665
+ return true;
1666
+ }
1667
+ catch (error) {
1668
+ this.updateStatus({ connected: false, lastError: 'Could not connect to MCP server' });
1669
+ return false;
1670
+ }
1671
+ }
1672
+ /**
1673
+ * Establish a new session or restore existing one
1674
+ */
1675
+ async connect(existingSessionId) {
1676
+ if (!await this.checkConnection()) {
1677
+ throw new Error('MCP Server not reachable');
1678
+ }
1679
+ const sessionId = existingSessionId || v4();
1680
+ // Register session - simulating for now, or real call if API exists
1681
+ // In real agentation-mcp, we heavily rely on the POST /session or implicit creation
1682
+ // For now we assume we just start using a session ID.
1683
+ this.updateStatus({ connected: true, sessionId });
1684
+ this.startPolling(sessionId);
1685
+ return sessionId;
1686
+ }
1687
+ /**
1688
+ * Send an annotation to the MCP server
1689
+ */
1690
+ async sendAnnotation(annotation) {
1691
+ const currentStatus = this._status.value;
1692
+ if (!currentStatus.connected || !currentStatus.sessionId) {
1693
+ throw new Error('Not connected to MCP server');
1694
+ }
1695
+ const mcpAnnotation = {
1696
+ ...annotation,
1697
+ id: v4(),
1698
+ sessionId: currentStatus.sessionId,
1699
+ url: window.location.href,
1700
+ status: 'pending'
1701
+ };
1702
+ try {
1703
+ await firstValueFrom(this.http.post(`${this.API_URL}/annotations`, mcpAnnotation));
1704
+ // Optimistic update
1705
+ const currentList = this._annotations.value;
1706
+ this._annotations.next([...currentList, mcpAnnotation]);
1707
+ }
1708
+ catch (error) {
1709
+ console.error('Failed to send annotation', error);
1710
+ throw error;
1711
+ }
1712
+ }
1713
+ /**
1714
+ * Poll for updates (e.g., resolve/dismiss status from agent)
1715
+ */
1716
+ startPolling(sessionId) {
1717
+ // Simple polling every 2 seconds
1718
+ timer(0, 2000).pipe(switchMap(() => this.http.get(`${this.API_URL}/sessions/${sessionId}/annotations`).pipe(catchError(() => of([])) // Handle errors silently in polling
1719
+ ))).subscribe(annotations => {
1720
+ if (annotations && annotations.length > 0) {
1721
+ this._annotations.next(annotations);
1722
+ }
1723
+ });
1724
+ }
1725
+ updateStatus(newStatus) {
1726
+ this._status.next({ ...this._status.value, ...newStatus });
1727
+ }
1728
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: McpService, deps: [{ token: i1$1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
1729
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: McpService, providedIn: 'root' });
1730
+ }
1731
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: McpService, decorators: [{
1732
+ type: Injectable,
1733
+ args: [{
1734
+ providedIn: 'root'
1735
+ }]
1736
+ }], ctorParameters: () => [{ type: i1$1.HttpClient }] });
1737
+
1738
+ class AnnotationPanelComponent {
1739
+ promptGenerator;
1740
+ mcpService;
1741
+ /** 選中的組件節點 */
1742
+ selectedNode = null;
1743
+ /** 面板關閉時觸發 */
1744
+ closed = new EventEmitter();
1745
+ /** 使用者輸入的意圖 */
1746
+ userIntent = '';
1747
+ /** 是否已複製到剪貼板 */
1748
+ copied = false;
1749
+ /** 是否已發送到 MCP */
1750
+ sent = false;
1751
+ /** 展開的區塊 */
1752
+ expandedSections = {
1753
+ inputs: true,
1754
+ outputs: true,
1755
+ properties: false,
1756
+ styles: false,
1757
+ };
1758
+ mcpStatus$;
1759
+ constructor(promptGenerator, mcpService) {
1760
+ this.promptGenerator = promptGenerator;
1761
+ this.mcpService = mcpService;
1762
+ this.mcpStatus$ = this.mcpService.status$;
1763
+ }
1764
+ /**
1765
+ * 發送標註給 Agent (MCP)
1766
+ */
1767
+ async sendToAgent() {
1768
+ if (!this.selectedNode)
1769
+ return;
1770
+ const annotation = {
1771
+ target: this.selectedNode,
1772
+ intent: this.userIntent || '(No specific intent provided)',
1773
+ timestamp: Date.now(),
1774
+ };
1775
+ try {
1776
+ await this.mcpService.sendAnnotation(annotation);
1777
+ this.sent = true;
1778
+ setTimeout(() => (this.sent = false), 2000);
1779
+ }
1780
+ catch (err) {
1781
+ console.error('[ng-directive-zero] Failed to send annotation:', err);
1782
+ // TODO: Error feedback to user
1783
+ }
1784
+ }
1785
+ /**
1786
+ * 複製 Markdown 到剪貼板
1787
+ */
1788
+ async copyToClipboard() {
1789
+ if (!this.selectedNode)
1790
+ return;
1791
+ const annotation = {
1792
+ target: this.selectedNode,
1793
+ intent: this.userIntent || '(No specific intent provided)',
1794
+ timestamp: Date.now(),
1795
+ };
1796
+ const markdown = this.promptGenerator.generatePrompt(annotation);
1797
+ try {
1798
+ await navigator.clipboard.writeText(markdown);
1799
+ this.copied = true;
1800
+ setTimeout(() => (this.copied = false), 2000);
1801
+ }
1802
+ catch (err) {
1803
+ console.error('[ng-directive-zero] Failed to copy:', err);
1804
+ // Fallback
1805
+ this.fallbackCopy(markdown);
1806
+ }
1807
+ }
1808
+ /**
1809
+ * 僅複製組件資訊(不含使用者意圖)
1810
+ */
1811
+ async copyComponentInfo() {
1812
+ if (!this.selectedNode)
1813
+ return;
1814
+ const markdown = this.promptGenerator.generateComponentInfo(this.selectedNode);
1815
+ try {
1816
+ await navigator.clipboard.writeText(markdown);
1817
+ this.copied = true;
1818
+ setTimeout(() => (this.copied = false), 2000);
1819
+ }
1820
+ catch (err) {
1821
+ console.error('[ng-directive-zero] Failed to copy:', err);
1822
+ this.fallbackCopy(markdown);
1823
+ }
1824
+ }
1825
+ /**
1826
+ * 清除選擇
1827
+ */
1828
+ clearSelection() {
1829
+ this.selectedNode = null;
1830
+ this.userIntent = '';
1831
+ this.closed.emit();
1832
+ }
1833
+ /**
1834
+ * 切換區塊展開狀態
1835
+ */
1836
+ toggleSection(section) {
1837
+ this.expandedSections[section] = !this.expandedSections[section];
1838
+ }
1839
+ /**
1840
+ * 獲取 Input 的條目
1841
+ */
1842
+ getInputEntries() {
1843
+ if (!this.selectedNode)
1844
+ return [];
1845
+ return Object.entries(this.selectedNode.inputs).map(([key, value]) => ({
1846
+ key,
1847
+ value,
1848
+ }));
1849
+ }
1850
+ /**
1851
+ * 獲取公開屬性條目
1852
+ */
1853
+ getPropertyEntries() {
1854
+ if (!this.selectedNode)
1855
+ return [];
1856
+ return Object.entries(this.selectedNode.publicProperties).map(([key, value]) => ({
1857
+ key,
1858
+ value,
1859
+ }));
1860
+ }
1861
+ /**
1862
+ * 獲取樣式條目
1863
+ */
1864
+ getStyleEntries() {
1865
+ if (!this.selectedNode)
1866
+ return [];
1867
+ return Object.entries(this.selectedNode.computedStyles)
1868
+ .filter(([, value]) => value && value !== 'none' && value !== 'normal')
1869
+ .map(([key, value]) => ({ key, value }));
1870
+ }
1871
+ /**
1872
+ * 格式化值為顯示字串
1873
+ */
1874
+ formatValue(value) {
1875
+ if (value === null)
1876
+ return 'null';
1877
+ if (value === undefined)
1878
+ return 'undefined';
1879
+ if (typeof value === 'string')
1880
+ return `"${value}"`;
1881
+ if (typeof value === 'object')
1882
+ return JSON.stringify(value);
1883
+ return String(value);
1884
+ }
1885
+ /**
1886
+ * 獲取值的類型顏色
1887
+ */
1888
+ getValueColor(value) {
1889
+ if (value === null || value === undefined)
1890
+ return '#808080';
1891
+ if (typeof value === 'string')
1892
+ return '#98c379';
1893
+ if (typeof value === 'number')
1894
+ return '#d19a66';
1895
+ if (typeof value === 'boolean')
1896
+ return '#56b6c2';
1897
+ return '#abb2bf';
1898
+ }
1899
+ /**
1900
+ * Fallback 複製方式
1901
+ */
1902
+ fallbackCopy(text) {
1903
+ const textarea = document.createElement('textarea');
1904
+ textarea.value = text;
1905
+ textarea.style.position = 'fixed';
1906
+ textarea.style.opacity = '0';
1907
+ document.body.appendChild(textarea);
1908
+ textarea.select();
1909
+ document.execCommand('copy');
1910
+ document.body.removeChild(textarea);
1911
+ this.copied = true;
1912
+ setTimeout(() => (this.copied = false), 2000);
1913
+ }
1914
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AnnotationPanelComponent, deps: [{ token: PromptGeneratorService }, { token: McpService }], target: i0.ɵɵFactoryTarget.Component });
1915
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: AnnotationPanelComponent, isStandalone: false, selector: "ag-annotation-panel", inputs: { selectedNode: "selectedNode" }, outputs: { closed: "closed" }, ngImport: i0, template: "<div class=\"ag-panel\" *ngIf=\"selectedNode\">\n <!-- \u6A19\u984C\u5217 -->\n <header class=\"ag-panel-header\">\n <div class=\"ag-panel-title\">\n <span class=\"ag-component-icon\">\uD83D\uDCE6</span>\n <span class=\"ag-component-name\">{{ selectedNode.displayName }}</span>\n </div>\n <button class=\"ag-close-button\" (click)=\"clearSelection()\" title=\"Close (Esc)\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </header>\n\n <!-- \u7D44\u4EF6\u57FA\u672C\u8CC7\u8A0A -->\n <section class=\"ag-section ag-info-section\">\n <div class=\"ag-info-row\">\n <span class=\"ag-info-label\">Selector</span>\n <code class=\"ag-info-value\">&lt;{{ selectedNode.selector }}&gt;</code>\n </div>\n <div class=\"ag-info-row\" *ngIf=\"selectedNode.parent\">\n <span class=\"ag-info-label\">Parent</span>\n <code class=\"ag-info-value\">{{ selectedNode.parent.displayName }}</code>\n </div>\n <div class=\"ag-info-row\" *ngIf=\"selectedNode.directives.length > 0\">\n <span class=\"ag-info-label\">Directives</span>\n <div class=\"ag-directive-list\">\n <span class=\"ag-directive-tag\" *ngFor=\"let dir of selectedNode.directives\">{{ dir }}</span>\n </div>\n </div>\n </section>\n\n <!-- @Input \u7D81\u5B9A -->\n <section class=\"ag-section\">\n <header class=\"ag-section-header\" (click)=\"toggleSection('inputs')\">\n <span class=\"ag-section-icon\">{{ expandedSections.inputs ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"ag-section-title\">&#64;Input Bindings</span>\n <span class=\"ag-section-count\">{{ getInputEntries().length }}</span>\n </header>\n <div class=\"ag-section-content\" *ngIf=\"expandedSections.inputs && getInputEntries().length > 0\">\n <div class=\"ag-prop-row\" *ngFor=\"let entry of getInputEntries()\">\n <span class=\"ag-prop-name\">{{ entry.key }}</span>\n <span class=\"ag-prop-value\" [style.color]=\"getValueColor(entry.value)\">\n {{ formatValue(entry.value) }}\n </span>\n </div>\n </div>\n <div class=\"ag-section-empty\" *ngIf=\"expandedSections.inputs && getInputEntries().length === 0\">\n No &#64;Input bindings\n </div>\n </section>\n\n <!-- @Output \u4E8B\u4EF6 -->\n <section class=\"ag-section\" *ngIf=\"selectedNode.outputs.length > 0\">\n <header class=\"ag-section-header\" (click)=\"toggleSection('outputs')\">\n <span class=\"ag-section-icon\">{{ expandedSections.outputs ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"ag-section-title\">&#64;Output Events</span>\n <span class=\"ag-section-count\">{{ selectedNode.outputs.length }}</span>\n </header>\n <div class=\"ag-section-content\" *ngIf=\"expandedSections.outputs\">\n <div class=\"ag-output-row\" *ngFor=\"let output of selectedNode.outputs\">\n <span class=\"ag-output-name\">{{ output }}</span>\n <span class=\"ag-output-type\">EventEmitter</span>\n </div>\n </div>\n </section>\n\n <!-- \u516C\u958B\u5C6C\u6027 -->\n <section class=\"ag-section\" *ngIf=\"getPropertyEntries().length > 0\">\n <header class=\"ag-section-header\" (click)=\"toggleSection('properties')\">\n <span class=\"ag-section-icon\">{{ expandedSections.properties ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"ag-section-title\">Public Properties</span>\n <span class=\"ag-section-count\">{{ getPropertyEntries().length }}</span>\n </header>\n <div class=\"ag-section-content\" *ngIf=\"expandedSections.properties\">\n <div class=\"ag-prop-row\" *ngFor=\"let entry of getPropertyEntries()\">\n <span class=\"ag-prop-name\">{{ entry.key }}</span>\n <span class=\"ag-prop-value\" [style.color]=\"getValueColor(entry.value)\">\n {{ formatValue(entry.value) }}\n </span>\n </div>\n </div>\n </section>\n\n <!-- Computed Styles -->\n <section class=\"ag-section\">\n <header class=\"ag-section-header\" (click)=\"toggleSection('styles')\">\n <span class=\"ag-section-icon\">{{ expandedSections.styles ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"ag-section-title\">Computed Styles</span>\n <span class=\"ag-section-count\">{{ getStyleEntries().length }}</span>\n </header>\n <div class=\"ag-section-content\" *ngIf=\"expandedSections.styles\">\n <div class=\"ag-style-row\" *ngFor=\"let entry of getStyleEntries()\">\n <span class=\"ag-style-name\">{{ entry.key }}</span>\n <span class=\"ag-style-value\">{{ entry.value }}</span>\n </div>\n </div>\n </section>\n\n <!-- \u4F7F\u7528\u8005\u610F\u5716\u8F38\u5165 -->\n <section class=\"ag-section ag-intent-section\">\n <label class=\"ag-intent-label\" for=\"userIntent\">\n \uD83D\uDCAC Your Instruction for AI\n </label>\n <textarea\n id=\"userIntent\"\n class=\"ag-intent-input\"\n [(ngModel)]=\"userIntent\"\n placeholder=\"e.g., 'This button should be disabled when loading'\"\n rows=\"3\"\n ></textarea>\n </section>\n\n <!-- \u64CD\u4F5C\u6309\u9215 -->\n <footer class=\"ag-panel-footer\">\n <!-- MCP Send Button -->\n <button\n *ngIf=\"(mcpStatus$ | async)?.connected\"\n class=\"ag-button ag-button-primary ag-mcp-button\"\n (click)=\"sendToAgent()\"\n [class.ag-button-success]=\"sent\"\n >\n <span *ngIf=\"!sent\">\uD83E\uDD16 Send to Agent</span>\n <span *ngIf=\"sent\">\u2705 Sent!</span>\n </button>\n\n <button\n class=\"ag-button ag-button-primary\"\n (click)=\"copyToClipboard()\"\n [class.ag-button-success]=\"copied\"\n >\n <span *ngIf=\"!copied\">\uD83D\uDCCB Copy with Intent</span>\n <span *ngIf=\"copied\">\u2705 Copied!</span>\n </button>\n <button\n class=\"ag-button ag-button-secondary\"\n (click)=\"copyComponentInfo()\"\n >\n \uD83D\uDCC4 Copy Info Only\n </button>\n </footer>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.ag-panel{position:fixed;top:20px;right:20px;width:380px;max-height:calc(100vh - 40px);background:#0a0a0f;border:1px solid #2a2a3a;clip-path:polygon(0 12px,12px 0,calc(100% - 12px) 0,100% 12px,100% calc(100% - 12px),calc(100% - 12px) 100%,12px 100%,0 calc(100% - 12px));font-family:JetBrains Mono,Fira Code,monospace;font-size:12px;color:#e0e0e0;overflow:hidden;display:flex;flex-direction:column;z-index:999999;animation:slideInRight .15s ease-out;box-shadow:0 0 20px #00ff8814,0 8px 32px #0009}.ag-panel:before{content:\"\";position:absolute;top:0;left:12px;right:12px;height:1px;background:linear-gradient(90deg,transparent,#00ff88,transparent);z-index:1}@keyframes slideInRight{0%{opacity:0;transform:translate(16px)}to{opacity:1;transform:translate(0)}}.ag-panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#12121a;border-bottom:1px solid #2a2a3a;flex-shrink:0;position:relative}.ag-panel-header:before{content:\"\\25cf \\25cf \\25cf\";position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:7px;letter-spacing:3px;color:transparent;text-shadow:0 0 0 #ff3366,8px 0 0 #f59e0b,16px 0 0 #00ff88;pointer-events:none}.ag-panel-title{display:flex;align-items:center;gap:8px;padding-left:36px}.ag-component-icon{font-size:14px}.ag-component-name{font-family:Share Tech Mono,monospace;font-size:13px;font-weight:600;color:#0f8;letter-spacing:.05em;text-shadow:0 0 6px rgba(0,255,136,.4)}.ag-close-button{display:flex;align-items:center;justify-content:center;width:26px;height:26px;background:transparent;border:1px solid rgba(255,51,102,.3);color:#6b7280;cursor:pointer;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-close-button svg{width:13px;height:13px}.ag-close-button:hover{background:#ff336626;border-color:#f36;color:#f36;box-shadow:0 0 6px #ff33664d}.ag-section{border-bottom:1px solid #2a2a3a;flex-shrink:0}.ag-section:last-of-type{border-bottom:none}.ag-info-section{padding:10px 16px;background:#0f0f18}.ag-info-row{display:flex;align-items:flex-start;gap:10px;margin-bottom:7px}.ag-info-row:last-child{margin-bottom:0}.ag-info-label{flex-shrink:0;width:66px;color:#6b7280;font-size:10px;text-transform:uppercase;letter-spacing:.1em}.ag-info-value{font-family:Share Tech Mono,monospace;font-size:12px;color:#00d4ff;background:#00d4ff0f;border:1px solid rgba(0,212,255,.15);padding:1px 6px;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-directive-list{display:flex;flex-wrap:wrap;gap:4px}.ag-directive-tag{background:#ff00ff14;border:1px solid rgba(255,0,255,.2);color:#f0f;padding:1px 7px;font-size:10px;font-family:Share Tech Mono,monospace;letter-spacing:.05em;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-section-header{display:flex;align-items:center;gap:8px;padding:9px 16px;background:#12121a;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .1s}.ag-section-header:hover{background:#1c1c2e}.ag-section-icon{font-size:9px;color:#00ff8880;width:12px}.ag-section-title{flex:1;font-family:Share Tech Mono,monospace;font-size:11px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:#6b7280}.ag-section-count{background:#00ff8814;border:1px solid rgba(0,255,136,.2);color:#0f8;padding:1px 7px;font-size:10px;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-section-content{padding:6px 16px 10px;max-height:180px;overflow-y:auto}.ag-section-content::-webkit-scrollbar{width:4px}.ag-section-content::-webkit-scrollbar-track{background:#0a0a0f}.ag-section-content::-webkit-scrollbar-thumb{background:#2a2a3a}.ag-section-empty{padding:10px 16px;color:#6b7280;font-size:11px;letter-spacing:.05em}.ag-section-empty:before{content:\"// \";color:#00ff884d}.ag-prop-row,.ag-output-row,.ag-style-row{display:flex;justify-content:space-between;align-items:flex-start;padding:5px 0;border-bottom:1px solid rgba(42,42,58,.5)}.ag-prop-row:last-child,.ag-output-row:last-child,.ag-style-row:last-child{border-bottom:none}.ag-prop-name,.ag-output-name,.ag-style-name{font-family:Share Tech Mono,monospace;font-size:11px;color:#6b7280}.ag-prop-name:before,.ag-output-name:before,.ag-style-name:before{content:\".\";color:#0f86}.ag-prop-value,.ag-style-value{font-family:Share Tech Mono,monospace;font-size:11px;max-width:200px;word-break:break-all;text-align:right}.ag-output-type{font-size:10px;color:#6b7280;background:#ff00ff0f;border:1px solid rgba(255,0,255,.15);color:#ff00ffb3;padding:1px 6px;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-intent-section{padding:12px 16px;background:#0f0f18}.ag-intent-label{display:block;margin-bottom:8px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#6b7280}.ag-intent-label:before{content:\"$ \";color:#0f8}.ag-intent-input{width:100%;padding:9px 12px;background:#12121a;border:1px solid #2a2a3a;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));color:#0f8;font-family:JetBrains Mono,monospace;font-size:12px;letter-spacing:.03em;resize:vertical;transition:border-color .15s,box-shadow .15s}.ag-intent-input::placeholder{color:#6b728080}.ag-intent-input:focus{outline:none;border-color:#00ff8880;box-shadow:0 0 8px #00ff881f,inset 0 0 4px #00ff880a}.ag-panel-footer{display:flex;gap:8px;padding:12px 16px;background:#12121a;border-top:1px solid #2a2a3a;flex-shrink:0}.ag-button{flex:1;padding:9px 12px;border:none;background:transparent;font-family:Share Tech Mono,monospace;font-size:11px;font-weight:400;letter-spacing:.12em;text-transform:uppercase;cursor:pointer;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-button:active{transform:scale(.97)}.ag-button-primary{border:1px solid #00ff88;color:#0f8;text-shadow:0 0 5px rgba(0,255,136,.3)}.ag-button-primary:hover{background:#0f8;color:#0a0a0f;text-shadow:none;box-shadow:0 0 10px #0f86}.ag-button-success{background:#00ff881a;border:1px solid #00ff88;color:#0f8;box-shadow:0 0 8px #0f83}.ag-button-secondary{border:1px solid #2a2a3a;color:#6b7280}.ag-button-secondary:hover{border-color:#6b728066;color:#e0e0e0;background:#6b72800d}.ag-mcp-button{border:1px solid #f59e0b;color:#f59e0b;text-shadow:0 0 5px rgba(245,158,11,.3)}.ag-mcp-button:hover{background:#f59e0b;color:#0a0a0f;text-shadow:none;box-shadow:0 0 10px #f59e0b66}.ag-mcp-button.ag-button-success{border-color:#0f8;color:#0f8;background:#00ff881a;text-shadow:none}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }] });
1916
+ }
1917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AnnotationPanelComponent, decorators: [{
1918
+ type: Component,
1919
+ args: [{ selector: 'ag-annotation-panel', standalone: false, template: "<div class=\"ag-panel\" *ngIf=\"selectedNode\">\n <!-- \u6A19\u984C\u5217 -->\n <header class=\"ag-panel-header\">\n <div class=\"ag-panel-title\">\n <span class=\"ag-component-icon\">\uD83D\uDCE6</span>\n <span class=\"ag-component-name\">{{ selectedNode.displayName }}</span>\n </div>\n <button class=\"ag-close-button\" (click)=\"clearSelection()\" title=\"Close (Esc)\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </header>\n\n <!-- \u7D44\u4EF6\u57FA\u672C\u8CC7\u8A0A -->\n <section class=\"ag-section ag-info-section\">\n <div class=\"ag-info-row\">\n <span class=\"ag-info-label\">Selector</span>\n <code class=\"ag-info-value\">&lt;{{ selectedNode.selector }}&gt;</code>\n </div>\n <div class=\"ag-info-row\" *ngIf=\"selectedNode.parent\">\n <span class=\"ag-info-label\">Parent</span>\n <code class=\"ag-info-value\">{{ selectedNode.parent.displayName }}</code>\n </div>\n <div class=\"ag-info-row\" *ngIf=\"selectedNode.directives.length > 0\">\n <span class=\"ag-info-label\">Directives</span>\n <div class=\"ag-directive-list\">\n <span class=\"ag-directive-tag\" *ngFor=\"let dir of selectedNode.directives\">{{ dir }}</span>\n </div>\n </div>\n </section>\n\n <!-- @Input \u7D81\u5B9A -->\n <section class=\"ag-section\">\n <header class=\"ag-section-header\" (click)=\"toggleSection('inputs')\">\n <span class=\"ag-section-icon\">{{ expandedSections.inputs ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"ag-section-title\">&#64;Input Bindings</span>\n <span class=\"ag-section-count\">{{ getInputEntries().length }}</span>\n </header>\n <div class=\"ag-section-content\" *ngIf=\"expandedSections.inputs && getInputEntries().length > 0\">\n <div class=\"ag-prop-row\" *ngFor=\"let entry of getInputEntries()\">\n <span class=\"ag-prop-name\">{{ entry.key }}</span>\n <span class=\"ag-prop-value\" [style.color]=\"getValueColor(entry.value)\">\n {{ formatValue(entry.value) }}\n </span>\n </div>\n </div>\n <div class=\"ag-section-empty\" *ngIf=\"expandedSections.inputs && getInputEntries().length === 0\">\n No &#64;Input bindings\n </div>\n </section>\n\n <!-- @Output \u4E8B\u4EF6 -->\n <section class=\"ag-section\" *ngIf=\"selectedNode.outputs.length > 0\">\n <header class=\"ag-section-header\" (click)=\"toggleSection('outputs')\">\n <span class=\"ag-section-icon\">{{ expandedSections.outputs ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"ag-section-title\">&#64;Output Events</span>\n <span class=\"ag-section-count\">{{ selectedNode.outputs.length }}</span>\n </header>\n <div class=\"ag-section-content\" *ngIf=\"expandedSections.outputs\">\n <div class=\"ag-output-row\" *ngFor=\"let output of selectedNode.outputs\">\n <span class=\"ag-output-name\">{{ output }}</span>\n <span class=\"ag-output-type\">EventEmitter</span>\n </div>\n </div>\n </section>\n\n <!-- \u516C\u958B\u5C6C\u6027 -->\n <section class=\"ag-section\" *ngIf=\"getPropertyEntries().length > 0\">\n <header class=\"ag-section-header\" (click)=\"toggleSection('properties')\">\n <span class=\"ag-section-icon\">{{ expandedSections.properties ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"ag-section-title\">Public Properties</span>\n <span class=\"ag-section-count\">{{ getPropertyEntries().length }}</span>\n </header>\n <div class=\"ag-section-content\" *ngIf=\"expandedSections.properties\">\n <div class=\"ag-prop-row\" *ngFor=\"let entry of getPropertyEntries()\">\n <span class=\"ag-prop-name\">{{ entry.key }}</span>\n <span class=\"ag-prop-value\" [style.color]=\"getValueColor(entry.value)\">\n {{ formatValue(entry.value) }}\n </span>\n </div>\n </div>\n </section>\n\n <!-- Computed Styles -->\n <section class=\"ag-section\">\n <header class=\"ag-section-header\" (click)=\"toggleSection('styles')\">\n <span class=\"ag-section-icon\">{{ expandedSections.styles ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"ag-section-title\">Computed Styles</span>\n <span class=\"ag-section-count\">{{ getStyleEntries().length }}</span>\n </header>\n <div class=\"ag-section-content\" *ngIf=\"expandedSections.styles\">\n <div class=\"ag-style-row\" *ngFor=\"let entry of getStyleEntries()\">\n <span class=\"ag-style-name\">{{ entry.key }}</span>\n <span class=\"ag-style-value\">{{ entry.value }}</span>\n </div>\n </div>\n </section>\n\n <!-- \u4F7F\u7528\u8005\u610F\u5716\u8F38\u5165 -->\n <section class=\"ag-section ag-intent-section\">\n <label class=\"ag-intent-label\" for=\"userIntent\">\n \uD83D\uDCAC Your Instruction for AI\n </label>\n <textarea\n id=\"userIntent\"\n class=\"ag-intent-input\"\n [(ngModel)]=\"userIntent\"\n placeholder=\"e.g., 'This button should be disabled when loading'\"\n rows=\"3\"\n ></textarea>\n </section>\n\n <!-- \u64CD\u4F5C\u6309\u9215 -->\n <footer class=\"ag-panel-footer\">\n <!-- MCP Send Button -->\n <button\n *ngIf=\"(mcpStatus$ | async)?.connected\"\n class=\"ag-button ag-button-primary ag-mcp-button\"\n (click)=\"sendToAgent()\"\n [class.ag-button-success]=\"sent\"\n >\n <span *ngIf=\"!sent\">\uD83E\uDD16 Send to Agent</span>\n <span *ngIf=\"sent\">\u2705 Sent!</span>\n </button>\n\n <button\n class=\"ag-button ag-button-primary\"\n (click)=\"copyToClipboard()\"\n [class.ag-button-success]=\"copied\"\n >\n <span *ngIf=\"!copied\">\uD83D\uDCCB Copy with Intent</span>\n <span *ngIf=\"copied\">\u2705 Copied!</span>\n </button>\n <button\n class=\"ag-button ag-button-secondary\"\n (click)=\"copyComponentInfo()\"\n >\n \uD83D\uDCC4 Copy Info Only\n </button>\n </footer>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.ag-panel{position:fixed;top:20px;right:20px;width:380px;max-height:calc(100vh - 40px);background:#0a0a0f;border:1px solid #2a2a3a;clip-path:polygon(0 12px,12px 0,calc(100% - 12px) 0,100% 12px,100% calc(100% - 12px),calc(100% - 12px) 100%,12px 100%,0 calc(100% - 12px));font-family:JetBrains Mono,Fira Code,monospace;font-size:12px;color:#e0e0e0;overflow:hidden;display:flex;flex-direction:column;z-index:999999;animation:slideInRight .15s ease-out;box-shadow:0 0 20px #00ff8814,0 8px 32px #0009}.ag-panel:before{content:\"\";position:absolute;top:0;left:12px;right:12px;height:1px;background:linear-gradient(90deg,transparent,#00ff88,transparent);z-index:1}@keyframes slideInRight{0%{opacity:0;transform:translate(16px)}to{opacity:1;transform:translate(0)}}.ag-panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#12121a;border-bottom:1px solid #2a2a3a;flex-shrink:0;position:relative}.ag-panel-header:before{content:\"\\25cf \\25cf \\25cf\";position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:7px;letter-spacing:3px;color:transparent;text-shadow:0 0 0 #ff3366,8px 0 0 #f59e0b,16px 0 0 #00ff88;pointer-events:none}.ag-panel-title{display:flex;align-items:center;gap:8px;padding-left:36px}.ag-component-icon{font-size:14px}.ag-component-name{font-family:Share Tech Mono,monospace;font-size:13px;font-weight:600;color:#0f8;letter-spacing:.05em;text-shadow:0 0 6px rgba(0,255,136,.4)}.ag-close-button{display:flex;align-items:center;justify-content:center;width:26px;height:26px;background:transparent;border:1px solid rgba(255,51,102,.3);color:#6b7280;cursor:pointer;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-close-button svg{width:13px;height:13px}.ag-close-button:hover{background:#ff336626;border-color:#f36;color:#f36;box-shadow:0 0 6px #ff33664d}.ag-section{border-bottom:1px solid #2a2a3a;flex-shrink:0}.ag-section:last-of-type{border-bottom:none}.ag-info-section{padding:10px 16px;background:#0f0f18}.ag-info-row{display:flex;align-items:flex-start;gap:10px;margin-bottom:7px}.ag-info-row:last-child{margin-bottom:0}.ag-info-label{flex-shrink:0;width:66px;color:#6b7280;font-size:10px;text-transform:uppercase;letter-spacing:.1em}.ag-info-value{font-family:Share Tech Mono,monospace;font-size:12px;color:#00d4ff;background:#00d4ff0f;border:1px solid rgba(0,212,255,.15);padding:1px 6px;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-directive-list{display:flex;flex-wrap:wrap;gap:4px}.ag-directive-tag{background:#ff00ff14;border:1px solid rgba(255,0,255,.2);color:#f0f;padding:1px 7px;font-size:10px;font-family:Share Tech Mono,monospace;letter-spacing:.05em;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-section-header{display:flex;align-items:center;gap:8px;padding:9px 16px;background:#12121a;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .1s}.ag-section-header:hover{background:#1c1c2e}.ag-section-icon{font-size:9px;color:#00ff8880;width:12px}.ag-section-title{flex:1;font-family:Share Tech Mono,monospace;font-size:11px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:#6b7280}.ag-section-count{background:#00ff8814;border:1px solid rgba(0,255,136,.2);color:#0f8;padding:1px 7px;font-size:10px;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-section-content{padding:6px 16px 10px;max-height:180px;overflow-y:auto}.ag-section-content::-webkit-scrollbar{width:4px}.ag-section-content::-webkit-scrollbar-track{background:#0a0a0f}.ag-section-content::-webkit-scrollbar-thumb{background:#2a2a3a}.ag-section-empty{padding:10px 16px;color:#6b7280;font-size:11px;letter-spacing:.05em}.ag-section-empty:before{content:\"// \";color:#00ff884d}.ag-prop-row,.ag-output-row,.ag-style-row{display:flex;justify-content:space-between;align-items:flex-start;padding:5px 0;border-bottom:1px solid rgba(42,42,58,.5)}.ag-prop-row:last-child,.ag-output-row:last-child,.ag-style-row:last-child{border-bottom:none}.ag-prop-name,.ag-output-name,.ag-style-name{font-family:Share Tech Mono,monospace;font-size:11px;color:#6b7280}.ag-prop-name:before,.ag-output-name:before,.ag-style-name:before{content:\".\";color:#0f86}.ag-prop-value,.ag-style-value{font-family:Share Tech Mono,monospace;font-size:11px;max-width:200px;word-break:break-all;text-align:right}.ag-output-type{font-size:10px;color:#6b7280;background:#ff00ff0f;border:1px solid rgba(255,0,255,.15);color:#ff00ffb3;padding:1px 6px;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-intent-section{padding:12px 16px;background:#0f0f18}.ag-intent-label{display:block;margin-bottom:8px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#6b7280}.ag-intent-label:before{content:\"$ \";color:#0f8}.ag-intent-input{width:100%;padding:9px 12px;background:#12121a;border:1px solid #2a2a3a;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));color:#0f8;font-family:JetBrains Mono,monospace;font-size:12px;letter-spacing:.03em;resize:vertical;transition:border-color .15s,box-shadow .15s}.ag-intent-input::placeholder{color:#6b728080}.ag-intent-input:focus{outline:none;border-color:#00ff8880;box-shadow:0 0 8px #00ff881f,inset 0 0 4px #00ff880a}.ag-panel-footer{display:flex;gap:8px;padding:12px 16px;background:#12121a;border-top:1px solid #2a2a3a;flex-shrink:0}.ag-button{flex:1;padding:9px 12px;border:none;background:transparent;font-family:Share Tech Mono,monospace;font-size:11px;font-weight:400;letter-spacing:.12em;text-transform:uppercase;cursor:pointer;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-button:active{transform:scale(.97)}.ag-button-primary{border:1px solid #00ff88;color:#0f8;text-shadow:0 0 5px rgba(0,255,136,.3)}.ag-button-primary:hover{background:#0f8;color:#0a0a0f;text-shadow:none;box-shadow:0 0 10px #0f86}.ag-button-success{background:#00ff881a;border:1px solid #00ff88;color:#0f8;box-shadow:0 0 8px #0f83}.ag-button-secondary{border:1px solid #2a2a3a;color:#6b7280}.ag-button-secondary:hover{border-color:#6b728066;color:#e0e0e0;background:#6b72800d}.ag-mcp-button{border:1px solid #f59e0b;color:#f59e0b;text-shadow:0 0 5px rgba(245,158,11,.3)}.ag-mcp-button:hover{background:#f59e0b;color:#0a0a0f;text-shadow:none;box-shadow:0 0 10px #f59e0b66}.ag-mcp-button.ag-button-success{border-color:#0f8;color:#0f8;background:#00ff881a;text-shadow:none}\n"] }]
1920
+ }], ctorParameters: () => [{ type: PromptGeneratorService }, { type: McpService }], propDecorators: { selectedNode: [{
1921
+ type: Input
1922
+ }], closed: [{
1923
+ type: Output
1924
+ }] } });
1925
+
1926
+ /**
1927
+ * ToolbarComponent
1928
+ *
1929
+ * 浮動工具列:提供錄製控制、檢視、複製、設定等功能
1930
+ */
1931
+ class ToolbarComponent {
1932
+ /** 當前錄製會話 */
1933
+ session = null;
1934
+ /** 當前設定 */
1935
+ settings = DEFAULT_SETTINGS;
1936
+ /** 綁定 dark mode class 到 host */
1937
+ get isDarkMode() {
1938
+ return this.settings.isDarkMode;
1939
+ }
1940
+ /** 工具列狀態 */
1941
+ state = {
1942
+ showSettings: false,
1943
+ showMarkers: false,
1944
+ isRecording: false,
1945
+ isMinimized: false,
1946
+ };
1947
+ /** 開始錄製 */
1948
+ startRecording = new EventEmitter();
1949
+ /** 結束錄製 */
1950
+ stopRecording = new EventEmitter();
1951
+ /** 切換檢視標記列表 */
1952
+ toggleMarkers = new EventEmitter();
1953
+ /** 複製到剪貼簿 */
1954
+ copyToClipboard = new EventEmitter();
1955
+ /** 清除所有標記 */
1956
+ clearMarkers = new EventEmitter();
1957
+ /** 切換設定面板 */
1958
+ toggleSettings = new EventEmitter();
1959
+ /** 關閉工具 */
1960
+ closeToolbar = new EventEmitter();
1961
+ /** 切換最小化狀態 */
1962
+ toggleMinimize = new EventEmitter();
1963
+ /** 設定變更 */
1964
+ settingsChange = new EventEmitter();
1965
+ /** 標記數量 */
1966
+ get markerCount() {
1967
+ return this.session?.markers.length ?? 0;
1968
+ }
1969
+ /** 是否有標記 */
1970
+ get hasMarkers() {
1971
+ return this.markerCount > 0;
1972
+ }
1973
+ /** 切換錄製狀態 */
1974
+ onToggleRecording() {
1975
+ if (this.state.isRecording) {
1976
+ this.stopRecording.emit();
1977
+ }
1978
+ else {
1979
+ this.startRecording.emit();
1980
+ }
1981
+ }
1982
+ /** 處理複製 */
1983
+ onCopy() {
1984
+ if (this.hasMarkers) {
1985
+ this.copyToClipboard.emit();
1986
+ }
1987
+ }
1988
+ /** 處理清除 */
1989
+ onClear() {
1990
+ if (this.hasMarkers) {
1991
+ this.clearMarkers.emit();
1992
+ }
1993
+ }
1994
+ /** 處理設定切換 */
1995
+ onToggleSettings() {
1996
+ this.toggleSettings.emit();
1997
+ }
1998
+ /** 處理標記列表切換 */
1999
+ onToggleMarkers() {
2000
+ this.toggleMarkers.emit();
2001
+ }
2002
+ /** 處理關閉 */
2003
+ onClose() {
2004
+ this.closeToolbar.emit();
2005
+ }
2006
+ /** 處理最小化切換 */
2007
+ onToggleMinimize() {
2008
+ this.toggleMinimize.emit();
2009
+ }
2010
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2011
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: ToolbarComponent, isStandalone: false, selector: "ag-toolbar", inputs: { session: "session", settings: "settings", state: "state" }, outputs: { startRecording: "startRecording", stopRecording: "stopRecording", toggleMarkers: "toggleMarkers", copyToClipboard: "copyToClipboard", clearMarkers: "clearMarkers", toggleSettings: "toggleSettings", closeToolbar: "closeToolbar", toggleMinimize: "toggleMinimize", settingsChange: "settingsChange" }, host: { properties: { "class.ag-dark-mode": "this.isDarkMode" } }, ngImport: i0, template: "<div class=\"ag-toolbar\" [class.collapsed]=\"state.isMinimized\" [class.recording]=\"state.isRecording\">\n <!-- \u6536\u5408\u72C0\u614B\uFF1A\u986F\u793A\u5C55\u958B\u6309\u9215 -->\n <button\n *ngIf=\"state.isMinimized\"\n class=\"ag-toolbar-btn ag-btn-expand\"\n (click)=\"onToggleMinimize()\"\n title=\"Expand Toolbar\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <!-- \u4E09\u6A6B\u7DDA + \u83F1\u5F62\u5716\u6A19 -->\n <line x1=\"4\" y1=\"6\" x2=\"14\" y2=\"6\"/>\n <line x1=\"4\" y1=\"12\" x2=\"14\" y2=\"12\"/>\n <line x1=\"4\" y1=\"18\" x2=\"14\" y2=\"18\"/>\n <path d=\"M17 9l3 3-3 3\" fill=\"none\"/>\n </svg>\n </button>\n\n <!-- \u5C55\u958B\u72C0\u614B\uFF1A\u986F\u793A\u5B8C\u6574\u5DE5\u5177\u5217 -->\n <ng-container *ngIf=\"!state.isMinimized\">\n <!-- \u6A19\u8A18\u8A08\u6578\u5FBD\u7AE0 -->\n <div class=\"ag-marker-badge\" *ngIf=\"hasMarkers\">\n {{ markerCount }}\n </div>\n\n <!-- \u4E3B\u5DE5\u5177\u5217 -->\n <div class=\"ag-toolbar-buttons\">\n <!-- \u9304\u88FD\u6309\u9215 -->\n <button\n class=\"ag-toolbar-btn ag-btn-record\"\n [class.active]=\"state.isRecording\"\n (click)=\"onToggleRecording()\"\n [title]=\"state.isRecording ? 'Stop Recording' : 'Start Recording'\"\n >\n <svg *ngIf=\"!state.isRecording\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M8 5v14l11-7z\"/>\n </svg>\n <svg *ngIf=\"state.isRecording\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\"/>\n </svg>\n </button>\n\n <!-- \u6AA2\u8996\u6A19\u8A18 -->\n <button\n class=\"ag-toolbar-btn\"\n [class.active]=\"state.showMarkers\"\n [disabled]=\"!hasMarkers\"\n (click)=\"onToggleMarkers()\"\n title=\"View Markers\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z\"/>\n </svg>\n </button>\n\n <!-- \u8907\u88FD -->\n <button\n class=\"ag-toolbar-btn\"\n [disabled]=\"!hasMarkers\"\n (click)=\"onCopy()\"\n title=\"Copy to Clipboard\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/>\n </svg>\n </button>\n\n <!-- \u6E05\u9664 -->\n <button\n class=\"ag-toolbar-btn\"\n [disabled]=\"!hasMarkers\"\n (click)=\"onClear()\"\n title=\"Clear Markers\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n\n <!-- \u5206\u9694\u7DDA -->\n <div class=\"ag-toolbar-divider\"></div>\n\n <!-- \u8A2D\u5B9A -->\n <!-- <button\n class=\"ag-toolbar-btn\"\n [class.active]=\"state.showSettings\"\n (click)=\"onToggleSettings()\"\n title=\"Settings\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/>\n </svg>\n </button> -->\n\n <!-- \u6536\u5408\u6309\u9215 -->\n <button\n class=\"ag-toolbar-btn ag-btn-collapse\"\n (click)=\"onToggleMinimize()\"\n title=\"Collapse Toolbar\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"4\" y1=\"6\" x2=\"14\" y2=\"6\"/>\n <line x1=\"4\" y1=\"12\" x2=\"14\" y2=\"12\"/>\n <line x1=\"4\" y1=\"18\" x2=\"14\" y2=\"18\"/>\n <path d=\"M20 9l-3 3 3 3\"/>\n </svg>\n </button>\n </div>\n </ng-container>\n\n <!-- Settings Panel -->\n <!-- <ag-settings-panel\n *ngIf=\"state.showSettings\"\n [settings]=\"settings\"\n (settingsChange)=\"settingsChange.emit($event)\"\n (closed)=\"onToggleSettings()\"\n ></ag-settings-panel> -->\n</div>\n", styles: [".ag-toolbar{position:fixed;bottom:24px;right:24px;z-index:999999;display:flex;align-items:center;gap:6px;background:#12121a;border:1px solid #2a2a3a;padding:8px 12px;clip-path:polygon(0 12px,12px 0,calc(100% - 12px) 0,100% 12px,100% calc(100% - 12px),calc(100% - 12px) 100%,12px 100%,0 calc(100% - 12px));transition:border-color .3s,box-shadow .3s}.ag-toolbar:before{content:\"\";position:absolute;inset:0;pointer-events:none;background:linear-gradient(180deg,rgba(0,255,136,.03) 0%,transparent 100%)}.ag-toolbar.recording{border-color:#f369;animation:recordingPulse 2s ease-in-out infinite}.ag-toolbar.collapsed{padding:0;clip-path:polygon(0 8px,8px 0,calc(100% - 8px) 0,100% 8px,100% calc(100% - 8px),calc(100% - 8px) 100%,8px 100%,0 calc(100% - 8px));width:48px;height:48px;justify-content:center;border-color:#00ff884d;box-shadow:0 0 8px #00ff8826}.ag-toolbar.collapsed .ag-toolbar-buttons,.ag-toolbar.collapsed .ag-marker-badge{display:none}.ag-marker-badge{position:absolute;top:-8px;left:-8px;min-width:20px;height:20px;padding:0 5px;display:flex;align-items:center;justify-content:center;background:#0f8;color:#0a0a0f;font-family:Orbitron,monospace;font-size:10px;font-weight:700;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));box-shadow:0 0 8px #0f89}.ag-toolbar-buttons{display:flex;align-items:center;gap:2px}.ag-toolbar-btn{display:flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;border:1px solid transparent;background:transparent;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));color:#6b7280;cursor:pointer;transition:all .15s ease}.ag-toolbar-btn svg{width:18px;height:18px;stroke-width:1.5}.ag-toolbar-btn:hover:not(:disabled){background:#00ff8814;border-color:#0f83;color:#0f8;filter:drop-shadow(0 0 4px rgba(0,255,136,.4))}.ag-toolbar-btn:active:not(:disabled){transform:scale(.93)}.ag-toolbar-btn:disabled{color:#6b72804d;cursor:not-allowed}.ag-toolbar-btn.active{background:#00ff881a;border-color:#00ff884d;color:#0f8;box-shadow:inset 0 0 6px #00ff881a}.ag-btn-expand{width:48px;height:48px}.ag-btn-expand:hover:not(:disabled){background:#00ff8814}.ag-btn-record.active{background:#ff33661a;border-color:#f366;color:#f36}.ag-btn-record.active svg{animation:recordBlink 1s step-end infinite;filter:drop-shadow(0 0 4px #ff3366)}.ag-toolbar-divider{width:1px;height:22px;background:#2a2a3a;margin:0 4px}@keyframes recordBlink{0%,to{opacity:1}50%{opacity:.4}}@keyframes recordingPulse{0%,to{box-shadow:0 0 5px #f36,0 0 15px #ff336640,0 0 0 1px #f366}50%{box-shadow:0 0 12px #f36,0 0 25px #f366,0 0 0 1px #f36}}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
2012
+ }
2013
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ToolbarComponent, decorators: [{
2014
+ type: Component,
2015
+ args: [{ selector: 'ag-toolbar', standalone: false, template: "<div class=\"ag-toolbar\" [class.collapsed]=\"state.isMinimized\" [class.recording]=\"state.isRecording\">\n <!-- \u6536\u5408\u72C0\u614B\uFF1A\u986F\u793A\u5C55\u958B\u6309\u9215 -->\n <button\n *ngIf=\"state.isMinimized\"\n class=\"ag-toolbar-btn ag-btn-expand\"\n (click)=\"onToggleMinimize()\"\n title=\"Expand Toolbar\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <!-- \u4E09\u6A6B\u7DDA + \u83F1\u5F62\u5716\u6A19 -->\n <line x1=\"4\" y1=\"6\" x2=\"14\" y2=\"6\"/>\n <line x1=\"4\" y1=\"12\" x2=\"14\" y2=\"12\"/>\n <line x1=\"4\" y1=\"18\" x2=\"14\" y2=\"18\"/>\n <path d=\"M17 9l3 3-3 3\" fill=\"none\"/>\n </svg>\n </button>\n\n <!-- \u5C55\u958B\u72C0\u614B\uFF1A\u986F\u793A\u5B8C\u6574\u5DE5\u5177\u5217 -->\n <ng-container *ngIf=\"!state.isMinimized\">\n <!-- \u6A19\u8A18\u8A08\u6578\u5FBD\u7AE0 -->\n <div class=\"ag-marker-badge\" *ngIf=\"hasMarkers\">\n {{ markerCount }}\n </div>\n\n <!-- \u4E3B\u5DE5\u5177\u5217 -->\n <div class=\"ag-toolbar-buttons\">\n <!-- \u9304\u88FD\u6309\u9215 -->\n <button\n class=\"ag-toolbar-btn ag-btn-record\"\n [class.active]=\"state.isRecording\"\n (click)=\"onToggleRecording()\"\n [title]=\"state.isRecording ? 'Stop Recording' : 'Start Recording'\"\n >\n <svg *ngIf=\"!state.isRecording\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M8 5v14l11-7z\"/>\n </svg>\n <svg *ngIf=\"state.isRecording\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\"/>\n </svg>\n </button>\n\n <!-- \u6AA2\u8996\u6A19\u8A18 -->\n <button\n class=\"ag-toolbar-btn\"\n [class.active]=\"state.showMarkers\"\n [disabled]=\"!hasMarkers\"\n (click)=\"onToggleMarkers()\"\n title=\"View Markers\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z\"/>\n </svg>\n </button>\n\n <!-- \u8907\u88FD -->\n <button\n class=\"ag-toolbar-btn\"\n [disabled]=\"!hasMarkers\"\n (click)=\"onCopy()\"\n title=\"Copy to Clipboard\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/>\n </svg>\n </button>\n\n <!-- \u6E05\u9664 -->\n <button\n class=\"ag-toolbar-btn\"\n [disabled]=\"!hasMarkers\"\n (click)=\"onClear()\"\n title=\"Clear Markers\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"3 6 5 6 21 6\"/>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\n </svg>\n </button>\n\n <!-- \u5206\u9694\u7DDA -->\n <div class=\"ag-toolbar-divider\"></div>\n\n <!-- \u8A2D\u5B9A -->\n <!-- <button\n class=\"ag-toolbar-btn\"\n [class.active]=\"state.showSettings\"\n (click)=\"onToggleSettings()\"\n title=\"Settings\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/>\n </svg>\n </button> -->\n\n <!-- \u6536\u5408\u6309\u9215 -->\n <button\n class=\"ag-toolbar-btn ag-btn-collapse\"\n (click)=\"onToggleMinimize()\"\n title=\"Collapse Toolbar\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"4\" y1=\"6\" x2=\"14\" y2=\"6\"/>\n <line x1=\"4\" y1=\"12\" x2=\"14\" y2=\"12\"/>\n <line x1=\"4\" y1=\"18\" x2=\"14\" y2=\"18\"/>\n <path d=\"M20 9l-3 3 3 3\"/>\n </svg>\n </button>\n </div>\n </ng-container>\n\n <!-- Settings Panel -->\n <!-- <ag-settings-panel\n *ngIf=\"state.showSettings\"\n [settings]=\"settings\"\n (settingsChange)=\"settingsChange.emit($event)\"\n (closed)=\"onToggleSettings()\"\n ></ag-settings-panel> -->\n</div>\n", styles: [".ag-toolbar{position:fixed;bottom:24px;right:24px;z-index:999999;display:flex;align-items:center;gap:6px;background:#12121a;border:1px solid #2a2a3a;padding:8px 12px;clip-path:polygon(0 12px,12px 0,calc(100% - 12px) 0,100% 12px,100% calc(100% - 12px),calc(100% - 12px) 100%,12px 100%,0 calc(100% - 12px));transition:border-color .3s,box-shadow .3s}.ag-toolbar:before{content:\"\";position:absolute;inset:0;pointer-events:none;background:linear-gradient(180deg,rgba(0,255,136,.03) 0%,transparent 100%)}.ag-toolbar.recording{border-color:#f369;animation:recordingPulse 2s ease-in-out infinite}.ag-toolbar.collapsed{padding:0;clip-path:polygon(0 8px,8px 0,calc(100% - 8px) 0,100% 8px,100% calc(100% - 8px),calc(100% - 8px) 100%,8px 100%,0 calc(100% - 8px));width:48px;height:48px;justify-content:center;border-color:#00ff884d;box-shadow:0 0 8px #00ff8826}.ag-toolbar.collapsed .ag-toolbar-buttons,.ag-toolbar.collapsed .ag-marker-badge{display:none}.ag-marker-badge{position:absolute;top:-8px;left:-8px;min-width:20px;height:20px;padding:0 5px;display:flex;align-items:center;justify-content:center;background:#0f8;color:#0a0a0f;font-family:Orbitron,monospace;font-size:10px;font-weight:700;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));box-shadow:0 0 8px #0f89}.ag-toolbar-buttons{display:flex;align-items:center;gap:2px}.ag-toolbar-btn{display:flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;border:1px solid transparent;background:transparent;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));color:#6b7280;cursor:pointer;transition:all .15s ease}.ag-toolbar-btn svg{width:18px;height:18px;stroke-width:1.5}.ag-toolbar-btn:hover:not(:disabled){background:#00ff8814;border-color:#0f83;color:#0f8;filter:drop-shadow(0 0 4px rgba(0,255,136,.4))}.ag-toolbar-btn:active:not(:disabled){transform:scale(.93)}.ag-toolbar-btn:disabled{color:#6b72804d;cursor:not-allowed}.ag-toolbar-btn.active{background:#00ff881a;border-color:#00ff884d;color:#0f8;box-shadow:inset 0 0 6px #00ff881a}.ag-btn-expand{width:48px;height:48px}.ag-btn-expand:hover:not(:disabled){background:#00ff8814}.ag-btn-record.active{background:#ff33661a;border-color:#f366;color:#f36}.ag-btn-record.active svg{animation:recordBlink 1s step-end infinite;filter:drop-shadow(0 0 4px #ff3366)}.ag-toolbar-divider{width:1px;height:22px;background:#2a2a3a;margin:0 4px}@keyframes recordBlink{0%,to{opacity:1}50%{opacity:.4}}@keyframes recordingPulse{0%,to{box-shadow:0 0 5px #f36,0 0 15px #ff336640,0 0 0 1px #f366}50%{box-shadow:0 0 12px #f36,0 0 25px #f366,0 0 0 1px #f36}}\n"] }]
2016
+ }], propDecorators: { session: [{
2017
+ type: Input
2018
+ }], settings: [{
2019
+ type: Input
2020
+ }], isDarkMode: [{
2021
+ type: HostBinding,
2022
+ args: ['class.ag-dark-mode']
2023
+ }], state: [{
2024
+ type: Input
2025
+ }], startRecording: [{
2026
+ type: Output
2027
+ }], stopRecording: [{
2028
+ type: Output
2029
+ }], toggleMarkers: [{
2030
+ type: Output
2031
+ }], copyToClipboard: [{
2032
+ type: Output
2033
+ }], clearMarkers: [{
2034
+ type: Output
2035
+ }], toggleSettings: [{
2036
+ type: Output
2037
+ }], closeToolbar: [{
2038
+ type: Output
2039
+ }], toggleMinimize: [{
2040
+ type: Output
2041
+ }], settingsChange: [{
2042
+ type: Output
2043
+ }] } });
2044
+
2045
+ /**
2046
+ * SettingsPanelComponent
2047
+ *
2048
+ * 設定面板:顏色選擇、輸出詳細程度、選項開關等
2049
+ */
2050
+ class SettingsPanelComponent {
2051
+ mcpService;
2052
+ /** 當前設定 */
2053
+ settings = DEFAULT_SETTINGS;
2054
+ /** 綁定 dark mode class 到 host */
2055
+ get isDarkMode() {
2056
+ return this.settings.isDarkMode;
2057
+ }
2058
+ /** 面板關閉時觸發 */
2059
+ closed = new EventEmitter();
2060
+ /** 設定變更時觸發 */
2061
+ settingsChange = new EventEmitter();
2062
+ mcpStatus$;
2063
+ constructor(mcpService) {
2064
+ this.mcpService = mcpService;
2065
+ this.mcpStatus$ = this.mcpService.status$;
2066
+ }
2067
+ connectMcp() {
2068
+ this.mcpService.connect();
2069
+ }
2070
+ /** 可用顏色列表 */
2071
+ colors = ['purple', 'blue', 'cyan', 'green', 'yellow', 'orange', 'red'];
2072
+ /** 顏色對應的 HEX 值 */
2073
+ colorHex = MARKER_COLORS;
2074
+ /** 輸出詳細程度選項 */
2075
+ outputOptions = ['compact', 'standard', 'detailed', 'forensic'];
2076
+ /** 選擇顏色 */
2077
+ selectColor(color) {
2078
+ this.updateSettings({ markerColor: color });
2079
+ }
2080
+ /** 選擇輸出詳細程度 */
2081
+ selectOutputDetail(detail) {
2082
+ this.updateSettings({ outputDetail: detail });
2083
+ }
2084
+ /** 切換 Angular 組件顯示 */
2085
+ toggleAngularComponents() {
2086
+ this.updateSettings({ showAngularComponents: !this.settings.showAngularComponents });
2087
+ }
2088
+ /** 切換複製後清除 */
2089
+ toggleClearOnCopy() {
2090
+ this.updateSettings({ clearOnCopy: !this.settings.clearOnCopy });
2091
+ }
2092
+ /** 切換阻止頁面互動 */
2093
+ toggleBlockInteractions() {
2094
+ this.updateSettings({ blockPageInteractions: !this.settings.blockPageInteractions });
2095
+ }
2096
+ /** 切換主題(深色/淺色) */
2097
+ toggleTheme() {
2098
+ this.updateSettings({ isDarkMode: !this.settings.isDarkMode });
2099
+ }
2100
+ /** 關閉面板 */
2101
+ close() {
2102
+ this.closed.emit();
2103
+ }
2104
+ /** 更新設定 */
2105
+ updateSettings(partial) {
2106
+ this.settings = { ...this.settings, ...partial };
2107
+ this.settingsChange.emit(this.settings);
2108
+ }
2109
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: SettingsPanelComponent, deps: [{ token: McpService }], target: i0.ɵɵFactoryTarget.Component });
2110
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: SettingsPanelComponent, isStandalone: false, selector: "ag-settings-panel", inputs: { settings: "settings" }, outputs: { closed: "closed", settingsChange: "settingsChange" }, host: { properties: { "class.ag-dark-mode": "this.isDarkMode" } }, ngImport: i0, template: "<div class=\"ag-settings-panel\">\n <!-- \u6A19\u984C -->\n <header class=\"ag-settings-header\">\n <div class=\"ag-settings-title\">\n <span class=\"ag-logo\">/agentation</span>\n <span class=\"ag-version\">v1.0.0</span>\n </div>\n <button class=\"ag-theme-toggle\" title=\"Toggle Theme\" (click)=\"toggleTheme()\">\n <!-- \u6708\u4EAE\u5716\u793A (light mode) -->\n <svg *ngIf=\"!settings.isDarkMode\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/>\n </svg>\n <!-- \u592A\u967D\u5716\u793A (dark mode) -->\n <svg *ngIf=\"settings.isDarkMode\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"/>\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"/>\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"/>\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"/>\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"/>\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"/>\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"/>\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"/>\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"/>\n </svg>\n </button>\n </header>\n\n <!-- \u8F38\u51FA\u8A73\u7D30\u7A0B\u5EA6 -->\n <section class=\"ag-settings-section\">\n <div class=\"ag-settings-row\">\n <label class=\"ag-settings-label\">\n Output Detail\n <span class=\"ag-help-icon\" title=\"Level of detail in the generated output\">?</span>\n </label>\n <div class=\"ag-dropdown\">\n <select\n [value]=\"settings.outputDetail\"\n (change)=\"selectOutputDetail($any($event.target).value)\"\n >\n <option value=\"compact\">Compact</option>\n <option value=\"standard\">Standard</option>\n <option value=\"detailed\">Detailed</option>\n <option value=\"forensic\">Forensic</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- Angular \u7D44\u4EF6\u958B\u95DC -->\n <!-- <section class=\"ag-settings-section\">\n <div class=\"ag-settings-row\">\n <label class=\"ag-settings-label\">\n Angular Components\n <span class=\"ag-help-icon\" title=\"Include Angular component metadata in output\">?</span>\n </label>\n <label class=\"ag-toggle\">\n <input\n type=\"checkbox\"\n [checked]=\"settings.showAngularComponents\"\n (change)=\"toggleAngularComponents()\"\n />\n <span class=\"ag-toggle-slider\"></span>\n </label>\n </div>\n </section> -->\n\n <!-- \u6A19\u8A18\u984F\u8272 -->\n <section class=\"ag-settings-section\">\n <label class=\"ag-settings-label\">Marker Colour</label>\n <div class=\"ag-color-picker\">\n <button\n *ngFor=\"let color of colors\"\n class=\"ag-color-option\"\n [class.selected]=\"settings.markerColor === color\"\n [style.background-color]=\"colorHex[color]\"\n (click)=\"selectColor(color)\"\n [title]=\"color\"\n ></button>\n </div>\n </section>\n\n <!-- \u5176\u4ED6\u9078\u9805 -->\n <section class=\"ag-settings-section ag-options-section\">\n <label class=\"ag-checkbox-row\">\n <input\n type=\"checkbox\"\n [checked]=\"settings.clearOnCopy\"\n (change)=\"toggleClearOnCopy()\"\n />\n <span>Clear on copy/send</span>\n <span class=\"ag-help-icon\" title=\"Clear markers after copying to clipboard\">?</span>\n </label>\n\n <!-- <label class=\"ag-checkbox-row\">\n <input\n type=\"checkbox\"\n [checked]=\"settings.blockPageInteractions\"\n (change)=\"toggleBlockInteractions()\"\n />\n <span>Block page interactions</span>\n </label> -->\n </section>\n\n <!-- MCP & Webhooks \u9023\u7D50 -->\n <!-- <section class=\"ag-settings-section ag-link-section\">\n <a href=\"javascript:void(0)\" class=\"ag-settings-link\">\n Manage MCP &amp; Webhooks\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"9 18 15 12 9 6\"/>\n </svg>\n </a>\n </section> -->\n</div>\n", styles: ["@charset \"UTF-8\";.ag-settings-panel{position:absolute;bottom:calc(100% + 12px);right:0;left:auto;transform:none;z-index:999998;width:320px;background:#12121a;border:1px solid #2a2a3a;clip-path:polygon(0 10px,10px 0,calc(100% - 10px) 0,100% 10px,100% calc(100% - 10px),calc(100% - 10px) 100%,10px 100%,0 calc(100% - 10px));font-family:JetBrains Mono,monospace;box-shadow:0 0 20px #00ff880f,0 10px 40px #000000b3}.ag-settings-panel:before{content:\"\";position:absolute;top:0;left:10px;right:10px;height:1px;background:linear-gradient(90deg,transparent,#00ff88,transparent);pointer-events:none}.ag-settings-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #2a2a3a;background:#0a0a0f;position:relative}.ag-settings-header:before{content:\"\\25cf \\25cf \\25cf\";position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:7px;letter-spacing:3px;color:transparent;text-shadow:0 0 0 #ff3366,8px 0 0 #f59e0b,16px 0 0 #00ff88;pointer-events:none}.ag-settings-title{display:flex;align-items:baseline;gap:8px;padding-left:36px}.ag-logo{font-family:Orbitron,monospace;font-size:13px;font-weight:700;letter-spacing:.1em;color:#0f8;text-shadow:0 0 6px rgba(0,255,136,.4)}.ag-version{font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.1em;color:#6b7280}.ag-theme-toggle{display:flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:1px solid rgba(0,255,136,.2);background:transparent;color:#6b7280;cursor:pointer;transition:all .15s ease;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-theme-toggle svg{width:16px;height:16px;stroke-width:1.5}.ag-theme-toggle:hover{background:#00ff8814;border-color:#0f86;color:#0f8;filter:drop-shadow(0 0 4px rgba(0,255,136,.4))}.ag-settings-section{padding:14px 18px;border-bottom:1px solid #2a2a3a}.ag-settings-section:last-child{border-bottom:none}.ag-settings-row{display:flex;align-items:center;justify-content:space-between;gap:12px}.ag-settings-label{display:flex;align-items:center;gap:6px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:#6b7280}.ag-help-icon{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;font-size:9px;font-family:Orbitron,monospace;color:#00ff8880;border:1px solid rgba(0,255,136,.2);clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px));cursor:help}.ag-dropdown select{padding:6px 28px 6px 10px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;color:#0f8;background:#0a0a0f;border:1px solid rgba(0,255,136,.25);clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));cursor:pointer;appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2300ff88' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 8px center;transition:border-color .15s,box-shadow .15s}.ag-dropdown select:focus{outline:none;border-color:#00ff8880;box-shadow:0 0 6px #00ff881f}.ag-dropdown select option{background:#12121a;color:#e0e0e0}.ag-toggle{position:relative;display:inline-block;width:40px;height:22px}.ag-toggle input{opacity:0;width:0;height:0}.ag-toggle-slider{position:absolute;cursor:pointer;inset:0;background:#1c1c2e;border:1px solid #2a2a3a;transition:.2s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-toggle-slider:before{position:absolute;content:\"\";height:14px;width:14px;left:3px;bottom:3px;background:#6b7280;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px));transition:.2s}input:checked+.ag-toggle-slider{background:#00ff881a;border-color:#0f86;box-shadow:0 0 6px #0f83}input:checked+.ag-toggle-slider:before{transform:translate(18px);background:#0f8;box-shadow:0 0 4px #00ff8880}.ag-color-picker{display:flex;gap:8px;margin-top:10px}.ag-color-option{width:28px;height:28px;padding:0;border:2px solid transparent;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));cursor:pointer;transition:all .15s ease;position:relative}.ag-color-option:hover{filter:brightness(1.2);transform:scale(1.1)}.ag-color-option.selected{border-color:#e0e0e0;box-shadow:0 0 8px #ffffff4d}.ag-options-section{display:flex;flex-direction:column;gap:10px}.ag-checkbox-row{display:flex;align-items:center;gap:10px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#6b7280;cursor:pointer;transition:color .15s}.ag-checkbox-row:hover{color:#e0e0e0}.ag-checkbox-row input[type=checkbox]{width:16px;height:16px;accent-color:#00ff88;cursor:pointer}.ag-link-section{padding:12px 18px}.ag-settings-link{display:flex;align-items:center;justify-content:space-between;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#6b7280;text-decoration:none;transition:color .15s}.ag-settings-link svg{width:14px;height:14px;color:#6b728080}.ag-settings-link:hover{color:#0f8}.ag-settings-link:hover svg{color:#0f8}.ag-status-indicator{display:flex;align-items:center;gap:8px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#6b7280;padding:6px 10px;background:#0a0a0f;border:1px solid #2a2a3a;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-status-indicator .ag-status-dot{width:6px;height:6px;background:#6b728066;clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%);transition:background-color .3s}.ag-status-indicator.connected{color:#0f8;border-color:#00ff884d;background:#00ff880a}.ag-status-indicator.connected .ag-status-dot{background:#0f8;box-shadow:0 0 4px #0f89}.ag-btn-small{padding:4px 10px;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:#0a0a0f;background:#0f8;border:none;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px));cursor:pointer;transition:filter .15s;box-shadow:0 0 8px #00ff884d}.ag-btn-small:hover{filter:brightness(1.1)}.ag-error-text{color:#f36;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.05em;margin-top:4px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }] });
2111
+ }
2112
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: SettingsPanelComponent, decorators: [{
2113
+ type: Component,
2114
+ args: [{ selector: 'ag-settings-panel', standalone: false, template: "<div class=\"ag-settings-panel\">\n <!-- \u6A19\u984C -->\n <header class=\"ag-settings-header\">\n <div class=\"ag-settings-title\">\n <span class=\"ag-logo\">/agentation</span>\n <span class=\"ag-version\">v1.0.0</span>\n </div>\n <button class=\"ag-theme-toggle\" title=\"Toggle Theme\" (click)=\"toggleTheme()\">\n <!-- \u6708\u4EAE\u5716\u793A (light mode) -->\n <svg *ngIf=\"!settings.isDarkMode\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/>\n </svg>\n <!-- \u592A\u967D\u5716\u793A (dark mode) -->\n <svg *ngIf=\"settings.isDarkMode\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"/>\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"/>\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"/>\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"/>\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"/>\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"/>\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"/>\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"/>\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"/>\n </svg>\n </button>\n </header>\n\n <!-- \u8F38\u51FA\u8A73\u7D30\u7A0B\u5EA6 -->\n <section class=\"ag-settings-section\">\n <div class=\"ag-settings-row\">\n <label class=\"ag-settings-label\">\n Output Detail\n <span class=\"ag-help-icon\" title=\"Level of detail in the generated output\">?</span>\n </label>\n <div class=\"ag-dropdown\">\n <select\n [value]=\"settings.outputDetail\"\n (change)=\"selectOutputDetail($any($event.target).value)\"\n >\n <option value=\"compact\">Compact</option>\n <option value=\"standard\">Standard</option>\n <option value=\"detailed\">Detailed</option>\n <option value=\"forensic\">Forensic</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- Angular \u7D44\u4EF6\u958B\u95DC -->\n <!-- <section class=\"ag-settings-section\">\n <div class=\"ag-settings-row\">\n <label class=\"ag-settings-label\">\n Angular Components\n <span class=\"ag-help-icon\" title=\"Include Angular component metadata in output\">?</span>\n </label>\n <label class=\"ag-toggle\">\n <input\n type=\"checkbox\"\n [checked]=\"settings.showAngularComponents\"\n (change)=\"toggleAngularComponents()\"\n />\n <span class=\"ag-toggle-slider\"></span>\n </label>\n </div>\n </section> -->\n\n <!-- \u6A19\u8A18\u984F\u8272 -->\n <section class=\"ag-settings-section\">\n <label class=\"ag-settings-label\">Marker Colour</label>\n <div class=\"ag-color-picker\">\n <button\n *ngFor=\"let color of colors\"\n class=\"ag-color-option\"\n [class.selected]=\"settings.markerColor === color\"\n [style.background-color]=\"colorHex[color]\"\n (click)=\"selectColor(color)\"\n [title]=\"color\"\n ></button>\n </div>\n </section>\n\n <!-- \u5176\u4ED6\u9078\u9805 -->\n <section class=\"ag-settings-section ag-options-section\">\n <label class=\"ag-checkbox-row\">\n <input\n type=\"checkbox\"\n [checked]=\"settings.clearOnCopy\"\n (change)=\"toggleClearOnCopy()\"\n />\n <span>Clear on copy/send</span>\n <span class=\"ag-help-icon\" title=\"Clear markers after copying to clipboard\">?</span>\n </label>\n\n <!-- <label class=\"ag-checkbox-row\">\n <input\n type=\"checkbox\"\n [checked]=\"settings.blockPageInteractions\"\n (change)=\"toggleBlockInteractions()\"\n />\n <span>Block page interactions</span>\n </label> -->\n </section>\n\n <!-- MCP & Webhooks \u9023\u7D50 -->\n <!-- <section class=\"ag-settings-section ag-link-section\">\n <a href=\"javascript:void(0)\" class=\"ag-settings-link\">\n Manage MCP &amp; Webhooks\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"9 18 15 12 9 6\"/>\n </svg>\n </a>\n </section> -->\n</div>\n", styles: ["@charset \"UTF-8\";.ag-settings-panel{position:absolute;bottom:calc(100% + 12px);right:0;left:auto;transform:none;z-index:999998;width:320px;background:#12121a;border:1px solid #2a2a3a;clip-path:polygon(0 10px,10px 0,calc(100% - 10px) 0,100% 10px,100% calc(100% - 10px),calc(100% - 10px) 100%,10px 100%,0 calc(100% - 10px));font-family:JetBrains Mono,monospace;box-shadow:0 0 20px #00ff880f,0 10px 40px #000000b3}.ag-settings-panel:before{content:\"\";position:absolute;top:0;left:10px;right:10px;height:1px;background:linear-gradient(90deg,transparent,#00ff88,transparent);pointer-events:none}.ag-settings-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #2a2a3a;background:#0a0a0f;position:relative}.ag-settings-header:before{content:\"\\25cf \\25cf \\25cf\";position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:7px;letter-spacing:3px;color:transparent;text-shadow:0 0 0 #ff3366,8px 0 0 #f59e0b,16px 0 0 #00ff88;pointer-events:none}.ag-settings-title{display:flex;align-items:baseline;gap:8px;padding-left:36px}.ag-logo{font-family:Orbitron,monospace;font-size:13px;font-weight:700;letter-spacing:.1em;color:#0f8;text-shadow:0 0 6px rgba(0,255,136,.4)}.ag-version{font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.1em;color:#6b7280}.ag-theme-toggle{display:flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:1px solid rgba(0,255,136,.2);background:transparent;color:#6b7280;cursor:pointer;transition:all .15s ease;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-theme-toggle svg{width:16px;height:16px;stroke-width:1.5}.ag-theme-toggle:hover{background:#00ff8814;border-color:#0f86;color:#0f8;filter:drop-shadow(0 0 4px rgba(0,255,136,.4))}.ag-settings-section{padding:14px 18px;border-bottom:1px solid #2a2a3a}.ag-settings-section:last-child{border-bottom:none}.ag-settings-row{display:flex;align-items:center;justify-content:space-between;gap:12px}.ag-settings-label{display:flex;align-items:center;gap:6px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:#6b7280}.ag-help-icon{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;font-size:9px;font-family:Orbitron,monospace;color:#00ff8880;border:1px solid rgba(0,255,136,.2);clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px));cursor:help}.ag-dropdown select{padding:6px 28px 6px 10px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;color:#0f8;background:#0a0a0f;border:1px solid rgba(0,255,136,.25);clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));cursor:pointer;appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2300ff88' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 8px center;transition:border-color .15s,box-shadow .15s}.ag-dropdown select:focus{outline:none;border-color:#00ff8880;box-shadow:0 0 6px #00ff881f}.ag-dropdown select option{background:#12121a;color:#e0e0e0}.ag-toggle{position:relative;display:inline-block;width:40px;height:22px}.ag-toggle input{opacity:0;width:0;height:0}.ag-toggle-slider{position:absolute;cursor:pointer;inset:0;background:#1c1c2e;border:1px solid #2a2a3a;transition:.2s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-toggle-slider:before{position:absolute;content:\"\";height:14px;width:14px;left:3px;bottom:3px;background:#6b7280;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px));transition:.2s}input:checked+.ag-toggle-slider{background:#00ff881a;border-color:#0f86;box-shadow:0 0 6px #0f83}input:checked+.ag-toggle-slider:before{transform:translate(18px);background:#0f8;box-shadow:0 0 4px #00ff8880}.ag-color-picker{display:flex;gap:8px;margin-top:10px}.ag-color-option{width:28px;height:28px;padding:0;border:2px solid transparent;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));cursor:pointer;transition:all .15s ease;position:relative}.ag-color-option:hover{filter:brightness(1.2);transform:scale(1.1)}.ag-color-option.selected{border-color:#e0e0e0;box-shadow:0 0 8px #ffffff4d}.ag-options-section{display:flex;flex-direction:column;gap:10px}.ag-checkbox-row{display:flex;align-items:center;gap:10px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#6b7280;cursor:pointer;transition:color .15s}.ag-checkbox-row:hover{color:#e0e0e0}.ag-checkbox-row input[type=checkbox]{width:16px;height:16px;accent-color:#00ff88;cursor:pointer}.ag-link-section{padding:12px 18px}.ag-settings-link{display:flex;align-items:center;justify-content:space-between;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#6b7280;text-decoration:none;transition:color .15s}.ag-settings-link svg{width:14px;height:14px;color:#6b728080}.ag-settings-link:hover{color:#0f8}.ag-settings-link:hover svg{color:#0f8}.ag-status-indicator{display:flex;align-items:center;gap:8px;font-family:Share Tech Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#6b7280;padding:6px 10px;background:#0a0a0f;border:1px solid #2a2a3a;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-status-indicator .ag-status-dot{width:6px;height:6px;background:#6b728066;clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%);transition:background-color .3s}.ag-status-indicator.connected{color:#0f8;border-color:#00ff884d;background:#00ff880a}.ag-status-indicator.connected .ag-status-dot{background:#0f8;box-shadow:0 0 4px #0f89}.ag-btn-small{padding:4px 10px;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:#0a0a0f;background:#0f8;border:none;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px));cursor:pointer;transition:filter .15s;box-shadow:0 0 8px #00ff884d}.ag-btn-small:hover{filter:brightness(1.1)}.ag-error-text{color:#f36;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.05em;margin-top:4px}\n"] }]
2115
+ }], ctorParameters: () => [{ type: McpService }], propDecorators: { settings: [{
2116
+ type: Input
2117
+ }], isDarkMode: [{
2118
+ type: HostBinding,
2119
+ args: ['class.ag-dark-mode']
2120
+ }], closed: [{
2121
+ type: Output
2122
+ }], settingsChange: [{
2123
+ type: Output
2124
+ }] } });
2125
+
2126
+ /**
2127
+ * MarkersPanelComponent
2128
+ *
2129
+ * 已標記組件列表:顯示、編輯意圖、刪除標記
2130
+ */
2131
+ class MarkersPanelComponent {
2132
+ /** 標記列表 */
2133
+ markers = [];
2134
+ /** 面板關閉時觸發 */
2135
+ closed = new EventEmitter();
2136
+ /** 刪除標記時觸發 */
2137
+ deleteMarker = new EventEmitter();
2138
+ /** 更新標記意圖時觸發 */
2139
+ updateIntent = new EventEmitter();
2140
+ /** 跳轉到標記時觸發 */
2141
+ scrollToMarker = new EventEmitter();
2142
+ /** 顏色對應的 HEX 值 */
2143
+ colorHex = MARKER_COLORS;
2144
+ /** 當前編輯的標記索引 */
2145
+ editingIndex = null;
2146
+ /** 編輯中的意圖文字 */
2147
+ editingIntent = '';
2148
+ /** 開始編輯意圖 */
2149
+ startEdit(marker) {
2150
+ this.editingIndex = marker.index;
2151
+ this.editingIntent = marker.intent;
2152
+ }
2153
+ /** 保存編輯 */
2154
+ saveEdit() {
2155
+ if (this.editingIndex !== null) {
2156
+ this.updateIntent.emit({
2157
+ index: this.editingIndex,
2158
+ intent: this.editingIntent,
2159
+ });
2160
+ this.editingIndex = null;
2161
+ this.editingIntent = '';
2162
+ }
2163
+ }
2164
+ /** 取消編輯 */
2165
+ cancelEdit() {
2166
+ this.editingIndex = null;
2167
+ this.editingIntent = '';
2168
+ }
2169
+ /** 刪除標記 */
2170
+ onDelete(index) {
2171
+ this.deleteMarker.emit(index);
2172
+ }
2173
+ /** 跳轉到標記 */
2174
+ onScrollTo(index) {
2175
+ this.scrollToMarker.emit(index);
2176
+ }
2177
+ /** 關閉面板 */
2178
+ close() {
2179
+ this.closed.emit();
2180
+ }
2181
+ /** 處理 Enter 鍵 */
2182
+ onKeyDown(event) {
2183
+ if (event.key === 'Enter' && !event.shiftKey) {
2184
+ event.preventDefault();
2185
+ this.saveEdit();
2186
+ }
2187
+ else if (event.key === 'Escape') {
2188
+ this.cancelEdit();
2189
+ }
2190
+ }
2191
+ /** trackBy 函數 */
2192
+ trackByIndex(index, marker) {
2193
+ return marker.index;
2194
+ }
2195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: MarkersPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2196
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: MarkersPanelComponent, isStandalone: false, selector: "ag-markers-panel", inputs: { markers: "markers" }, outputs: { closed: "closed", deleteMarker: "deleteMarker", updateIntent: "updateIntent", scrollToMarker: "scrollToMarker" }, ngImport: i0, template: "<div class=\"ag-markers-panel\">\n <!-- \u6A19\u984C -->\n <header class=\"ag-markers-header\">\n <h3 class=\"ag-markers-title\">\n <span class=\"ag-markers-icon\">\uD83D\uDCCC</span>\n Marked Components\n <span class=\"ag-markers-count\">{{ markers.length }}</span>\n </h3>\n <button class=\"ag-close-btn\" (click)=\"close()\" title=\"Close\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </header>\n\n <!-- \u6A19\u8A18\u5217\u8868 -->\n <div class=\"ag-markers-list\" *ngIf=\"markers.length > 0\">\n <div\n class=\"ag-marker-item\"\n *ngFor=\"let marker of markers; trackBy: trackByIndex\"\n >\n <!-- \u6A19\u8A18\u7DE8\u865F -->\n <div\n class=\"ag-marker-number\"\n [style.background-color]=\"colorHex[marker.color]\"\n (click)=\"onScrollTo(marker.index)\"\n >\n {{ marker.index }}\n </div>\n\n <!-- \u6A19\u8A18\u5167\u5BB9 -->\n <div class=\"ag-marker-content\">\n <div class=\"ag-marker-component\">\n <span class=\"ag-marker-name\">{{ marker.target.displayName }}</span>\n <code class=\"ag-marker-selector\">&lt;{{ marker.target.selector }}&gt;</code>\n </div>\n\n <!-- \u610F\u5716\u986F\u793A/\u7DE8\u8F2F -->\n <div class=\"ag-marker-intent\" *ngIf=\"editingIndex !== marker.index\">\n <span class=\"ag-intent-text\" *ngIf=\"marker.intent\">{{ marker.intent }}</span>\n <button class=\"ag-edit-btn\" (click)=\"startEdit(marker)\" title=\"Edit intent\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n {{ marker.intent ? 'Edit' : 'Add intent' }}\n </button>\n </div>\n\n <!-- \u7DE8\u8F2F\u6A21\u5F0F -->\n <div class=\"ag-marker-edit\" *ngIf=\"editingIndex === marker.index\">\n <textarea\n class=\"ag-edit-textarea\"\n [(ngModel)]=\"editingIntent\"\n (keydown)=\"onKeyDown($event)\"\n placeholder=\"Describe what you want AI to do...\"\n rows=\"2\"\n ></textarea>\n <div class=\"ag-edit-actions\">\n <button class=\"ag-save-btn\" (click)=\"saveEdit()\">Save</button>\n <button class=\"ag-cancel-btn\" (click)=\"cancelEdit()\">Cancel</button>\n </div>\n </div>\n </div>\n\n <!-- \u522A\u9664\u6309\u9215 -->\n <button class=\"ag-delete-btn\" (click)=\"onDelete(marker.index)\" title=\"Delete marker\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- \u7A7A\u72C0\u614B -->\n <div class=\"ag-markers-empty\" *ngIf=\"markers.length === 0\">\n <span class=\"ag-empty-icon\">\uD83D\uDCED</span>\n <p>No markers yet</p>\n <span class=\"ag-empty-hint\">Click on components to add markers</span>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";.ag-markers-panel{position:fixed;bottom:80px;left:50%;transform:translate(-50%);z-index:999998;width:400px;max-height:420px;background:#12121a;border:1px solid #2a2a3a;clip-path:polygon(0 12px,12px 0,calc(100% - 12px) 0,100% 12px,100% calc(100% - 12px),calc(100% - 12px) 100%,12px 100%,0 calc(100% - 12px));overflow:hidden;display:flex;flex-direction:column;box-shadow:0 0 20px #00ff880f,0 10px 40px #000000b3}.ag-markers-panel:before{content:\"\";position:absolute;top:0;left:12px;right:12px;height:1px;background:linear-gradient(90deg,transparent,#00ff88,transparent);z-index:1;pointer-events:none}.ag-markers-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #2a2a3a;background:#0a0a0f;flex-shrink:0;position:relative}.ag-markers-header:before{content:\"\\25cf \\25cf \\25cf\";position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:7px;letter-spacing:3px;color:transparent;text-shadow:0 0 0 #ff3366,8px 0 0 #f59e0b,16px 0 0 #00ff88;pointer-events:none}.ag-markers-title{display:flex;align-items:center;gap:8px;margin:0;padding-left:36px;font-family:Orbitron,monospace;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.15em;color:#e0e0e0}.ag-markers-icon{font-size:14px}.ag-markers-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 5px;background:#0f8;color:#0a0a0f;font-family:Orbitron,monospace;font-size:10px;font-weight:700;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));box-shadow:0 0 6px #00ff8880}.ag-close-btn{display:flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:1px solid rgba(255,51,102,.3);background:transparent;color:#6b7280;cursor:pointer;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-close-btn svg{width:14px;height:14px}.ag-close-btn:hover{background:#ff336626;border-color:#f36;color:#f36;box-shadow:0 0 6px #ff33664d}.ag-markers-list{flex:1;overflow-y:auto;padding:8px}.ag-markers-list::-webkit-scrollbar{width:4px}.ag-markers-list::-webkit-scrollbar-track{background:#0a0a0f}.ag-markers-list::-webkit-scrollbar-thumb{background:#2a2a3a}.ag-marker-item{display:flex;align-items:flex-start;gap:10px;padding:10px;border:1px solid transparent;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));transition:background .1s,border-color .1s}.ag-marker-item+.ag-marker-item{margin-top:4px}.ag-marker-item:hover{background:#1c1c2e;border-color:#00ff8826}.ag-marker-item:hover .ag-delete-btn{opacity:1}.ag-marker-number{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:26px;height:26px;color:#0a0a0f;font-family:Orbitron,monospace;font-size:11px;font-weight:700;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));cursor:pointer;transition:filter .15s,transform .1s}.ag-marker-number:hover{filter:brightness(1.2);transform:scale(1.1)}.ag-marker-content{flex:1;min-width:0}.ag-marker-component{display:flex;align-items:baseline;gap:7px;margin-bottom:5px;flex-wrap:wrap}.ag-marker-name{font-family:JetBrains Mono,monospace;font-size:12px;font-weight:600;color:#e0e0e0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ag-marker-selector{font-family:Share Tech Mono,monospace;font-size:11px;color:#00d4ff;background:#00d4ff0f;border:1px solid rgba(0,212,255,.15);padding:0 5px;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-marker-intent{display:flex;align-items:center;gap:8px}.ag-intent-text{font-size:11px;color:#6b7280;line-height:1.4;letter-spacing:.02em}.ag-edit-btn{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid rgba(0,255,136,.2);background:transparent;color:#0f89;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;transition:all .15s;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-edit-btn svg{width:10px;height:10px}.ag-edit-btn:hover{background:#00ff8814;border-color:#0f86;color:#0f8}.ag-marker-edit{margin-top:7px}.ag-edit-textarea{width:100%;padding:7px 10px;background:#0a0a0f;border:1px solid #2a2a3a;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));color:#0f8;font-family:JetBrains Mono,monospace;font-size:12px;resize:none}.ag-edit-textarea::placeholder{color:#6b728080}.ag-edit-textarea:focus{outline:none;border-color:#00ff8880;box-shadow:0 0 6px #00ff881a}.ag-edit-actions{display:flex;gap:6px;margin-top:7px}.ag-save-btn,.ag-cancel-btn{padding:5px 12px;border:none;background:transparent;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.12em;text-transform:uppercase;cursor:pointer;transition:all .15s;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-save-btn{border:1px solid #00ff88;color:#0f8}.ag-save-btn:hover{background:#0f8;color:#0a0a0f;box-shadow:0 0 8px #0f86}.ag-cancel-btn{border:1px solid #2a2a3a;color:#6b7280}.ag-cancel-btn:hover{border-color:#6b728066;color:#e0e0e0;background:#6b72800d}.ag-delete-btn{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:1px solid transparent;background:transparent;color:#f366;cursor:pointer;opacity:0;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-delete-btn svg{width:12px;height:12px}.ag-delete-btn:hover{background:#ff33661f;border-color:#f366;color:#f36;opacity:1;box-shadow:0 0 6px #f363}.ag-markers-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 20px;text-align:center;gap:10px}.ag-empty-icon{font-size:28px;opacity:.5}.ag-markers-empty p{margin:0;font-family:Orbitron,monospace;font-size:12px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:#6b7280}.ag-empty-hint{font-family:Share Tech Mono,monospace;font-size:11px;color:#6b728099;letter-spacing:.08em}.ag-empty-hint:before{content:\"> \";color:#0f86}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
2197
+ }
2198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: MarkersPanelComponent, decorators: [{
2199
+ type: Component,
2200
+ args: [{ selector: 'ag-markers-panel', standalone: false, template: "<div class=\"ag-markers-panel\">\n <!-- \u6A19\u984C -->\n <header class=\"ag-markers-header\">\n <h3 class=\"ag-markers-title\">\n <span class=\"ag-markers-icon\">\uD83D\uDCCC</span>\n Marked Components\n <span class=\"ag-markers-count\">{{ markers.length }}</span>\n </h3>\n <button class=\"ag-close-btn\" (click)=\"close()\" title=\"Close\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </header>\n\n <!-- \u6A19\u8A18\u5217\u8868 -->\n <div class=\"ag-markers-list\" *ngIf=\"markers.length > 0\">\n <div\n class=\"ag-marker-item\"\n *ngFor=\"let marker of markers; trackBy: trackByIndex\"\n >\n <!-- \u6A19\u8A18\u7DE8\u865F -->\n <div\n class=\"ag-marker-number\"\n [style.background-color]=\"colorHex[marker.color]\"\n (click)=\"onScrollTo(marker.index)\"\n >\n {{ marker.index }}\n </div>\n\n <!-- \u6A19\u8A18\u5167\u5BB9 -->\n <div class=\"ag-marker-content\">\n <div class=\"ag-marker-component\">\n <span class=\"ag-marker-name\">{{ marker.target.displayName }}</span>\n <code class=\"ag-marker-selector\">&lt;{{ marker.target.selector }}&gt;</code>\n </div>\n\n <!-- \u610F\u5716\u986F\u793A/\u7DE8\u8F2F -->\n <div class=\"ag-marker-intent\" *ngIf=\"editingIndex !== marker.index\">\n <span class=\"ag-intent-text\" *ngIf=\"marker.intent\">{{ marker.intent }}</span>\n <button class=\"ag-edit-btn\" (click)=\"startEdit(marker)\" title=\"Edit intent\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>\n </svg>\n {{ marker.intent ? 'Edit' : 'Add intent' }}\n </button>\n </div>\n\n <!-- \u7DE8\u8F2F\u6A21\u5F0F -->\n <div class=\"ag-marker-edit\" *ngIf=\"editingIndex === marker.index\">\n <textarea\n class=\"ag-edit-textarea\"\n [(ngModel)]=\"editingIntent\"\n (keydown)=\"onKeyDown($event)\"\n placeholder=\"Describe what you want AI to do...\"\n rows=\"2\"\n ></textarea>\n <div class=\"ag-edit-actions\">\n <button class=\"ag-save-btn\" (click)=\"saveEdit()\">Save</button>\n <button class=\"ag-cancel-btn\" (click)=\"cancelEdit()\">Cancel</button>\n </div>\n </div>\n </div>\n\n <!-- \u522A\u9664\u6309\u9215 -->\n <button class=\"ag-delete-btn\" (click)=\"onDelete(marker.index)\" title=\"Delete marker\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- \u7A7A\u72C0\u614B -->\n <div class=\"ag-markers-empty\" *ngIf=\"markers.length === 0\">\n <span class=\"ag-empty-icon\">\uD83D\uDCED</span>\n <p>No markers yet</p>\n <span class=\"ag-empty-hint\">Click on components to add markers</span>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";.ag-markers-panel{position:fixed;bottom:80px;left:50%;transform:translate(-50%);z-index:999998;width:400px;max-height:420px;background:#12121a;border:1px solid #2a2a3a;clip-path:polygon(0 12px,12px 0,calc(100% - 12px) 0,100% 12px,100% calc(100% - 12px),calc(100% - 12px) 100%,12px 100%,0 calc(100% - 12px));overflow:hidden;display:flex;flex-direction:column;box-shadow:0 0 20px #00ff880f,0 10px 40px #000000b3}.ag-markers-panel:before{content:\"\";position:absolute;top:0;left:12px;right:12px;height:1px;background:linear-gradient(90deg,transparent,#00ff88,transparent);z-index:1;pointer-events:none}.ag-markers-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #2a2a3a;background:#0a0a0f;flex-shrink:0;position:relative}.ag-markers-header:before{content:\"\\25cf \\25cf \\25cf\";position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:7px;letter-spacing:3px;color:transparent;text-shadow:0 0 0 #ff3366,8px 0 0 #f59e0b,16px 0 0 #00ff88;pointer-events:none}.ag-markers-title{display:flex;align-items:center;gap:8px;margin:0;padding-left:36px;font-family:Orbitron,monospace;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.15em;color:#e0e0e0}.ag-markers-icon{font-size:14px}.ag-markers-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 5px;background:#0f8;color:#0a0a0f;font-family:Orbitron,monospace;font-size:10px;font-weight:700;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));box-shadow:0 0 6px #00ff8880}.ag-close-btn{display:flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:1px solid rgba(255,51,102,.3);background:transparent;color:#6b7280;cursor:pointer;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-close-btn svg{width:14px;height:14px}.ag-close-btn:hover{background:#ff336626;border-color:#f36;color:#f36;box-shadow:0 0 6px #ff33664d}.ag-markers-list{flex:1;overflow-y:auto;padding:8px}.ag-markers-list::-webkit-scrollbar{width:4px}.ag-markers-list::-webkit-scrollbar-track{background:#0a0a0f}.ag-markers-list::-webkit-scrollbar-thumb{background:#2a2a3a}.ag-marker-item{display:flex;align-items:flex-start;gap:10px;padding:10px;border:1px solid transparent;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));transition:background .1s,border-color .1s}.ag-marker-item+.ag-marker-item{margin-top:4px}.ag-marker-item:hover{background:#1c1c2e;border-color:#00ff8826}.ag-marker-item:hover .ag-delete-btn{opacity:1}.ag-marker-number{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:26px;height:26px;color:#0a0a0f;font-family:Orbitron,monospace;font-size:11px;font-weight:700;clip-path:polygon(0 5px,5px 0,calc(100% - 5px) 0,100% 5px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px));cursor:pointer;transition:filter .15s,transform .1s}.ag-marker-number:hover{filter:brightness(1.2);transform:scale(1.1)}.ag-marker-content{flex:1;min-width:0}.ag-marker-component{display:flex;align-items:baseline;gap:7px;margin-bottom:5px;flex-wrap:wrap}.ag-marker-name{font-family:JetBrains Mono,monospace;font-size:12px;font-weight:600;color:#e0e0e0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ag-marker-selector{font-family:Share Tech Mono,monospace;font-size:11px;color:#00d4ff;background:#00d4ff0f;border:1px solid rgba(0,212,255,.15);padding:0 5px;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-marker-intent{display:flex;align-items:center;gap:8px}.ag-intent-text{font-size:11px;color:#6b7280;line-height:1.4;letter-spacing:.02em}.ag-edit-btn{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid rgba(0,255,136,.2);background:transparent;color:#0f89;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;transition:all .15s;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-edit-btn svg{width:10px;height:10px}.ag-edit-btn:hover{background:#00ff8814;border-color:#0f86;color:#0f8}.ag-marker-edit{margin-top:7px}.ag-edit-textarea{width:100%;padding:7px 10px;background:#0a0a0f;border:1px solid #2a2a3a;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));color:#0f8;font-family:JetBrains Mono,monospace;font-size:12px;resize:none}.ag-edit-textarea::placeholder{color:#6b728080}.ag-edit-textarea:focus{outline:none;border-color:#00ff8880;box-shadow:0 0 6px #00ff881a}.ag-edit-actions{display:flex;gap:6px;margin-top:7px}.ag-save-btn,.ag-cancel-btn{padding:5px 12px;border:none;background:transparent;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.12em;text-transform:uppercase;cursor:pointer;transition:all .15s;clip-path:polygon(0 3px,3px 0,calc(100% - 3px) 0,100% 3px,100% calc(100% - 3px),calc(100% - 3px) 100%,3px 100%,0 calc(100% - 3px))}.ag-save-btn{border:1px solid #00ff88;color:#0f8}.ag-save-btn:hover{background:#0f8;color:#0a0a0f;box-shadow:0 0 8px #0f86}.ag-cancel-btn{border:1px solid #2a2a3a;color:#6b7280}.ag-cancel-btn:hover{border-color:#6b728066;color:#e0e0e0;background:#6b72800d}.ag-delete-btn{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:1px solid transparent;background:transparent;color:#f366;cursor:pointer;opacity:0;transition:all .15s;clip-path:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px))}.ag-delete-btn svg{width:12px;height:12px}.ag-delete-btn:hover{background:#ff33661f;border-color:#f366;color:#f36;opacity:1;box-shadow:0 0 6px #f363}.ag-markers-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 20px;text-align:center;gap:10px}.ag-empty-icon{font-size:28px;opacity:.5}.ag-markers-empty p{margin:0;font-family:Orbitron,monospace;font-size:12px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:#6b7280}.ag-empty-hint{font-family:Share Tech Mono,monospace;font-size:11px;color:#6b728099;letter-spacing:.08em}.ag-empty-hint:before{content:\"> \";color:#0f86}\n"] }]
2201
+ }], propDecorators: { markers: [{
2202
+ type: Input
2203
+ }], closed: [{
2204
+ type: Output
2205
+ }], deleteMarker: [{
2206
+ type: Output
2207
+ }], updateIntent: [{
2208
+ type: Output
2209
+ }], scrollToMarker: [{
2210
+ type: Output
2211
+ }] } });
2212
+
2213
+ class AgentationComponent {
2214
+ promptGenerator;
2215
+ isDev = isDevMode();
2216
+ /** 設定 */
2217
+ settings = { ...DEFAULT_SETTINGS };
2218
+ /** 工具列狀態 */
2219
+ toolbarState = {
2220
+ showSettings: false,
2221
+ showMarkers: false,
2222
+ isRecording: false,
2223
+ isMinimized: false,
2224
+ };
2225
+ /** 錄製會話 */
2226
+ session = {
2227
+ id: this.generateId(),
2228
+ markers: [],
2229
+ startTime: 0,
2230
+ isRecording: false,
2231
+ };
2232
+ // Just used for legacy panel if needed, but mainly for overlay
2233
+ selectedNode = null;
2234
+ constructor(promptGenerator) {
2235
+ this.promptGenerator = promptGenerator;
2236
+ }
2237
+ ngOnInit() {
2238
+ }
2239
+ // ==================== 工具列事件 ====================
2240
+ /** 開始錄製 */
2241
+ onStartRecording() {
2242
+ this.session = {
2243
+ id: this.generateId(),
2244
+ markers: [],
2245
+ startTime: Date.now(),
2246
+ isRecording: true,
2247
+ };
2248
+ this.toolbarState.isRecording = true;
2249
+ this.toolbarState.showSettings = false;
2250
+ this.toolbarState.showMarkers = false;
2251
+ }
2252
+ /** 停止錄製 */
2253
+ onStopRecording() {
2254
+ this.session.isRecording = false;
2255
+ this.session.endTime = Date.now();
2256
+ this.toolbarState.isRecording = false;
2257
+ }
2258
+ /** 處理錄製狀態變更(來自 Overlay 快捷鍵等) */
2259
+ onRecordingChanged(isRecording) {
2260
+ if (isRecording) {
2261
+ this.onStartRecording();
2262
+ }
2263
+ else {
2264
+ this.onStopRecording();
2265
+ }
2266
+ }
2267
+ /** 切換顯示標記列表 */
2268
+ onToggleMarkers() {
2269
+ this.toolbarState.showMarkers = !this.toolbarState.showMarkers;
2270
+ if (this.toolbarState.showMarkers) {
2271
+ this.toolbarState.showSettings = false;
2272
+ }
2273
+ }
2274
+ /** 複製到剪貼簿 */
2275
+ async onCopyToClipboard() {
2276
+ if (this.session.markers.length === 0)
2277
+ return;
2278
+ const markdown = this.generateMultiMarkerOutput();
2279
+ try {
2280
+ await navigator.clipboard.writeText(markdown);
2281
+ console.log('[Agentation] Copied to clipboard');
2282
+ if (this.settings.clearOnCopy) {
2283
+ this.onClearMarkers();
2284
+ }
2285
+ }
2286
+ catch (err) {
2287
+ console.error('[Agentation] Failed to copy:', err);
2288
+ }
2289
+ }
2290
+ /** 清除所有標記 */
2291
+ onClearMarkers() {
2292
+ this.session.markers = [];
2293
+ }
2294
+ /** 切換設定面板 */
2295
+ onToggleSettings() {
2296
+ this.toolbarState.showSettings = !this.toolbarState.showSettings;
2297
+ if (this.toolbarState.showSettings) {
2298
+ this.toolbarState.showMarkers = false;
2299
+ }
2300
+ }
2301
+ /** 關閉工具列 */
2302
+ onCloseToolbar() {
2303
+ this.toolbarState.isRecording = false;
2304
+ this.toolbarState.showSettings = false;
2305
+ this.toolbarState.showMarkers = false;
2306
+ this.session.markers = [];
2307
+ }
2308
+ /** 切換最小化 */
2309
+ onToggleMinimize() {
2310
+ this.toolbarState.isMinimized = !this.toolbarState.isMinimized;
2311
+ // 如果工具列收合,則關閉設定面板
2312
+ if (this.toolbarState.isMinimized) {
2313
+ this.toolbarState.showSettings = false;
2314
+ }
2315
+ }
2316
+ /** 設定變更 */
2317
+ onSettingsChange(newSettings) {
2318
+ // 如果顏色有變更,更新所有已存在的 markers
2319
+ if (newSettings.markerColor !== this.settings.markerColor) {
2320
+ this.session.markers = this.session.markers.map((m) => ({
2321
+ ...m,
2322
+ color: newSettings.markerColor,
2323
+ }));
2324
+ }
2325
+ this.settings = newSettings;
2326
+ }
2327
+ // ==================== 標記事件 ====================
2328
+ /** 新增標記 */
2329
+ onMarkerAdded(node) {
2330
+ const marker = {
2331
+ index: this.session.markers.length + 1,
2332
+ target: node,
2333
+ intent: '',
2334
+ color: this.settings.markerColor,
2335
+ timestamp: Date.now(),
2336
+ };
2337
+ this.session.markers = [...this.session.markers, marker];
2338
+ // console.log('[Agentation] Marker added:', marker);
2339
+ }
2340
+ /** 刪除標記 */
2341
+ onDeleteMarker(index) {
2342
+ this.session.markers = this.session.markers
2343
+ .filter((m) => m.index !== index)
2344
+ .map((m, i) => ({ ...m, index: i + 1 }));
2345
+ }
2346
+ /** 更新標記意圖 */
2347
+ onUpdateIntent(event) {
2348
+ this.session.markers = this.session.markers.map((m) => m.index === event.index ? { ...m, intent: event.intent } : m);
2349
+ }
2350
+ /** 跳轉到標記 */
2351
+ onScrollToMarker(index) {
2352
+ const marker = this.session.markers.find((m) => m.index === index);
2353
+ if (marker) {
2354
+ marker.target.domElement.scrollIntoView({
2355
+ behavior: 'smooth',
2356
+ block: 'center',
2357
+ });
2358
+ }
2359
+ }
2360
+ // ==================== 輔助方法 ====================
2361
+ /** 生成多標記輸出 */
2362
+ generateMultiMarkerOutput() {
2363
+ const markers = this.session.markers.map((marker) => ({
2364
+ target: marker.target,
2365
+ intent: marker.intent || '',
2366
+ }));
2367
+ return this.promptGenerator.generatePageFeedback(markers, {
2368
+ outputDetail: this.settings.outputDetail,
2369
+ pageUrl: window.location.href,
2370
+ viewport: { width: window.innerWidth, height: window.innerHeight },
2371
+ userAgent: navigator.userAgent,
2372
+ timestamp: Date.now(),
2373
+ });
2374
+ }
2375
+ /** 生成唯一 ID */
2376
+ generateId() {
2377
+ return `session-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
2378
+ }
2379
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AgentationComponent, deps: [{ token: PromptGeneratorService }], target: i0.ɵɵFactoryTarget.Component });
2380
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: AgentationComponent, isStandalone: false, selector: "ag-directive-zero", ngImport: i0, template: "<ng-container *ngIf=\"isDev\">\n <!-- Overlay (\u591A\u9078\u6A19\u8A18) -->\n <ag-overlay [markers]=\"session.markers\" [settings]=\"settings\" [isRecording]=\"toolbarState.isRecording\"\n [isMinimized]=\"toolbarState.isMinimized\" (markerAdded)=\"onMarkerAdded($event)\"\n (markerDeleted)=\"onDeleteMarker($event)\" (recordingChanged)=\"onRecordingChanged($event)\"></ag-overlay>\n\n <!-- Toolbar (\u6D6E\u52D5\u5DE5\u5177\u5217) -->\n <ag-toolbar [session]=\"session\" [settings]=\"settings\" [state]=\"toolbarState\" (startRecording)=\"onStartRecording()\"\n (stopRecording)=\"onStopRecording()\" (toggleMarkers)=\"onToggleMarkers()\" (copyToClipboard)=\"onCopyToClipboard()\"\n (clearMarkers)=\"onClearMarkers()\" (toggleSettings)=\"onToggleSettings()\" (toggleMinimize)=\"onToggleMinimize()\"\n (closeToolbar)=\"onCloseToolbar()\" (settingsChange)=\"onSettingsChange($event)\"></ag-toolbar>\n\n <!-- Markers Panel -->\n <ag-markers-panel *ngIf=\"toolbarState.showMarkers\" [markers]=\"session.markers\" (deleteMarker)=\"onDeleteMarker($event)\"\n (updateIntent)=\"onUpdateIntent($event)\" (scrollToMarker)=\"onScrollToMarker($event)\"\n (closed)=\"toolbarState.showMarkers = false\"></ag-markers-panel>\n</ng-container>", styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: OverlayComponent, selector: "ag-overlay", inputs: ["markers", "settings", "isRecording", "isMinimized"], outputs: ["markerAdded", "componentSelected", "componentHovered", "recordingChanged", "markerDeleted"] }, { kind: "component", type: ToolbarComponent, selector: "ag-toolbar", inputs: ["session", "settings", "state"], outputs: ["startRecording", "stopRecording", "toggleMarkers", "copyToClipboard", "clearMarkers", "toggleSettings", "closeToolbar", "toggleMinimize", "settingsChange"] }, { kind: "component", type: MarkersPanelComponent, selector: "ag-markers-panel", inputs: ["markers"], outputs: ["closed", "deleteMarker", "updateIntent", "scrollToMarker"] }] });
2381
+ }
2382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AgentationComponent, decorators: [{
2383
+ type: Component,
2384
+ args: [{ selector: 'ag-directive-zero', standalone: false, template: "<ng-container *ngIf=\"isDev\">\n <!-- Overlay (\u591A\u9078\u6A19\u8A18) -->\n <ag-overlay [markers]=\"session.markers\" [settings]=\"settings\" [isRecording]=\"toolbarState.isRecording\"\n [isMinimized]=\"toolbarState.isMinimized\" (markerAdded)=\"onMarkerAdded($event)\"\n (markerDeleted)=\"onDeleteMarker($event)\" (recordingChanged)=\"onRecordingChanged($event)\"></ag-overlay>\n\n <!-- Toolbar (\u6D6E\u52D5\u5DE5\u5177\u5217) -->\n <ag-toolbar [session]=\"session\" [settings]=\"settings\" [state]=\"toolbarState\" (startRecording)=\"onStartRecording()\"\n (stopRecording)=\"onStopRecording()\" (toggleMarkers)=\"onToggleMarkers()\" (copyToClipboard)=\"onCopyToClipboard()\"\n (clearMarkers)=\"onClearMarkers()\" (toggleSettings)=\"onToggleSettings()\" (toggleMinimize)=\"onToggleMinimize()\"\n (closeToolbar)=\"onCloseToolbar()\" (settingsChange)=\"onSettingsChange($event)\"></ag-toolbar>\n\n <!-- Markers Panel -->\n <ag-markers-panel *ngIf=\"toolbarState.showMarkers\" [markers]=\"session.markers\" (deleteMarker)=\"onDeleteMarker($event)\"\n (updateIntent)=\"onUpdateIntent($event)\" (scrollToMarker)=\"onScrollToMarker($event)\"\n (closed)=\"toolbarState.showMarkers = false\"></ag-markers-panel>\n</ng-container>", styles: [":host{display:block}\n"] }]
2385
+ }], ctorParameters: () => [{ type: PromptGeneratorService }] });
2386
+
2387
+ /**
2388
+ * NgDirectiveZeroModule
2389
+ *
2390
+ * Angular 版本的 Agentation 工具
2391
+ * 提供視覺化 DOM 檢查器、組件樹遍歷、AI 語意化序列化等功能
2392
+ *
2393
+ * 使用方式:
2394
+ * ```typescript
2395
+ * @NgModule({
2396
+ * imports: [NgDirectiveZeroModule.forRoot()]
2397
+ * })
2398
+ * export class AppModule {}
2399
+ * ```
2400
+ */
2401
+ class NgDirectiveZeroModule {
2402
+ /**
2403
+ * 在根模組中使用,提供單例服務
2404
+ */
2405
+ static forRoot() {
2406
+ return {
2407
+ ngModule: NgDirectiveZeroModule,
2408
+ providers: [
2409
+ ComponentWalkerService,
2410
+ DataSanitizerService,
2411
+ PromptGeneratorService,
2412
+ McpService,
2413
+ ],
2414
+ };
2415
+ }
2416
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: NgDirectiveZeroModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2417
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.18", ngImport: i0, type: NgDirectiveZeroModule, declarations: [OverlayComponent,
2418
+ AnnotationPanelComponent,
2419
+ ToolbarComponent,
2420
+ SettingsPanelComponent,
2421
+ MarkersPanelComponent,
2422
+ InlineEditorComponent,
2423
+ AgentationComponent], imports: [CommonModule,
2424
+ FormsModule,
2425
+ HttpClientModule], exports: [OverlayComponent,
2426
+ AnnotationPanelComponent,
2427
+ ToolbarComponent,
2428
+ SettingsPanelComponent,
2429
+ MarkersPanelComponent,
2430
+ InlineEditorComponent,
2431
+ AgentationComponent] });
2432
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: NgDirectiveZeroModule, imports: [CommonModule,
2433
+ FormsModule,
2434
+ HttpClientModule] });
2435
+ }
2436
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: NgDirectiveZeroModule, decorators: [{
2437
+ type: NgModule,
2438
+ args: [{
2439
+ declarations: [
2440
+ OverlayComponent,
2441
+ AnnotationPanelComponent,
2442
+ ToolbarComponent,
2443
+ SettingsPanelComponent,
2444
+ MarkersPanelComponent,
2445
+ InlineEditorComponent,
2446
+ AgentationComponent,
2447
+ ],
2448
+ imports: [
2449
+ CommonModule,
2450
+ FormsModule,
2451
+ HttpClientModule,
2452
+ ],
2453
+ exports: [
2454
+ OverlayComponent,
2455
+ AnnotationPanelComponent,
2456
+ ToolbarComponent,
2457
+ SettingsPanelComponent,
2458
+ MarkersPanelComponent,
2459
+ InlineEditorComponent,
2460
+ AgentationComponent,
2461
+ ],
2462
+ }]
2463
+ }] });
2464
+
2465
+ /*
2466
+ * ng-directive-zero Public API
2467
+ */
2468
+ // Module
2469
+
2470
+ /**
2471
+ * Generated bundle index. Do not edit.
2472
+ */
2473
+
2474
+ export { AgentationComponent, AnnotationPanelComponent, ComponentWalkerService, DEFAULT_SETTINGS, DataSanitizerService, InlineEditorComponent, KEY_COMPUTED_STYLES, MARKER_COLORS, MarkersPanelComponent, McpService, NgDirectiveZeroModule, OverlayComponent, PromptGeneratorService, SettingsPanelComponent, ToolbarComponent };
2475
+ //# sourceMappingURL=ng-directive-zero.mjs.map