aegon-dangerious 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/build-report/components/AiPromptGenerateDialog.vue +349 -0
  2. package/build-report/components/CapsuleScrollbar.vue +145 -0
  3. package/build-report/components/ChapterTitleScroll.vue +292 -0
  4. package/build-report/components/PromptField.vue +314 -0
  5. package/build-report/components/RecentReportsTable.vue +408 -0
  6. package/build-report/components/ReportChapterPreview.vue +437 -0
  7. package/build-report/components/ReportChapterWorkspace.vue +923 -0
  8. package/build-report/components/ReportCollapsibleSection.vue +132 -0
  9. package/build-report/components/ReportConfigPanel.vue +609 -0
  10. package/build-report/components/ReportGenerateSettingsPanel.vue +403 -0
  11. package/build-report/components/ReportGeneratingPanel.vue +908 -0
  12. package/build-report/components/ReportHistoryList.vue +157 -0
  13. package/build-report/components/ReportHistoryPanel.vue +140 -0
  14. package/build-report/components/ReportPromptPanel.vue +445 -0
  15. package/build-report/components/ReportSectionConfirmPopover.vue +232 -0
  16. package/build-report/components/ReportSourceFileSummary.vue +544 -0
  17. package/build-report/components/ReportSourcesMoreOverlay.vue +279 -0
  18. package/build-report/components/ReportSourcesPanel.vue +1055 -0
  19. package/build-report/components/ReportTemplateDetail.vue +176 -0
  20. package/build-report/components/ReportTemplateEditorFormPanel.vue +602 -0
  21. package/build-report/components/ReportTemplatePicker.vue +802 -0
  22. package/build-report/components/ReportTemplatePromptField.vue +314 -0
  23. package/build-report/components/ReportWorkflowSidebar.vue +104 -0
  24. package/build-report/components/reportEdit.vue +334 -0
  25. package/build-report/index.vue +646 -0
  26. package/build-report/report-list.vue +407 -0
  27. package/build-report/report-template-data.ts +248 -0
  28. package/build-report/styles/report-workflow-dialog.css +33 -0
  29. package/build-report/useReportWorkflow.ts +343 -0
  30. package/build-report/utils/report-edit-route.ts +109 -0
  31. package/build-report/utils/scroll.ts +361 -0
  32. package/package.json +12 -0
@@ -0,0 +1,602 @@
1
+ <template>
2
+ <section class="template-editor-form">
3
+ <div class="template-editor-form__row">
4
+ <label class="template-editor-field template-editor-field--row">
5
+ <span class="template-editor-label">主要語言<span class="template-editor-label__required">*</span></span>
6
+ <div class="template-editor-field__control template-editor-field__control--lang">
7
+ <el-select v-model="form.language" class="template-editor-field__lang">
8
+ <el-option v-for="item in languageOptions" :key="item.value" :label="item.label" :value="item.value" />
9
+ </el-select>
10
+ </div>
11
+ </label>
12
+ </div>
13
+
14
+ <div class="template-editor-form__row">
15
+ <label class="template-editor-field template-editor-field--row">
16
+ <span class="template-editor-label">模板標題<span class="template-editor-label__required">*</span></span>
17
+ <div class="template-editor-field__control">
18
+ <el-input v-model="form.title" :maxlength="TEMPLATE_TITLE_MAX" show-word-limit
19
+ class="template-editor-field__input" />
20
+ </div>
21
+ </label>
22
+ </div>
23
+
24
+ <div class="template-editor-form__row">
25
+ <label class="template-editor-field template-editor-field--row template-editor-field--textarea-row">
26
+ <span class="template-editor-label">模板描述<span class="template-editor-label__required">*</span></span>
27
+ <div class="template-editor-field__control">
28
+ <el-input v-model="form.description" type="textarea" :rows="4" :maxlength="TEMPLATE_DESC_MAX" show-word-limit
29
+ resize="none" class="template-editor-field__textarea" />
30
+ </div>
31
+ </label>
32
+ </div>
33
+
34
+ <div class="template-editor-chapters">
35
+ <div v-for="chapter in form.chapters" :key="chapter.id" class="template-editor-chapter">
36
+ <button type="button" class="template-editor-chapter__header" @click="toggleChapter(chapter.id)">
37
+ <span class="template-editor-chapter__drag" aria-hidden="true">
38
+ <i /><i /><i />
39
+ </span>
40
+ <span class="template-editor-chapter__header-title-wrap">
41
+ <span class="template-editor-chapter__header-title">
42
+ {{ getChapterDisplayTitle(chapter) }}
43
+ </span>
44
+ <el-icon v-if="isChapterTitleEdited(chapter)" class="template-editor-chapter__title-edited"
45
+ aria-label="章節標題已修改">
46
+ <Edit />
47
+ </el-icon>
48
+ </span>
49
+ <el-icon class="template-editor-chapter__chevron"
50
+ :class="{ 'template-editor-chapter__chevron--expanded': chapter.expanded }">
51
+ <ArrowRight />
52
+ </el-icon>
53
+ </button>
54
+
55
+ <div v-if="chapter.expanded" class="template-editor-chapter__body">
56
+ <div class="template-editor-chapter__toolbar">
57
+ <button v-if="isEditMode" type="button"
58
+ class="template-center-toolbar-btn gen-ai-action-btn template-editor-chapter__action-btn" :class="isChapterEditing(chapter.id)
59
+ ? 'template-center-toolbar-btn--outline template-center-toolbar-btn--delete'
60
+ : 'template-center-toolbar-btn--ghost'"
61
+ :disabled="isChapterEditing(chapter.id) && form.chapters.length <= 1"
62
+ @click.stop="handleChapterToolbarAction(chapter)">
63
+ {{ isChapterEditing(chapter.id) ? '刪除' : '編輯' }}
64
+ </button>
65
+ <button v-else type="button"
66
+ class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--outline template-center-toolbar-btn--delete template-editor-chapter__action-btn"
67
+ :disabled="form.chapters.length <= 1" @click.stop="handleDeleteChapter(chapter)">
68
+ 刪除
69
+ </button>
70
+ </div>
71
+
72
+ <template v-if="showChapterFields(chapter.id)">
73
+ <label class="template-editor-field template-editor-field--row">
74
+ <span class="template-editor-label">章節標題</span>
75
+ <div class="template-editor-field__control">
76
+ <el-input v-model="chapter.title" :maxlength="CHAPTER_TITLE_MAX" show-word-limit
77
+ class="template-editor-field__input" />
78
+ </div>
79
+ </label>
80
+
81
+ <label class="template-editor-field template-editor-field--row template-editor-field--textarea-row">
82
+ <span class="template-editor-label">章節描述</span>
83
+ <div class="template-editor-field__control">
84
+ <el-input v-model="chapter.description" type="textarea" :rows="4" :maxlength="CHAPTER_DESC_MAX"
85
+ show-word-limit resize="none" class="template-editor-field__textarea" />
86
+ </div>
87
+ </label>
88
+
89
+ <ReportTemplatePromptField v-model="chapter.prompt" :maxlength="CHAPTER_PROMPT_MAX"
90
+ :rows="CHAPTER_PROMPT_ROWS" :chapter-title="chapter.title" :chapter-description="chapter.description"
91
+ v-bind="resolvePromptFieldProps(chapter)" />
92
+ </template>
93
+ </div>
94
+ </div>
95
+ </div>
96
+
97
+ <div class="template-editor-form__add">
98
+ <button type="button" class="template-editor-form__add-btn" aria-label="新增章節"
99
+ :disabled="form.chapters.length >= CHAPTER_MAX_COUNT" @click="addChapter">
100
+ +
101
+ </button>
102
+ </div>
103
+
104
+ <div v-if="$slots['footer-actions']" class="template-editor-form__footer-actions">
105
+ <slot name="footer-actions" />
106
+ </div>
107
+ </section>
108
+ </template>
109
+
110
+ <script setup lang="ts">
111
+ import { ArrowRight, Edit } from '@element-plus/icons-vue'
112
+ import { ElMessage, ElMessageBox } from 'element-plus'
113
+ import { computed, ref, watch } from 'vue'
114
+ import {
115
+ createEmptyChapter,
116
+ type TemplateChapterForm,
117
+ type TemplateEditorForm,
118
+ type TemplateEditorMode,
119
+ } from '../report-template-data'
120
+ import ReportTemplatePromptField, {
121
+ type PromptFieldLayoutBind,
122
+ } from './ReportTemplatePromptField.vue'
123
+
124
+ const TEMPLATE_TITLE_MAX = 100
125
+ const TEMPLATE_DESC_MAX = 3000
126
+ const CHAPTER_TITLE_MAX = 100
127
+ const CHAPTER_DESC_MAX = 1000
128
+ const CHAPTER_PROMPT_MAX = 8000
129
+ const CHAPTER_PROMPT_ROWS = 8
130
+ const CHAPTER_MAX_COUNT = 100
131
+
132
+ const languageOptions = [
133
+ { label: '中文', value: 'zh' },
134
+ { label: 'English', value: 'en' },
135
+ ]
136
+
137
+ const props = defineProps<{
138
+ modelValue: TemplateEditorForm
139
+ editorMode: TemplateEditorMode
140
+ }>()
141
+
142
+ const emit = defineEmits<{
143
+ 'update:modelValue': [value: TemplateEditorForm]
144
+ }>()
145
+
146
+ const form = computed({
147
+ get: () => props.modelValue,
148
+ set: (value) => emit('update:modelValue', value),
149
+ })
150
+
151
+ const editingChapterId = ref<string | null>(null)
152
+ const chapterOriginalTitles = ref<Record<string, string>>({})
153
+
154
+ const isEditMode = computed(() => props.editorMode === 'edit')
155
+ const isCreateMode = computed(() => props.editorMode === 'create')
156
+
157
+ function syncChapterOriginalTitles() {
158
+ const map: Record<string, string> = {}
159
+ for (const chapter of form.value.chapters) {
160
+ map[chapter.id] = chapter.title
161
+ }
162
+ chapterOriginalTitles.value = map
163
+ }
164
+
165
+ watch(() => props.modelValue, () => syncChapterOriginalTitles(), { deep: true, immediate: true })
166
+
167
+ function isChapterEditing(chapterId: string) {
168
+ return isEditMode.value && editingChapterId.value === chapterId
169
+ }
170
+
171
+ function showChapterFields(chapterId: string) {
172
+ return !isEditMode.value || isChapterEditing(chapterId)
173
+ }
174
+
175
+ function isChapterTitleEdited(chapter: TemplateChapterForm) {
176
+ const original = chapterOriginalTitles.value[chapter.id] ?? ''
177
+ return chapter.title.trim() !== original.trim()
178
+ }
179
+
180
+ function expandChapter(id: string) {
181
+ form.value = {
182
+ ...form.value,
183
+ chapters: form.value.chapters.map((chapter) => ({
184
+ ...chapter,
185
+ expanded: chapter.id === id,
186
+ })),
187
+ }
188
+ }
189
+
190
+ function toggleChapter(id: string) {
191
+ const target = form.value.chapters.find((chapter) => chapter.id === id)
192
+ if (!target) return
193
+
194
+ if (target.expanded) {
195
+ if (editingChapterId.value === id) {
196
+ editingChapterId.value = null
197
+ }
198
+ form.value = {
199
+ ...form.value,
200
+ chapters: form.value.chapters.map((chapter) =>
201
+ chapter.id === id ? { ...chapter, expanded: false } : chapter,
202
+ ),
203
+ }
204
+ return
205
+ }
206
+
207
+ expandChapter(id)
208
+ }
209
+
210
+ function handleStartEditChapter(chapter: TemplateChapterForm) {
211
+ if (editingChapterId.value && editingChapterId.value !== chapter.id) {
212
+ ElMessage.warning('請先保存當前章節,再編輯其他章節')
213
+ return
214
+ }
215
+
216
+ editingChapterId.value = chapter.id
217
+ expandChapter(chapter.id)
218
+ }
219
+
220
+ function handleChapterToolbarAction(chapter: TemplateChapterForm) {
221
+ if (isChapterEditing(chapter.id)) {
222
+ void handleDeleteChapter(chapter)
223
+ return
224
+ }
225
+ handleStartEditChapter(chapter)
226
+ }
227
+
228
+ function addChapter() {
229
+ if (form.value.chapters.length >= CHAPTER_MAX_COUNT) {
230
+ ElMessage.warning(`章節數量已達上限 ${CHAPTER_MAX_COUNT} 個`)
231
+ return
232
+ }
233
+
234
+ const collapsed = form.value.chapters.map((chapter) => ({ ...chapter, expanded: false }))
235
+ const newChapter = createEmptyChapter(true)
236
+ form.value = {
237
+ ...form.value,
238
+ chapters: [...collapsed, newChapter],
239
+ }
240
+ chapterOriginalTitles.value = {
241
+ ...chapterOriginalTitles.value,
242
+ [newChapter.id]: '',
243
+ }
244
+ }
245
+
246
+ function removeChapter(id: string) {
247
+ if (form.value.chapters.length <= 1) return
248
+ if (editingChapterId.value === id) {
249
+ editingChapterId.value = null
250
+ }
251
+ const next = form.value.chapters.filter((chapter) => chapter.id !== id)
252
+ if (!next.some((chapter) => chapter.expanded) && next[0]) {
253
+ next[0] = { ...next[0], expanded: true }
254
+ }
255
+ form.value = { ...form.value, chapters: next }
256
+ const { [id]: _removed, ...rest } = chapterOriginalTitles.value
257
+ chapterOriginalTitles.value = rest
258
+ }
259
+
260
+ async function handleDeleteChapter(chapter: TemplateChapterForm) {
261
+ if (form.value.chapters.length <= 1) return
262
+
263
+ const chapterName = getChapterDisplayTitle(chapter)
264
+ try {
265
+ await ElMessageBox.confirm(`確定刪除章節「${chapterName}」嗎?`, '刪除', {
266
+ confirmButtonText: '確定',
267
+ cancelButtonText: '取消',
268
+ type: 'warning',
269
+ })
270
+ removeChapter(chapter.id)
271
+ ElMessage.success(`已刪除:${chapterName}`)
272
+ } catch {
273
+ // 用戶取消
274
+ }
275
+ }
276
+
277
+ function getChapterDisplayTitle(chapter: TemplateChapterForm) {
278
+ return chapter.title.trim() || '章節標題'
279
+ }
280
+
281
+ function resolvePromptFieldProps(_chapter: TemplateChapterForm): PromptFieldLayoutBind {
282
+ return {
283
+ labelWidth: '100px',
284
+ fieldGap: '16px',
285
+ width: '100%',
286
+ }
287
+ }
288
+
289
+ function resetEditingState() {
290
+ editingChapterId.value = null
291
+ }
292
+
293
+ defineExpose({
294
+ resetEditingState,
295
+ isCreateMode,
296
+ })
297
+ </script>
298
+
299
+ <style scoped>
300
+ .template-editor-form {
301
+ --gen-ai-fs-base: clamp(13px, calc(14 * 100vw / 1920), 14px);
302
+ --template-editor-label-w: 100px;
303
+ --template-editor-field-gap: 16px;
304
+ --report-accent: #c53355;
305
+ --report-accent-soft: #fdf0f2;
306
+ display: flex;
307
+ flex-direction: column;
308
+ gap: 20px;
309
+ font-size: var(--gen-ai-fs-base);
310
+ }
311
+
312
+ .template-editor-form__row {
313
+ width: 100%;
314
+ }
315
+
316
+ .template-editor-field {
317
+ margin: 0;
318
+ }
319
+
320
+ .template-editor-field--row {
321
+ display: grid;
322
+ grid-template-columns: var(--template-editor-label-w) minmax(0, 1fr);
323
+ column-gap: var(--template-editor-field-gap);
324
+ align-items: center;
325
+ width: 100%;
326
+ }
327
+
328
+ .template-editor-field--textarea-row {
329
+ align-items: start;
330
+ }
331
+
332
+ .template-editor-field--textarea-row .template-editor-label {
333
+ padding-top: 8px;
334
+ }
335
+
336
+ .template-editor-label {
337
+ flex-shrink: 0;
338
+ font-size: 14px;
339
+ font-weight: 500;
340
+ font-family: Arial, sans-serif;
341
+ color: #303133;
342
+ white-space: nowrap;
343
+ }
344
+
345
+ .template-editor-label__required {
346
+ margin-left: 2px;
347
+ color: #f56c6c;
348
+ }
349
+
350
+ .template-editor-field__control {
351
+ min-width: 0;
352
+ width: 100%;
353
+ }
354
+
355
+ .template-editor-field__control--lang {
356
+ width: auto;
357
+ max-width: 270px;
358
+ }
359
+
360
+ .template-editor-field__lang {
361
+ width: 270px;
362
+ }
363
+
364
+ .template-editor-field__input,
365
+ .template-editor-field__textarea {
366
+ width: 100%;
367
+ }
368
+
369
+ .template-editor-field__input :deep(.el-input__wrapper),
370
+ .template-editor-field__textarea :deep(.el-textarea__inner) {
371
+ font-size: var(--gen-ai-fs-base);
372
+ }
373
+
374
+ .template-editor-field__lang :deep(.el-select__wrapper) {
375
+ min-height: 32px;
376
+ }
377
+
378
+ .template-editor-field__input :deep(.el-input__count),
379
+ .template-editor-field__textarea :deep(.el-input__count) {
380
+ right: 0;
381
+ bottom: -18px;
382
+ background: transparent;
383
+ font-size: 12px;
384
+ color: #909399;
385
+ }
386
+
387
+ .template-editor-chapters {
388
+ display: flex;
389
+ flex-direction: column;
390
+ gap: 0;
391
+ margin-top: 15px;
392
+ padding-top: 15px;
393
+ border-top: 1px solid #ebeef5;
394
+ }
395
+
396
+ .template-editor-chapter {
397
+ border-bottom: 1px solid #ebeef5;
398
+ }
399
+
400
+ .template-editor-chapter:first-child {
401
+ margin-top: 16px;
402
+ }
403
+
404
+ .template-editor-chapter__header {
405
+ display: grid;
406
+ grid-template-columns: 20px var(--template-editor-label-w) minmax(0, 1fr) 20px;
407
+ column-gap: var(--template-editor-field-gap);
408
+ align-items: center;
409
+ width: 100%;
410
+ min-height: 48px;
411
+ padding: 0 4px;
412
+ border: none;
413
+ background: #fff;
414
+ text-align: left;
415
+ cursor: pointer;
416
+ transition: background-color 0.2s ease;
417
+ }
418
+
419
+ .template-editor-chapter__header:hover {
420
+ background: #f5f7fa;
421
+ }
422
+
423
+ .template-editor-chapter__body {
424
+ display: flex;
425
+ flex-direction: column;
426
+ gap: 20px;
427
+ padding: 0 4px 16px;
428
+ }
429
+
430
+ .template-editor-chapter__toolbar {
431
+ display: grid;
432
+ grid-template-columns: var(--template-editor-label-w) minmax(0, 1fr);
433
+ column-gap: var(--template-editor-field-gap);
434
+ margin-top: 20px;
435
+ }
436
+
437
+ .template-editor-chapter__action-btn {
438
+ grid-column: 2;
439
+ justify-self: end;
440
+ width: 83px;
441
+ min-width: 83px;
442
+ height: 32px;
443
+ padding: 0;
444
+ }
445
+
446
+ .template-center-toolbar-btn {
447
+ display: inline-flex;
448
+ align-items: center;
449
+ justify-content: center;
450
+ gap: 6px;
451
+ box-sizing: border-box;
452
+ height: 32px;
453
+ min-width: 83px;
454
+ padding: 0 14px;
455
+ border-radius: 4px;
456
+ border: 1px solid #dcdfe6;
457
+ background: #fff;
458
+ color: #606266;
459
+ font-size: 15px;
460
+ font-family: Arial, sans-serif;
461
+ font-weight: 400;
462
+ line-height: 1;
463
+ white-space: nowrap;
464
+ cursor: pointer;
465
+ transition: all 0.2s ease;
466
+ }
467
+
468
+ .template-center-toolbar-btn--outline {
469
+ border-color: var(--report-accent);
470
+ color: var(--report-accent);
471
+ background: #fff;
472
+ }
473
+
474
+ .template-center-toolbar-btn--outline:hover:not(:disabled) {
475
+ border-color: var(--report-accent);
476
+ color: var(--report-accent);
477
+ background: var(--report-accent-soft);
478
+ }
479
+
480
+ .template-center-toolbar-btn--delete {
481
+ border-color: var(--report-accent);
482
+ color: var(--report-accent);
483
+ background: #fff;
484
+ }
485
+
486
+ .template-center-toolbar-btn--delete:hover:not(:disabled) {
487
+ border-color: var(--report-accent);
488
+ color: var(--report-accent);
489
+ background: var(--report-accent-soft);
490
+ }
491
+
492
+ .template-center-toolbar-btn--ghost {
493
+ border: 1px solid #dcdfe6;
494
+ background: #fff;
495
+ color: #606266;
496
+ }
497
+
498
+ .template-center-toolbar-btn--ghost:hover:not(:disabled) {
499
+ border-color: var(--report-accent);
500
+ color: var(--report-accent);
501
+ background: var(--report-accent-soft);
502
+ }
503
+
504
+ .template-editor-chapter__drag {
505
+ grid-column: 1;
506
+ display: inline-flex;
507
+ flex-direction: column;
508
+ justify-content: center;
509
+ gap: 3px;
510
+ width: 12px;
511
+ flex-shrink: 0;
512
+ }
513
+
514
+ .template-editor-chapter__drag i {
515
+ display: block;
516
+ width: 4px;
517
+ height: 4px;
518
+ border-radius: 50%;
519
+ background: #909399;
520
+ }
521
+
522
+ .template-editor-chapter__header-title-wrap {
523
+ grid-column: 2 / 4;
524
+ display: inline-flex;
525
+ align-items: center;
526
+ gap: 4px;
527
+ min-width: 0;
528
+ }
529
+
530
+ .template-editor-chapter__header-title {
531
+ min-width: 0;
532
+ font-size: 14px;
533
+ font-weight: 500;
534
+ font-family: Arial, sans-serif;
535
+ color: #303133;
536
+ overflow: hidden;
537
+ text-overflow: ellipsis;
538
+ white-space: nowrap;
539
+ }
540
+
541
+ .template-editor-chapter__title-edited {
542
+ flex-shrink: 0;
543
+ font-size: 14px;
544
+ color: #c53355;
545
+ }
546
+
547
+ .template-editor-chapter__chevron {
548
+ grid-column: 4;
549
+ justify-self: end;
550
+ flex-shrink: 0;
551
+ font-size: 16px;
552
+ color: #909399;
553
+ transition: transform 0.2s ease;
554
+ }
555
+
556
+ .template-editor-chapter__chevron--expanded {
557
+ transform: rotate(90deg);
558
+ }
559
+
560
+ .template-editor-field--textarea-row .template-editor-field__control {
561
+ position: relative;
562
+ padding-bottom: 18px;
563
+ }
564
+
565
+ .template-editor-form__add {
566
+ display: flex;
567
+ justify-content: center;
568
+ padding: 8px 0 0;
569
+ }
570
+
571
+ .template-editor-form__footer-actions {
572
+ display: flex;
573
+ justify-content: center;
574
+ }
575
+
576
+ .template-editor-form__add-btn {
577
+ display: inline-flex;
578
+ align-items: center;
579
+ justify-content: center;
580
+ box-sizing: border-box;
581
+ width: 160px;
582
+ height: 39px;
583
+ border: 1px solid #dcdfe6;
584
+ border-radius: 4px;
585
+ background: #fff;
586
+ color: #606266;
587
+ font-size: 24px;
588
+ line-height: 1;
589
+ cursor: pointer;
590
+ transition: all 0.2s ease;
591
+ }
592
+
593
+ .template-editor-form__add-btn:hover:not(:disabled) {
594
+ border-color: #c53355;
595
+ color: #c53355;
596
+ }
597
+
598
+ .template-editor-form__add-btn:disabled {
599
+ opacity: 0.45;
600
+ cursor: not-allowed;
601
+ }
602
+ </style>