aegon-lv97 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.
- package/gen-ai/build-report/chartStreamRefs.ts +227 -0
- package/gen-ai/build-report/coherence-check/index.vue +559 -0
- package/gen-ai/build-report/components/ReportChapterWorkspace.vue +4571 -0
- package/gen-ai/build-report/components/ReportConfigPanel.vue +727 -0
- package/gen-ai/build-report/components/ReportGenerateSettingsPanel.vue +2361 -0
- package/gen-ai/build-report/components/ReportGeneratingPanel.vue +1765 -0
- package/gen-ai/build-report/components/ReportPromptPanel.vue +625 -0
- package/gen-ai/build-report/components/ReportSourceFileSummary.vue +665 -0
- package/gen-ai/build-report/components/ReportSourcesPanel.vue +1327 -0
- package/gen-ai/build-report/components/ReportTemplateDetail.vue +191 -0
- package/gen-ai/build-report/components/ReportTemplateEditorFormPanel.vue +667 -0
- package/gen-ai/build-report/components/ReportTemplatePicker.vue +302 -0
- package/gen-ai/build-report/components/ReportWorkflowSidebar.vue +148 -0
- package/gen-ai/build-report/components/paramsSetting.vue +886 -0
- package/gen-ai/build-report/components/reportEdit.vue +2283 -0
- package/gen-ai/build-report/index.vue +856 -0
- package/gen-ai/build-report/report-domain.ts +1133 -0
- package/gen-ai/build-report/report-template-data.ts +295 -0
- package/gen-ai/build-report/useReportWorkflow.ts +1386 -0
- package/gen-ai/common/assets/NotoSans-SC.woff2 +0 -0
- package/gen-ai/common/assets/NotoSans-TC.woff2 +0 -0
- package/gen-ai/common/assets/NotoSans.woff2 +0 -0
- package/gen-ai/common/assets/fonts.css +278 -0
- package/gen-ai/common/assets/pagination.css +163 -0
- package/gen-ai/common/directives/btnLoading.ts +455 -0
- package/gen-ai/common/directives/debounce.ts +98 -0
- package/gen-ai/common/directives/index.ts +30 -0
- package/gen-ai/common/en.md +417 -0
- package/gen-ai/common/index.ts +36 -0
- package/gen-ai/common/types/component-expose.ts +9 -0
- package/gen-ai/common/utils/api-res-status.ts +7 -0
- package/gen-ai/common/utils/navigation.ts +59 -0
- package/gen-ai/common/utils/remote-request.ts +45 -0
- package/gen-ai/common/utils/scroll.ts +577 -0
- package/gen-ai/components/AIExport/docx-export.ts +950 -0
- package/gen-ai/components/AIExport/docxExport.ts +2566 -0
- package/gen-ai/components/AIExport/index.ts +52 -0
- package/gen-ai/components/AIExport/parseMarkdownHtml.ts +653 -0
- package/gen-ai/components/CapsuleScrollbar.vue +149 -0
- package/gen-ai/components/ChapterTitleScroll.vue +468 -0
- package/gen-ai/components/EntryHeroNavCards.vue +379 -0
- package/gen-ai/components/GenAiPagination.vue +52 -0
- package/gen-ai/components/ModelCard.vue +1114 -0
- package/gen-ai/components/Preview/FilePreviewPage.vue +949 -0
- package/gen-ai/components/Preview/PreviewCapsuleScroll.vue +205 -0
- package/gen-ai/components/Preview/PreviewOutlineTree.vue +148 -0
- package/gen-ai/components/Preview/index.ts +11 -0
- package/gen-ai/components/Preview/preview.ts +724 -0
- package/gen-ai/components/PromptField.vue +505 -0
- package/gen-ai/components/RecentReportsTable.vue +974 -0
- package/gen-ai/components/ReportCollapsibleSection.vue +172 -0
- package/gen-ai/components/ReportHistoryList.vue +637 -0
- package/gen-ai/components/ReportSectionConfirmPopover.vue +251 -0
- package/gen-ai/components/editor/ChapterTiptapEditor.vue +3718 -0
- package/gen-ai/components/editor/StructureBlockView.vue +441 -0
- package/gen-ai/components/editor/TiptapToolbar.vue +1036 -0
- package/gen-ai/components/editor/TypewriterDocEditor.vue +422 -0
- package/gen-ai/components/editor/_StructureOrgNode.vue +326 -0
- package/gen-ai/components/editor/index.ts +28 -0
- package/gen-ai/components/editor/parseMarkdownHtml.ts +754 -0
- package/gen-ai/components/editor/reviewCitationTip.ts +163 -0
- package/gen-ai/gen-entry/api/index.ts +32 -0
- package/gen-ai/gen-entry/index.vue +287 -0
- package/gen-ai/gen-entry/types/index.ts +4 -0
- package/gen-ai/i.mark +2 -0
- package/gen-ai/management-center/index.vue +1815 -0
- package/gen-ai/management-center/mockData.ts +248 -0
- package/gen-ai/management-center/types/index.ts +37 -0
- package/gen-ai/model-manager/TemplateEditorFormPanel.vue +661 -0
- package/gen-ai/model-manager/api/index.ts +32 -0
- package/gen-ai/model-manager/api/mockApi.ts +33 -0
- package/gen-ai/model-manager/index.vue +1583 -0
- package/gen-ai/model-manager/mockData.ts +177 -0
- package/gen-ai/model-manager/template-detail-shared.ts +154 -0
- package/gen-ai/model-manager/template-editor.vue +345 -0
- package/gen-ai/model-manager/types/index.ts +40 -0
- package/gen-ai/model-manager/utils.ts +25 -0
- package/package.json +12 -0
|
@@ -0,0 +1,2361 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<aside
|
|
3
|
+
ref="settingsPanelRef"
|
|
4
|
+
class="chatbot-report-panel chatbot-report-panel--settings"
|
|
5
|
+
:class="{
|
|
6
|
+
'is-readonly': readonly,
|
|
7
|
+
'is-compact': !settingsOpen && (readonly || !historyOpen),
|
|
8
|
+
}"
|
|
9
|
+
>
|
|
10
|
+
<ReportCollapsibleSection v-model:open="settingsOpen" title="生成報告設置" card-hover>
|
|
11
|
+
<div class="chatbot-report-settings__content">
|
|
12
|
+
<div class="chatbot-report-chapter-select">
|
|
13
|
+
<div class="chatbot-report-chapter-header">
|
|
14
|
+
<span class="chatbot-report-chapter-header__title">選擇章節及模板</span>
|
|
15
|
+
<span
|
|
16
|
+
class="chatbot-report-chapter-header__col chatbot-report-chapter-header__col--version"
|
|
17
|
+
>版本</span
|
|
18
|
+
>
|
|
19
|
+
<span
|
|
20
|
+
class="chatbot-report-chapter-header__col chatbot-report-chapter-header__col--generator"
|
|
21
|
+
>生成人</span
|
|
22
|
+
>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<label class="chatbot-report-chapter-select-all">
|
|
26
|
+
<span
|
|
27
|
+
class="chatbot-report-chapter-row__sort chatbot-report-chapter-row__sort--placeholder"
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
/>
|
|
30
|
+
<input
|
|
31
|
+
class="chatbot-report-chapter-row__check"
|
|
32
|
+
type="checkbox"
|
|
33
|
+
:checked="allChaptersSelected"
|
|
34
|
+
:disabled="readonly"
|
|
35
|
+
@change="toggleAllChapters"
|
|
36
|
+
/>
|
|
37
|
+
<span class="chatbot-report-chapter-select-all__label">全部章節</span>
|
|
38
|
+
</label>
|
|
39
|
+
|
|
40
|
+
<div class="chatbot-report-chapter-list">
|
|
41
|
+
<div
|
|
42
|
+
v-for="(preset, index) in orderedPresets"
|
|
43
|
+
:key="preset.key"
|
|
44
|
+
class="chatbot-report-chapter-row-wrap"
|
|
45
|
+
:data-chapter-key="preset.key"
|
|
46
|
+
:class="{ 'is-popover-open': openChapterDetailKey === preset.key }"
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
class="chatbot-report-chapter-row"
|
|
50
|
+
:class="{
|
|
51
|
+
'is-dragging': dragChapterKey === preset.key,
|
|
52
|
+
'is-drag-over': dragOverChapterKey === preset.key,
|
|
53
|
+
'is-unavailable': isChapterUnavailable(preset),
|
|
54
|
+
'is-ellipsis-clickable': readonly || hasChapterRowEllipsis(preset),
|
|
55
|
+
'is-detail-open': openChapterDetailKey === preset.key,
|
|
56
|
+
}"
|
|
57
|
+
:draggable="!readonly"
|
|
58
|
+
@dragstart="onChapterDragStart($event, preset.key)"
|
|
59
|
+
@dragend="handleChapterDragEnd"
|
|
60
|
+
@dragover.prevent="dragOverChapterKey = preset.key"
|
|
61
|
+
@drop="handleChapterDrop(preset.key)"
|
|
62
|
+
@click="onChapterRowClick(preset.key, $event)"
|
|
63
|
+
>
|
|
64
|
+
<el-icon class="chatbot-report-chapter-row__sort" aria-hidden="true">
|
|
65
|
+
<Sort />
|
|
66
|
+
</el-icon>
|
|
67
|
+
<input
|
|
68
|
+
v-model="selectedChapters[preset.key]"
|
|
69
|
+
class="chatbot-report-chapter-row__check"
|
|
70
|
+
type="checkbox"
|
|
71
|
+
:disabled="readonly || isChapterUnavailable(preset)"
|
|
72
|
+
@click.stop
|
|
73
|
+
@mousedown.stop
|
|
74
|
+
/>
|
|
75
|
+
<div class="chatbot-report-chapter-row__label" @mousedown.stop @dragstart.prevent>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
class="chatbot-report-chapter-row__label-text"
|
|
79
|
+
:class="{ 'is-truncated': labelOverflowMap[preset.key] }"
|
|
80
|
+
draggable="false"
|
|
81
|
+
:ref="(el) => bindChapterLabelEl(preset.key, el)"
|
|
82
|
+
@mousedown.stop
|
|
83
|
+
>
|
|
84
|
+
<span class="chatbot-report-chapter-row__label-main">
|
|
85
|
+
{{ formatChapterDisplayTitle(index, preset.label) }}
|
|
86
|
+
</span>
|
|
87
|
+
<span
|
|
88
|
+
v-if="labelOverflowMap[preset.key]"
|
|
89
|
+
class="chatbot-report-chapter-row__label-ellipsis"
|
|
90
|
+
aria-hidden="true"
|
|
91
|
+
>...</span
|
|
92
|
+
>
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
<template v-if="isChapterUnavailable(preset)">
|
|
96
|
+
<span class="chatbot-report-chapter-row__no-version">{{
|
|
97
|
+
unavailableVersionLabel
|
|
98
|
+
}}</span>
|
|
99
|
+
</template>
|
|
100
|
+
<template v-else>
|
|
101
|
+
<el-select
|
|
102
|
+
v-model="chapterVersions[preset.key]"
|
|
103
|
+
class="chatbot-report-chapter-row__version"
|
|
104
|
+
size="small"
|
|
105
|
+
teleported
|
|
106
|
+
:disabled="readonly || !getChapterVersionOptions(preset.key).length"
|
|
107
|
+
@click.stop
|
|
108
|
+
@mousedown.stop="onVersionSelectMouseDown"
|
|
109
|
+
@visible-change="onChapterVersionVisibleChange"
|
|
110
|
+
>
|
|
111
|
+
<el-option
|
|
112
|
+
v-for="version in getChapterVersionOptions(preset.key)"
|
|
113
|
+
:key="`${preset.key}-${version.value}-${version.text}`"
|
|
114
|
+
:label="version.text"
|
|
115
|
+
:value="version.value"
|
|
116
|
+
/>
|
|
117
|
+
</el-select>
|
|
118
|
+
<span
|
|
119
|
+
class="chatbot-report-chapter-row__generator"
|
|
120
|
+
:class="{
|
|
121
|
+
'is-truncated': isGeneratorNameTruncated(preset.key),
|
|
122
|
+
}"
|
|
123
|
+
>
|
|
124
|
+
<span class="chatbot-report-chapter-row__generator-main">
|
|
125
|
+
{{ formatGeneratorDisplayName(preset.key) }}
|
|
126
|
+
</span>
|
|
127
|
+
<span
|
|
128
|
+
v-if="isGeneratorNameTruncated(preset.key)"
|
|
129
|
+
class="chatbot-report-chapter-row__generator-ellipsis"
|
|
130
|
+
aria-hidden="true"
|
|
131
|
+
>...</span
|
|
132
|
+
>
|
|
133
|
+
</span>
|
|
134
|
+
</template>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<!-- 浮動詳情:absolute 不佔文檔流空間,覆蓋在後續章節之上 -->
|
|
138
|
+
<div
|
|
139
|
+
v-if="openChapterDetailKey === preset.key && openChapterDetail"
|
|
140
|
+
class="chatbot-report-chapter-title-popover-local"
|
|
141
|
+
role="dialog"
|
|
142
|
+
aria-label="章節詳情"
|
|
143
|
+
@click.stop
|
|
144
|
+
@mousedown.stop
|
|
145
|
+
>
|
|
146
|
+
<div class="chatbot-report-chapter-detail-row">
|
|
147
|
+
<span class="chatbot-report-chapter-detail-row__label">章節標題</span>
|
|
148
|
+
<span class="chatbot-report-chapter-detail-row__value">{{
|
|
149
|
+
openChapterDetail.title
|
|
150
|
+
}}</span>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="chatbot-report-chapter-detail-row">
|
|
153
|
+
<span class="chatbot-report-chapter-detail-row__label">版本</span>
|
|
154
|
+
<span class="chatbot-report-chapter-detail-row__value">{{
|
|
155
|
+
openChapterDetail.version
|
|
156
|
+
}}</span>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="chatbot-report-chapter-detail-row">
|
|
159
|
+
<span class="chatbot-report-chapter-detail-row__label">生成人</span>
|
|
160
|
+
<span class="chatbot-report-chapter-detail-row__value">{{
|
|
161
|
+
openChapterDetail.generator
|
|
162
|
+
}}</span>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div v-if="!readonly" class="chatbot-report-settings__toolbar">
|
|
170
|
+
<div class="chatbot-report-settings__toolbar-inner">
|
|
171
|
+
<div class="chatbot-report-settings__col-coherence">
|
|
172
|
+
<el-select
|
|
173
|
+
v-if="showChapterLanguageSelect"
|
|
174
|
+
v-model="chapterContentLanguage"
|
|
175
|
+
class="chatbot-report-settings__language"
|
|
176
|
+
size="small"
|
|
177
|
+
teleported
|
|
178
|
+
@mousedown="onVersionSelectMouseDown"
|
|
179
|
+
@visible-change="onChapterVersionVisibleChange"
|
|
180
|
+
>
|
|
181
|
+
<el-option
|
|
182
|
+
v-for="item in chapterContentLanguageOptions"
|
|
183
|
+
:key="item.value"
|
|
184
|
+
:label="item.label"
|
|
185
|
+
:value="item.value"
|
|
186
|
+
/>
|
|
187
|
+
</el-select>
|
|
188
|
+
<el-button
|
|
189
|
+
class="chatbot-report-settings__btn chatbot-report-settings__btn--primary chatbot-report-settings__btn--coherence"
|
|
190
|
+
:loading="coherenceChecking"
|
|
191
|
+
:disabled="readonly || coherenceChecking"
|
|
192
|
+
@click="handleCoherenceCheck"
|
|
193
|
+
>
|
|
194
|
+
報告連貫性檢查
|
|
195
|
+
</el-button>
|
|
196
|
+
</div>
|
|
197
|
+
<el-button
|
|
198
|
+
class="chatbot-report-settings__btn chatbot-report-settings__btn--primary chatbot-report-settings__btn--generate"
|
|
199
|
+
@click="handleGenerate"
|
|
200
|
+
>
|
|
201
|
+
生成報告
|
|
202
|
+
</el-button>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</ReportCollapsibleSection>
|
|
207
|
+
|
|
208
|
+
<!-- 無報告歷史時默認收起;有內容或滾動定位時可展開 -->
|
|
209
|
+
<ReportCollapsibleSection
|
|
210
|
+
v-if="!readonly"
|
|
211
|
+
v-model:open="historyOpen"
|
|
212
|
+
title="報告歷史"
|
|
213
|
+
card-hover
|
|
214
|
+
>
|
|
215
|
+
<ReportHistoryList
|
|
216
|
+
v-model:active-key="activeHistoryKey"
|
|
217
|
+
:items="reportHistoryItems"
|
|
218
|
+
:show-title="false"
|
|
219
|
+
:editable="false"
|
|
220
|
+
@select="handleSelectHistory"
|
|
221
|
+
/>
|
|
222
|
+
</ReportCollapsibleSection>
|
|
223
|
+
</aside>
|
|
224
|
+
</template>
|
|
225
|
+
|
|
226
|
+
<script setup lang="ts">
|
|
227
|
+
import genAiReportEditorApi, {
|
|
228
|
+
pickAdjustInstanceText,
|
|
229
|
+
pickExistsInstanceVersionNo,
|
|
230
|
+
} from '@/api/modules/build-report/reportEditor'
|
|
231
|
+
import { useGenAiBuildReportStore } from '@/store/modules/gen-ai'
|
|
232
|
+
import type {
|
|
233
|
+
GenAiAdjustInstanceSection,
|
|
234
|
+
GenAiInstanceVersionVO,
|
|
235
|
+
GenAiSimTradTargetLang,
|
|
236
|
+
} from '@/types/gen-ai/build-report/reportEditor'
|
|
237
|
+
import { Sort } from '@element-plus/icons-vue'
|
|
238
|
+
import { ElButton, ElIcon, ElMessage, ElMessageBox, ElOption, ElSelect } from 'element-plus'
|
|
239
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
|
240
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
241
|
+
import { ReportCollapsibleSection, ReportHistoryList } from '../../common'
|
|
242
|
+
import { isGenAiResponseOk } from '../../common/utils/api-res-status'
|
|
243
|
+
import {
|
|
244
|
+
buildChapterOrderForHistoryRecord,
|
|
245
|
+
buildGenerationSignature,
|
|
246
|
+
CHAPTER_CONTENT_LANGUAGE_OPTIONS,
|
|
247
|
+
findFinalizedReportById,
|
|
248
|
+
isCoherenceCheckCompleted,
|
|
249
|
+
loadCoherenceCheckSession,
|
|
250
|
+
markCoherenceCheckCompleted,
|
|
251
|
+
openReportCoherenceCheck,
|
|
252
|
+
saveCoherenceCheckSession,
|
|
253
|
+
serializeChapterLayoutFromState,
|
|
254
|
+
shouldShowChapterLanguageSelect,
|
|
255
|
+
translateChapterLabel,
|
|
256
|
+
translateUnavailableVersionLabel,
|
|
257
|
+
type ChapterContentLanguage,
|
|
258
|
+
type CoherenceCheckSessionPayload,
|
|
259
|
+
type ReportChapterSelectionItem,
|
|
260
|
+
} from '../report-domain'
|
|
261
|
+
import { useReportWorkflow, type ReportHistoryItem } from '../useReportWorkflow'
|
|
262
|
+
|
|
263
|
+
const props = withDefaults(
|
|
264
|
+
defineProps<{
|
|
265
|
+
readonly?: boolean
|
|
266
|
+
coherenceSession?: CoherenceCheckSessionPayload | null
|
|
267
|
+
}>(),
|
|
268
|
+
{
|
|
269
|
+
readonly: false,
|
|
270
|
+
coherenceSession: null,
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
const UNAVAILABLE_CHAPTER_LABELS = new Set(['附件與數'])
|
|
275
|
+
|
|
276
|
+
interface ChapterPreset {
|
|
277
|
+
key: string
|
|
278
|
+
label: string
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const emit = defineEmits<{
|
|
282
|
+
generated: [{ historyKey: string; displayTitle: string }]
|
|
283
|
+
}>()
|
|
284
|
+
|
|
285
|
+
const settingsOpen = ref(true)
|
|
286
|
+
const historyOpen = ref(false)
|
|
287
|
+
const route = useRoute()
|
|
288
|
+
const router = useRouter()
|
|
289
|
+
const {
|
|
290
|
+
selectedTemplate,
|
|
291
|
+
draft,
|
|
292
|
+
patchDraft,
|
|
293
|
+
historyPreviewRecordId,
|
|
294
|
+
historyPreviewLayoutBaseline,
|
|
295
|
+
historyPreviewApplyToken,
|
|
296
|
+
markHistoryPreviewDirty,
|
|
297
|
+
selectHistoryReport,
|
|
298
|
+
reportHistoryItems,
|
|
299
|
+
activeHistoryKey,
|
|
300
|
+
historyScrollTargetKey,
|
|
301
|
+
clearHistoryScrollTarget,
|
|
302
|
+
resetHistoryPreviewState,
|
|
303
|
+
settingsChapterPresets,
|
|
304
|
+
settingsSelectedChapters,
|
|
305
|
+
settingsChapterOrder,
|
|
306
|
+
settingsChapterVersionOptions,
|
|
307
|
+
settingsChapterVersions,
|
|
308
|
+
settingsChapterGenerators,
|
|
309
|
+
applyAdjustInstanceFiles,
|
|
310
|
+
requestSettingsTypewriterReplay,
|
|
311
|
+
settingsTypingChapterKey,
|
|
312
|
+
adjustInstanceContents,
|
|
313
|
+
setAdjustInstanceContents,
|
|
314
|
+
existsInstanceVersionNo,
|
|
315
|
+
setExistsInstanceVersionNo,
|
|
316
|
+
applyAdjustInstanceSections,
|
|
317
|
+
setCoherenceCheckResultReady,
|
|
318
|
+
clearCoherenceCheckResultReady,
|
|
319
|
+
coherenceCheckContentText,
|
|
320
|
+
coherenceResultReady,
|
|
321
|
+
markAdjustInstanceSettingSynced,
|
|
322
|
+
isAdjustInstanceSettingSynced,
|
|
323
|
+
suppressAdjustInstanceSettingWatch,
|
|
324
|
+
beginReportGenerateStream,
|
|
325
|
+
appendReportGenerateStream,
|
|
326
|
+
finishReportGenerateStream,
|
|
327
|
+
clearReportGenerateStream,
|
|
328
|
+
sourceFiles,
|
|
329
|
+
upsertChapterChartStreamRef,
|
|
330
|
+
clearChapterChartStreamRefs,
|
|
331
|
+
} = useReportWorkflow()
|
|
332
|
+
|
|
333
|
+
const genAiBuildReportStore = useGenAiBuildReportStore()
|
|
334
|
+
|
|
335
|
+
/** 本地測試:優先 store.instanceId */
|
|
336
|
+
function resolveInstanceId() {
|
|
337
|
+
return String(genAiBuildReportStore.instanceId ?? '').trim()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// report editor api
|
|
341
|
+
/** 報告歷史接口緩存(InstHistEnquiry.instanceVersionVOList) */
|
|
342
|
+
const genAiInstanceVersionVOList = ref<GenAiInstanceVersionVO[]>([])
|
|
343
|
+
/** 避免佈局回填 / 初始化時誤觸 AdjustInstanceSetting */
|
|
344
|
+
let suppressAdjustInstanceSetting = false
|
|
345
|
+
/** 用戶手動點報告歷史時,暫不因 existsInstanceVersionNo 回寫改選中 */
|
|
346
|
+
let suppressHistorySelectByExistsVersion = false
|
|
347
|
+
let adjustInstanceSettingSeq = 0
|
|
348
|
+
let simTradTransferSeq = 0
|
|
349
|
+
/** 中欄正文指紋:相同內容不重播打字機 */
|
|
350
|
+
let lastAdjustContentsFingerprint = ''
|
|
351
|
+
|
|
352
|
+
function fingerprintAdjustContents(
|
|
353
|
+
list: Array<{ sectionTitle?: string; sectionContent?: string }>
|
|
354
|
+
) {
|
|
355
|
+
return JSON.stringify(
|
|
356
|
+
list.map((item) => ({
|
|
357
|
+
t: String(item.sectionTitle ?? ''),
|
|
358
|
+
c: String(item.sectionContent ?? ''),
|
|
359
|
+
}))
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 按章節標題還原用戶拖拽後的順序。
|
|
365
|
+
* 背景:AdjustInstanceSetting 回寫調用 applyAdjustInstanceSections 時,
|
|
366
|
+
* 會把 settingsChapterOrder 重置為 chapter-0..n(接口列表下標),
|
|
367
|
+
* 導致「取消全選 → 拖拽 → 再勾全部章節」後打字機按接口序打印而非拖拽序。
|
|
368
|
+
*/
|
|
369
|
+
function restoreChapterOrderByLabels(orderLabels: string[]) {
|
|
370
|
+
if (!orderLabels.length || !settingsChapterPresets.value.length) return
|
|
371
|
+
// 標題 → 當前 key(回寫後 key 可能重編,只能用標題對齊)
|
|
372
|
+
const keyByLabel = new Map(
|
|
373
|
+
settingsChapterPresets.value.map((preset) => [preset.label, preset.key] as const)
|
|
374
|
+
)
|
|
375
|
+
const restored = orderLabels
|
|
376
|
+
.map((label) => keyByLabel.get(label))
|
|
377
|
+
.filter((key): key is string => typeof key === 'string')
|
|
378
|
+
if (!restored.length) return
|
|
379
|
+
const used = new Set(restored)
|
|
380
|
+
// 拖拽序裡沒有的章節(若有)接在後面,避免丟章
|
|
381
|
+
const rest = settingsChapterPresets.value
|
|
382
|
+
.map((preset) => preset.key)
|
|
383
|
+
.filter((key) => !used.has(key))
|
|
384
|
+
settingsChapterOrder.value = [...restored, ...rest]
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function splitCommaIds(raw: string | undefined | null): string[] {
|
|
388
|
+
if (!raw) return []
|
|
389
|
+
return String(raw)
|
|
390
|
+
.split(',')
|
|
391
|
+
.map((item) => item.trim())
|
|
392
|
+
.filter(Boolean)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** 統一版本號:V1 / v1 / 1 → V1,便於 existsInstanceVersionNo 對照 */
|
|
396
|
+
function normalizeInstanceVersionNo(raw: unknown): string {
|
|
397
|
+
if (raw == null || raw === '') return ''
|
|
398
|
+
const text = String(raw).trim().toUpperCase()
|
|
399
|
+
if (!text) return ''
|
|
400
|
+
return text.startsWith('V') ? text : `V${text}`
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function mapInstanceVersionToHistoryItem(
|
|
404
|
+
item: GenAiInstanceVersionVO,
|
|
405
|
+
index: number
|
|
406
|
+
): ReportHistoryItem {
|
|
407
|
+
const versionNo = item.instanceVersionNo
|
|
408
|
+
const titleFromNo =
|
|
409
|
+
versionNo != null && String(versionNo).trim() ? `報告版本 V${String(versionNo).trim()}` : ''
|
|
410
|
+
const title = item.objectName?.trim() || titleFromNo || `報告歷史 ${index + 1}`
|
|
411
|
+
const key =
|
|
412
|
+
item.instanceVersionId?.trim() ||
|
|
413
|
+
`${item.instanceId || 'hist'}-${index}-${item.sectionVersionIdList || ''}`
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
key,
|
|
417
|
+
title,
|
|
418
|
+
author: item.updateUserName || item.createdUserName || '',
|
|
419
|
+
finalizedAt: item.updateDatetime || item.createdDatetime || '',
|
|
420
|
+
recordId: key,
|
|
421
|
+
kind: 'history',
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** 當前 versionIdList(上→下)是否與某條報告歷史 sectionVersionIdList 一致 */
|
|
426
|
+
function findHistoryMatchingVersionIds(
|
|
427
|
+
versionIdList: string[]
|
|
428
|
+
): GenAiInstanceVersionVO | undefined {
|
|
429
|
+
if (!versionIdList.length) return undefined
|
|
430
|
+
const target = versionIdList.join(',')
|
|
431
|
+
return genAiInstanceVersionVOList.value.find((item) => {
|
|
432
|
+
const ids = splitCommaIds(item.sectionVersionIdList)
|
|
433
|
+
return ids.length === versionIdList.length && ids.join(',') === target
|
|
434
|
+
})
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** 已生成報告展示標題(接口歷史 / existsInstanceVersionNo) */
|
|
438
|
+
function resolveExistingReportDisplayTitle(vo?: GenAiInstanceVersionVO | null): string {
|
|
439
|
+
if (vo) {
|
|
440
|
+
const versionNo = vo.instanceVersionNo
|
|
441
|
+
const titleFromNo =
|
|
442
|
+
versionNo != null && String(versionNo).trim() ? `報告版本 ${String(versionNo).trim()}` : ''
|
|
443
|
+
return (
|
|
444
|
+
vo.objectName?.trim() ||
|
|
445
|
+
titleFromNo ||
|
|
446
|
+
`${draft.reportName || '報告'}${existsInstanceVersionNo.value ? ` ${existsInstanceVersionNo.value}` : ''}`
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
if (existsInstanceVersionNo.value) {
|
|
450
|
+
const name = draft.reportName?.trim() || '報告'
|
|
451
|
+
return `${name} ${existsInstanceVersionNo.value}`
|
|
452
|
+
}
|
|
453
|
+
return draft.reportName?.trim() || '報告'
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* 「已生成報告」校驗:僅對照 InstHistEnquiry 章節版本匹配
|
|
458
|
+
*/
|
|
459
|
+
async function findAlreadyGeneratedReportTitle(
|
|
460
|
+
_templateId: string,
|
|
461
|
+
_selection: ReportChapterSelectionItem[]
|
|
462
|
+
): Promise<string | null> {
|
|
463
|
+
const versionIdList = buildSelectedVersionIdList()
|
|
464
|
+
const instanceId = resolveInstanceId()
|
|
465
|
+
if (instanceId) {
|
|
466
|
+
await loadGenAiInstanceHistForReportPage()
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const matchedHist = findHistoryMatchingVersionIds(versionIdList)
|
|
470
|
+
if (matchedHist) {
|
|
471
|
+
return resolveExistingReportDisplayTitle(matchedHist)
|
|
472
|
+
}
|
|
473
|
+
return null
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* 報告歷史查詢 InstHistEnquiry
|
|
478
|
+
* 調試:參數僅 instanceId;返回 instanceVersionVOList
|
|
479
|
+
*/
|
|
480
|
+
const fetchGenAiInstanceHist = async (params: { instanceId: string }) => {
|
|
481
|
+
const res: any = await genAiReportEditorApi
|
|
482
|
+
.fetchGenAiInstanceHist({
|
|
483
|
+
instanceId: params.instanceId,
|
|
484
|
+
})
|
|
485
|
+
.catch((err) => {
|
|
486
|
+
console.error('接口异常', err)
|
|
487
|
+
return null
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
if (!isGenAiResponseOk(res)) {
|
|
492
|
+
console.warn('[report editor] InstHistEnquiry', res?.message || '接口异常')
|
|
493
|
+
return {
|
|
494
|
+
ok: false as const,
|
|
495
|
+
list: [] as GenAiInstanceVersionVO[],
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const list = (
|
|
499
|
+
Array.isArray(res?.data?.instanceVersionVOList) ? res.data.instanceVersionVOList : []
|
|
500
|
+
) as GenAiInstanceVersionVO[]
|
|
501
|
+
console.log('报告历史列表', list)
|
|
502
|
+
return { ok: true as const, list }
|
|
503
|
+
} catch (err) {
|
|
504
|
+
console.error('接口异常', err)
|
|
505
|
+
return {
|
|
506
|
+
ok: false as const,
|
|
507
|
+
list: [] as GenAiInstanceVersionVO[],
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/** 拉取報告歷史並覆蓋右側列表;失敗則空並收起 */
|
|
513
|
+
async function loadGenAiInstanceHistForReportPage() {
|
|
514
|
+
const instanceId = resolveInstanceId()
|
|
515
|
+
if (!instanceId) {
|
|
516
|
+
console.warn('[report editor] 無 instanceId,InstHistEnquiry 跳過')
|
|
517
|
+
genAiInstanceVersionVOList.value = []
|
|
518
|
+
reportHistoryItems.value = []
|
|
519
|
+
activeHistoryKey.value = ''
|
|
520
|
+
historyOpen.value = false
|
|
521
|
+
return false
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const result = await fetchGenAiInstanceHist({ instanceId })
|
|
525
|
+
if (!result.ok) {
|
|
526
|
+
genAiInstanceVersionVOList.value = []
|
|
527
|
+
reportHistoryItems.value = []
|
|
528
|
+
activeHistoryKey.value = ''
|
|
529
|
+
historyOpen.value = false
|
|
530
|
+
return false
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
genAiInstanceVersionVOList.value = result.list
|
|
534
|
+
reportHistoryItems.value = result.list.map((item, index) =>
|
|
535
|
+
mapInstanceVersionToHistoryItem(item, index)
|
|
536
|
+
)
|
|
537
|
+
historyOpen.value = reportHistoryItems.value.length > 0
|
|
538
|
+
// 進頁:existsInstanceVersionNo === instanceVersionNo → 選中第一個匹配 card
|
|
539
|
+
selectHistoryMatchingExistsVersionNo()
|
|
540
|
+
return true
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* AdjustInstance.existsInstanceVersionNo 對照報告歷史 instanceVersionNo
|
|
545
|
+
* 相等則該 card 為選中態(多個匹配取第一個);無匹配則選列表首條
|
|
546
|
+
*/
|
|
547
|
+
function selectHistoryMatchingExistsVersionNo() {
|
|
548
|
+
if (!reportHistoryItems.value.length) {
|
|
549
|
+
activeHistoryKey.value = ''
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
const target = normalizeInstanceVersionNo(existsInstanceVersionNo.value)
|
|
553
|
+
if (target) {
|
|
554
|
+
const matchIndex = genAiInstanceVersionVOList.value.findIndex(
|
|
555
|
+
(item) => normalizeInstanceVersionNo(item.instanceVersionNo) === target
|
|
556
|
+
)
|
|
557
|
+
if (matchIndex >= 0) {
|
|
558
|
+
activeHistoryKey.value = reportHistoryItems.value[matchIndex]?.key ?? ''
|
|
559
|
+
console.log('[report editor] 報告歷史選中(對照 existsInstanceVersionNo)', {
|
|
560
|
+
existsInstanceVersionNo: existsInstanceVersionNo.value,
|
|
561
|
+
key: activeHistoryKey.value,
|
|
562
|
+
})
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
activeHistoryKey.value = reportHistoryItems.value[0]?.key ?? ''
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* 已勾選章節的版本 UUID 列表(上→下)
|
|
571
|
+
* = instanceSections.versions[].value
|
|
572
|
+
*/
|
|
573
|
+
function buildSelectedVersionIdList(): string[] {
|
|
574
|
+
return chapterOrder.value
|
|
575
|
+
.map((key) => chapterPresets.value.find((preset) => preset.key === key))
|
|
576
|
+
.filter((preset): preset is ChapterPreset => !!preset)
|
|
577
|
+
.filter((preset) => !!selectedChapters.value[preset.key] && !isChapterUnavailable(preset))
|
|
578
|
+
.map((preset) => String(chapterVersions.value[preset.key] ?? '').trim())
|
|
579
|
+
.filter(Boolean)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* 調整報告設置 AdjustInstanceSetting
|
|
584
|
+
* 勾選章節 / 拖動 / 切換報告歷史後調用
|
|
585
|
+
* 回寫:資訊源 +(若有)右側章節設置 + 中欄正文
|
|
586
|
+
*/
|
|
587
|
+
const adjustGenAiInstanceSetting = async (params: {
|
|
588
|
+
instanceId: string
|
|
589
|
+
versionIdList: string[]
|
|
590
|
+
}) => {
|
|
591
|
+
const res: any = await genAiReportEditorApi
|
|
592
|
+
.adjustGenAiInstanceSetting({
|
|
593
|
+
instanceId: params.instanceId,
|
|
594
|
+
versionIdList: params.versionIdList,
|
|
595
|
+
})
|
|
596
|
+
.catch((err) => {
|
|
597
|
+
console.error('接口异常', err)
|
|
598
|
+
return null
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
if (!isGenAiResponseOk(res)) {
|
|
603
|
+
console.warn('[report editor] AdjustInstanceSetting', res?.message || '接口异常')
|
|
604
|
+
return { ok: false as const }
|
|
605
|
+
}
|
|
606
|
+
const data = res?.data ?? {}
|
|
607
|
+
const fileList = Array.isArray(data.instanceFileList) ? data.instanceFileList : []
|
|
608
|
+
const sectionList = Array.isArray(data.instanceSections) ? data.instanceSections : []
|
|
609
|
+
const contentList = Array.isArray(data.instanceContents) ? data.instanceContents : []
|
|
610
|
+
|
|
611
|
+
// —— 保留拖拽順序:回寫前先記下當前標題序,回寫後還原 ——
|
|
612
|
+
const preservedOrderLabels = settingsChapterOrder.value
|
|
613
|
+
.map((key) => settingsChapterPresets.value.find((preset) => preset.key === key)?.label)
|
|
614
|
+
.filter((label): label is string => !!label)
|
|
615
|
+
|
|
616
|
+
// 回寫會改 chapterOrder / versions → 抑制 watch,避免死循環閃爍
|
|
617
|
+
suppressAdjustInstanceSetting = true
|
|
618
|
+
try {
|
|
619
|
+
setExistsInstanceVersionNo(pickExistsInstanceVersionNo(data))
|
|
620
|
+
applyAdjustInstanceFiles(fileList)
|
|
621
|
+
|
|
622
|
+
// 右側「生成報告設置」章節勾選 / 版本 / 排序
|
|
623
|
+
let orderRestoredToDrag = false
|
|
624
|
+
if (sectionList.length) {
|
|
625
|
+
applyAdjustInstanceSections(sectionList)
|
|
626
|
+
// 接口剛寫入的默認序(chapter-0..n)
|
|
627
|
+
const apiOrderKeys = settingsChapterOrder.value.slice()
|
|
628
|
+
// 還原為用戶拖拽後的標題序
|
|
629
|
+
restoreChapterOrderByLabels(preservedOrderLabels)
|
|
630
|
+
// 是否相對接口默認序發生了拖拽重排(用於決定是否強制重播打字機)
|
|
631
|
+
orderRestoredToDrag =
|
|
632
|
+
JSON.stringify(settingsChapterOrder.value) !== JSON.stringify(apiOrderKeys)
|
|
633
|
+
}
|
|
634
|
+
// 中欄 editor 正文:
|
|
635
|
+
// 1) 內容指紋變了 → 按還原後的首章重播打字機
|
|
636
|
+
// 2) 內容沒變但拖拽序變了 → 仍按新順序重播,避免打印序錯亂
|
|
637
|
+
if (contentList.length) {
|
|
638
|
+
const fp = fingerprintAdjustContents(contentList)
|
|
639
|
+
// 0924:原样传入,保留 instanceContents[].refList
|
|
640
|
+
setAdjustInstanceContents(contentList)
|
|
641
|
+
// 0924
|
|
642
|
+
const firstKey = settingsChapterOrder.value[0]
|
|
643
|
+
if (fp !== lastAdjustContentsFingerprint) {
|
|
644
|
+
lastAdjustContentsFingerprint = fp
|
|
645
|
+
if (firstKey) requestSettingsTypewriterReplay(firstKey)
|
|
646
|
+
} else if (orderRestoredToDrag && firstKey) {
|
|
647
|
+
requestSettingsTypewriterReplay(firstKey)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
markAdjustInstanceSettingSynced(buildSelectedVersionIdList())
|
|
652
|
+
} finally {
|
|
653
|
+
void nextTick(() => {
|
|
654
|
+
suppressAdjustInstanceSetting = false
|
|
655
|
+
})
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
console.log('调整报告设置', {
|
|
659
|
+
versionIdList: params.versionIdList,
|
|
660
|
+
existsInstanceVersionNo: existsInstanceVersionNo.value,
|
|
661
|
+
sections: sectionList.length,
|
|
662
|
+
contents: contentList.length,
|
|
663
|
+
fileList,
|
|
664
|
+
})
|
|
665
|
+
return { ok: true as const }
|
|
666
|
+
} catch (err) {
|
|
667
|
+
console.error('接口异常', err)
|
|
668
|
+
return { ok: false as const }
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/** 勾選 / 拖動 / 切換章節版本後調用;全部不選不調用(原有佈局聯動 + 資訊源反顯) */
|
|
673
|
+
async function loadAdjustInstanceSettingOnChapterChange() {
|
|
674
|
+
if (props.readonly) return false
|
|
675
|
+
if (
|
|
676
|
+
suppressAdjustInstanceSetting ||
|
|
677
|
+
suppressAdjustInstanceSettingWatch.value ||
|
|
678
|
+
isApplyingHistoryLayout.value
|
|
679
|
+
) {
|
|
680
|
+
return false
|
|
681
|
+
}
|
|
682
|
+
// 尚無接口版本選項時不調(避免模板 mock V1..V6)
|
|
683
|
+
if (!Object.keys(settingsChapterVersionOptions.value).length) return false
|
|
684
|
+
|
|
685
|
+
const versionIdList = buildSelectedVersionIdList()
|
|
686
|
+
if (!versionIdList.length) {
|
|
687
|
+
console.log('[report editor] 未勾選章節,跳過 AdjustInstanceSetting')
|
|
688
|
+
return false
|
|
689
|
+
}
|
|
690
|
+
// 與上次回寫結果一致 → 跳過(打斷回寫觸發的 watch 循環)
|
|
691
|
+
if (isAdjustInstanceSettingSynced(versionIdList)) return false
|
|
692
|
+
|
|
693
|
+
const instanceId = resolveInstanceId()
|
|
694
|
+
if (!instanceId) {
|
|
695
|
+
console.warn('[report editor] 無 instanceId,AdjustInstanceSetting 跳過')
|
|
696
|
+
return false
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const seq = ++adjustInstanceSettingSeq
|
|
700
|
+
const result = await adjustGenAiInstanceSetting({ instanceId, versionIdList })
|
|
701
|
+
if (seq !== adjustInstanceSettingSeq) return false
|
|
702
|
+
return result.ok
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* 報告定稿 FinalInstance
|
|
707
|
+
* 生成報告設置頁點「生成報告」後調用;參數同 AdjustInstanceSetting
|
|
708
|
+
*/
|
|
709
|
+
const finalGenAiInstance = async (params: { instanceId: string; versionIdList: string[] }) => {
|
|
710
|
+
const res: any = await genAiReportEditorApi
|
|
711
|
+
.finalGenAiInstance({
|
|
712
|
+
instanceId: params.instanceId,
|
|
713
|
+
versionIdList: params.versionIdList,
|
|
714
|
+
})
|
|
715
|
+
.catch((err) => {
|
|
716
|
+
console.error('接口异常', err)
|
|
717
|
+
return null
|
|
718
|
+
})
|
|
719
|
+
|
|
720
|
+
try {
|
|
721
|
+
if (!isGenAiResponseOk(res)) {
|
|
722
|
+
console.warn('[report editor] FinalInstance', res?.message || '接口异常')
|
|
723
|
+
return { ok: false as const, message: String(res?.message || '報告定稿失敗') }
|
|
724
|
+
}
|
|
725
|
+
console.log('报告定稿 FinalInstance', {
|
|
726
|
+
versionIdList: params.versionIdList,
|
|
727
|
+
data: res?.data,
|
|
728
|
+
})
|
|
729
|
+
return { ok: true as const, message: String(res?.message || '') }
|
|
730
|
+
} catch (err) {
|
|
731
|
+
console.error('接口异常', err)
|
|
732
|
+
return { ok: false as const, message: '報告定稿失敗' }
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** 點「生成報告」→ FinalInstance;失敗不走本地定稿 */
|
|
737
|
+
async function loadFinalInstanceOnGenerate() {
|
|
738
|
+
const versionIdList = buildSelectedVersionIdList()
|
|
739
|
+
if (!versionIdList.length) {
|
|
740
|
+
ElMessage.warning('請先勾選章節')
|
|
741
|
+
return false
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const instanceId = resolveInstanceId()
|
|
745
|
+
if (!instanceId) {
|
|
746
|
+
console.warn('[report editor] 無 instanceId,FinalInstance 跳過')
|
|
747
|
+
ElMessage.warning('缺少報告實例,無法生成')
|
|
748
|
+
return false
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const result = await finalGenAiInstance({ instanceId, versionIdList })
|
|
752
|
+
if (!result.ok) {
|
|
753
|
+
ElMessage.warning(result.message || '報告定稿失敗')
|
|
754
|
+
return false
|
|
755
|
+
}
|
|
756
|
+
// 定稿成功後刷新報告歷史
|
|
757
|
+
await loadGenAiInstanceHistForReportPage()
|
|
758
|
+
return true
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/** zh-Hans → CHS(簡體),zh-Hant → CHT(繁體) */
|
|
762
|
+
function mapChapterContentLanguageToTargetLang(
|
|
763
|
+
lang: ChapterContentLanguage
|
|
764
|
+
): GenAiSimTradTargetLang {
|
|
765
|
+
return lang === 'zh-Hans' ? 'CHS' : 'CHT'
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* 繁簡轉換 SimTradTransfer
|
|
770
|
+
* 生成報告設置「語言」下拉切換後調用;回寫中欄 instanceContents
|
|
771
|
+
*/
|
|
772
|
+
const simTradTransferGenAiInstance = async (params: {
|
|
773
|
+
targetLang: GenAiSimTradTargetLang
|
|
774
|
+
instanceContents: Array<{ sectionTitle: string; sectionContent: string }>
|
|
775
|
+
}) => {
|
|
776
|
+
const res: any = await genAiReportEditorApi
|
|
777
|
+
.simTradTransferGenAiInstance({
|
|
778
|
+
targetLang: params.targetLang,
|
|
779
|
+
instanceContents: params.instanceContents,
|
|
780
|
+
})
|
|
781
|
+
.catch((err) => {
|
|
782
|
+
console.error('接口异常', err)
|
|
783
|
+
return null
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
try {
|
|
787
|
+
if (!isGenAiResponseOk(res)) {
|
|
788
|
+
console.warn('[report editor] SimTradTransfer', res?.message || '接口异常')
|
|
789
|
+
return { ok: false as const }
|
|
790
|
+
}
|
|
791
|
+
const raw = res?.data
|
|
792
|
+
const list = Array.isArray(raw?.instanceContents)
|
|
793
|
+
? raw.instanceContents
|
|
794
|
+
: Array.isArray(raw)
|
|
795
|
+
? raw
|
|
796
|
+
: []
|
|
797
|
+
// 0924:原样传入,保留 instanceContents[].refList
|
|
798
|
+
setAdjustInstanceContents(list)
|
|
799
|
+
// 0924
|
|
800
|
+
lastAdjustContentsFingerprint = fingerprintAdjustContents(adjustInstanceContents.value)
|
|
801
|
+
console.log('繁简转换 SimTradTransfer', {
|
|
802
|
+
targetLang: params.targetLang,
|
|
803
|
+
contents: adjustInstanceContents.value,
|
|
804
|
+
})
|
|
805
|
+
return { ok: true as const }
|
|
806
|
+
} catch (err) {
|
|
807
|
+
console.error('接口异常', err)
|
|
808
|
+
return { ok: false as const }
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/** 語言下拉:先走原有 draft 語言切換,再調繁簡轉換;無正文時僅保留本地語言(標題 i18n) */
|
|
813
|
+
async function loadSimTradTransferOnLanguageChange(lang: ChapterContentLanguage) {
|
|
814
|
+
if (props.readonly) return false
|
|
815
|
+
const contents = adjustInstanceContents.value
|
|
816
|
+
if (!contents.length) {
|
|
817
|
+
console.log('[report editor] 無 instanceContents,跳過 SimTradTransfer(保留本地語言切換)')
|
|
818
|
+
return false
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const targetLang = mapChapterContentLanguageToTargetLang(lang)
|
|
822
|
+
const seq = ++simTradTransferSeq
|
|
823
|
+
const result = await simTradTransferGenAiInstance({
|
|
824
|
+
targetLang,
|
|
825
|
+
instanceContents: contents.map((item) => ({
|
|
826
|
+
sectionTitle: item.sectionTitle,
|
|
827
|
+
sectionContent: item.sectionContent,
|
|
828
|
+
})),
|
|
829
|
+
})
|
|
830
|
+
if (seq !== simTradTransferSeq) return false
|
|
831
|
+
if (!result.ok) {
|
|
832
|
+
ElMessage.warning('繁簡轉換失敗')
|
|
833
|
+
return false
|
|
834
|
+
}
|
|
835
|
+
// 原有中欄重播:內容已換
|
|
836
|
+
const firstKey = settingsChapterOrder.value[0]
|
|
837
|
+
if (firstKey) requestSettingsTypewriterReplay(firstKey)
|
|
838
|
+
return true
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// —— report editor api end ——
|
|
842
|
+
|
|
843
|
+
function handleSelectHistory(item: ReportHistoryItem) {
|
|
844
|
+
activeHistoryKey.value = item.key
|
|
845
|
+
const voIndex = reportHistoryItems.value.findIndex((entry) => entry.key === item.key)
|
|
846
|
+
const vo = voIndex >= 0 ? genAiInstanceVersionVOList.value[voIndex] : undefined
|
|
847
|
+
if (vo) {
|
|
848
|
+
// 切換報告歷史 → AdjustInstanceSetting 刷新:生成報告設置 + 中欄 editor + 資訊源
|
|
849
|
+
const versionIdList = splitCommaIds(vo.sectionVersionIdList)
|
|
850
|
+
const instanceId = String(vo.instanceId ?? '').trim() || resolveInstanceId()
|
|
851
|
+
console.log('選中報告歷史', {
|
|
852
|
+
instanceId,
|
|
853
|
+
sectionIds: splitCommaIds(vo.sectionIdList),
|
|
854
|
+
sectionVersionIds: versionIdList,
|
|
855
|
+
instanceVersionNo: vo.instanceVersionNo,
|
|
856
|
+
vo,
|
|
857
|
+
})
|
|
858
|
+
if (instanceId && versionIdList.length) {
|
|
859
|
+
void loadAdjustInstanceSettingFromHistoryCard({ instanceId, versionIdList })
|
|
860
|
+
} else {
|
|
861
|
+
ElMessage.warning('該歷史缺少版本信息,無法刷新報告設置')
|
|
862
|
+
}
|
|
863
|
+
resetHistoryPreviewState()
|
|
864
|
+
return
|
|
865
|
+
}
|
|
866
|
+
if (!item.recordId) {
|
|
867
|
+
resetHistoryPreviewState()
|
|
868
|
+
return
|
|
869
|
+
}
|
|
870
|
+
// 本地定稿歷史:原有預覽邏輯
|
|
871
|
+
selectHistoryReport(item.recordId)
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* 點擊報告歷史 card → AdjustInstanceSetting 刷新頁面(資訊源等)
|
|
876
|
+
* 參數取自該 card 的 instanceId + sectionVersionIdList
|
|
877
|
+
*/
|
|
878
|
+
async function loadAdjustInstanceSettingFromHistoryCard(params: {
|
|
879
|
+
instanceId: string
|
|
880
|
+
versionIdList: string[]
|
|
881
|
+
}) {
|
|
882
|
+
if (props.readonly) return false
|
|
883
|
+
const selectedKey = activeHistoryKey.value
|
|
884
|
+
suppressAdjustInstanceSetting = true
|
|
885
|
+
suppressHistorySelectByExistsVersion = true
|
|
886
|
+
const seq = ++adjustInstanceSettingSeq
|
|
887
|
+
const result = await adjustGenAiInstanceSetting(params)
|
|
888
|
+
if (seq !== adjustInstanceSettingSeq) return false
|
|
889
|
+
// 保持用戶點選的 card
|
|
890
|
+
if (selectedKey) activeHistoryKey.value = selectedKey
|
|
891
|
+
void nextTick(() => {
|
|
892
|
+
suppressAdjustInstanceSetting = false
|
|
893
|
+
suppressHistorySelectByExistsVersion = false
|
|
894
|
+
})
|
|
895
|
+
return result.ok
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/** 中欄打字進度經 workflow 通知;右側自行滾動列表行,不依賴中欄 DOM */
|
|
899
|
+
watch(settingsTypingChapterKey, (key) => {
|
|
900
|
+
if (!key) return
|
|
901
|
+
void nextTick(() => {
|
|
902
|
+
const row = document.querySelector(
|
|
903
|
+
`.chatbot-report-chapter-row-wrap[data-chapter-key="${CSS.escape(key)}"]`
|
|
904
|
+
) as HTMLElement | null
|
|
905
|
+
row?.scrollIntoView({ block: 'nearest', behavior: 'smooth', inline: 'nearest' })
|
|
906
|
+
})
|
|
907
|
+
})
|
|
908
|
+
|
|
909
|
+
watch(historyScrollTargetKey, async (key) => {
|
|
910
|
+
if (!key) return
|
|
911
|
+
historyOpen.value = true
|
|
912
|
+
await nextTick()
|
|
913
|
+
clearHistoryScrollTarget()
|
|
914
|
+
})
|
|
915
|
+
|
|
916
|
+
/** 無報告歷史時收起面板,避免空內容佔位 */
|
|
917
|
+
watch(
|
|
918
|
+
reportHistoryItems,
|
|
919
|
+
(items) => {
|
|
920
|
+
if (!items.length) historyOpen.value = false
|
|
921
|
+
},
|
|
922
|
+
{ deep: true }
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
const chapterContentLanguageOptions = CHAPTER_CONTENT_LANGUAGE_OPTIONS
|
|
926
|
+
|
|
927
|
+
const showChapterLanguageSelect = computed(() => shouldShowChapterLanguageSelect(draft.language))
|
|
928
|
+
|
|
929
|
+
const chapterContentLanguage = computed({
|
|
930
|
+
get: () => draft.chapterContentLanguage,
|
|
931
|
+
set: (value: ChapterContentLanguage) => {
|
|
932
|
+
if (draft.chapterContentLanguage === value) return
|
|
933
|
+
patchDraft({ chapterContentLanguage: value })
|
|
934
|
+
// 語言下拉:CHS/CHT 繁簡轉換
|
|
935
|
+
void loadSimTradTransferOnLanguageChange(value)
|
|
936
|
+
},
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
const unavailableVersionLabel = computed(() =>
|
|
940
|
+
translateUnavailableVersionLabel(draft.language, 'zh-Hant')
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
const chapterPresets = settingsChapterPresets
|
|
944
|
+
const selectedChapters = settingsSelectedChapters
|
|
945
|
+
/** 與 AdjustInstance 共用:value=UUID(或回落 V1..V6) */
|
|
946
|
+
const chapterVersions = settingsChapterVersions
|
|
947
|
+
const chapterGenerators = settingsChapterGenerators
|
|
948
|
+
const chapterOrder = settingsChapterOrder
|
|
949
|
+
const dragChapterKey = ref<string | null>(null)
|
|
950
|
+
const dragOverChapterKey = ref<string | null>(null)
|
|
951
|
+
const isApplyingHistoryLayout = ref(false)
|
|
952
|
+
let chapterRowDragMoved = false
|
|
953
|
+
|
|
954
|
+
/** 章節版本下拉:僅接口 versions;無則空(不回落 V1..V6 mock) */
|
|
955
|
+
function getChapterVersionOptions(key: string) {
|
|
956
|
+
if (props.readonly) {
|
|
957
|
+
const text = String(chapterVersions.value[key] ?? '').trim()
|
|
958
|
+
const uploadUsrNameDisplay = String(chapterGenerators.value[key] ?? '').trim()
|
|
959
|
+
return text
|
|
960
|
+
? [{ text, value: text, selected: true, uploadUsrNameDisplay }]
|
|
961
|
+
: []
|
|
962
|
+
}
|
|
963
|
+
return settingsChapterVersionOptions.value[key] ?? []
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/** 詳情彈層展示 text,而非 UUID */
|
|
967
|
+
function getChapterVersionDisplayText(key: string) {
|
|
968
|
+
const value = chapterVersions.value[key]
|
|
969
|
+
const opt = getChapterVersionOptions(key).find((item) => item.value === value)
|
|
970
|
+
return opt?.text || value || '—'
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* 生成人展示:≤3 字完整顯示;>3 字只顯示前 4 字 + ...(對齊圖中 LOCA...)
|
|
975
|
+
* 完整資訊在詳情彈層
|
|
976
|
+
*/
|
|
977
|
+
const GENERATOR_NAME_FULL_MAX = 3
|
|
978
|
+
const GENERATOR_NAME_TRUNCATED_CHARS = 4
|
|
979
|
+
|
|
980
|
+
function getGeneratorNameChars(key: string) {
|
|
981
|
+
const name = getChapterVersionOptions(key).find(
|
|
982
|
+
(item) => item.value == chapterVersions.value[key]
|
|
983
|
+
)?.uploadUsrNameDisplay
|
|
984
|
+
return String(name ?? '').trim()
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function isGeneratorNameTruncated(key: string) {
|
|
988
|
+
return Array.from(getGeneratorNameChars(key)).length > GENERATOR_NAME_FULL_MAX
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function formatGeneratorDisplayName(key: string) {
|
|
992
|
+
const chars = Array.from(getGeneratorNameChars(key))
|
|
993
|
+
if (!chars.length) return ''
|
|
994
|
+
if (chars.length <= GENERATOR_NAME_FULL_MAX) return chars.join('')
|
|
995
|
+
return chars.slice(0, GENERATOR_NAME_TRUNCATED_CHARS).join('')
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const orderedPresets = computed(() =>
|
|
999
|
+
chapterOrder.value
|
|
1000
|
+
.map((key) => chapterPresets.value.find((preset) => preset.key === key))
|
|
1001
|
+
.filter((preset): preset is ChapterPreset => !!preset)
|
|
1002
|
+
)
|
|
1003
|
+
|
|
1004
|
+
const allChaptersSelected = computed(() => {
|
|
1005
|
+
const selectable = chapterPresets.value.filter((preset) => !isChapterUnavailable(preset))
|
|
1006
|
+
return selectable.length > 0 && selectable.every((preset) => selectedChapters.value[preset.key])
|
|
1007
|
+
})
|
|
1008
|
+
|
|
1009
|
+
function isChapterUnavailable(preset: ChapterPreset) {
|
|
1010
|
+
return UNAVAILABLE_CHAPTER_LABELS.has(preset.label)
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function formatChapterDisplayTitle(_index: number, label: string) {
|
|
1014
|
+
return translateChapterLabel(label, draft.language, 'zh-Hant')
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/** 章節標題是否溢出;完整詳情掛在當前章節行下方 */
|
|
1018
|
+
const labelOverflowMap = reactive<Record<string, boolean>>({})
|
|
1019
|
+
const labelElMap = new Map<string, HTMLElement>()
|
|
1020
|
+
const openChapterDetailKey = ref<string | null>(null)
|
|
1021
|
+
const openChapterDetail = ref<{ title: string; version: string; generator: string } | null>(null)
|
|
1022
|
+
const settingsPanelRef = ref<HTMLElement | null>(null)
|
|
1023
|
+
let labelResizeObserver: ResizeObserver | null = null
|
|
1024
|
+
|
|
1025
|
+
function measureChapterLabelOverflow(key: string) {
|
|
1026
|
+
const el = labelElMap.get(key)
|
|
1027
|
+
if (!el) {
|
|
1028
|
+
labelOverflowMap[key] = false
|
|
1029
|
+
return
|
|
1030
|
+
}
|
|
1031
|
+
const main = el.querySelector('.chatbot-report-chapter-row__label-main') as HTMLElement | null
|
|
1032
|
+
if (!main) {
|
|
1033
|
+
labelOverflowMap[key] = false
|
|
1034
|
+
return
|
|
1035
|
+
}
|
|
1036
|
+
labelOverflowMap[key] = main.scrollWidth > main.clientWidth + 1
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function bindChapterLabelEl(key: string, el: unknown) {
|
|
1040
|
+
const prev = labelElMap.get(key)
|
|
1041
|
+
if (prev && labelResizeObserver) labelResizeObserver.unobserve(prev)
|
|
1042
|
+
|
|
1043
|
+
const node = el as HTMLElement | null
|
|
1044
|
+
if (!node) {
|
|
1045
|
+
labelElMap.delete(key)
|
|
1046
|
+
labelOverflowMap[key] = false
|
|
1047
|
+
return
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
labelElMap.set(key, node)
|
|
1051
|
+
labelResizeObserver?.observe(node)
|
|
1052
|
+
const shell = node.closest('.chatbot-report-chapter-row__label')
|
|
1053
|
+
if (shell) labelResizeObserver?.observe(shell)
|
|
1054
|
+
void nextTick(() => measureChapterLabelOverflow(key))
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function remeasureAllChapterLabels() {
|
|
1058
|
+
for (const key of labelElMap.keys()) measureChapterLabelOverflow(key)
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function buildChapterDetail(key: string) {
|
|
1062
|
+
const presetIndex = orderedPresets.value.findIndex((item) => item.key === key)
|
|
1063
|
+
const preset = orderedPresets.value[presetIndex]
|
|
1064
|
+
if (!preset) return null
|
|
1065
|
+
return {
|
|
1066
|
+
title: formatChapterDisplayTitle(presetIndex, preset.label),
|
|
1067
|
+
version: isChapterUnavailable(preset)
|
|
1068
|
+
? unavailableVersionLabel.value
|
|
1069
|
+
: getChapterVersionDisplayText(preset.key),
|
|
1070
|
+
generator: getGeneratorNameChars(key) || '',
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function closeChapterDetailPopover() {
|
|
1075
|
+
openChapterDetailKey.value = null
|
|
1076
|
+
openChapterDetail.value = null
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/** 關閉面板內已展開的 el-select 下拉(與詳情彈層互斥) */
|
|
1080
|
+
function closeOpenSelectDropdowns() {
|
|
1081
|
+
const panel = settingsPanelRef.value
|
|
1082
|
+
if (!panel) return
|
|
1083
|
+
panel
|
|
1084
|
+
.querySelectorAll<HTMLElement>('.el-select__wrapper[aria-expanded="true"]')
|
|
1085
|
+
.forEach((node) => {
|
|
1086
|
+
node.blur()
|
|
1087
|
+
node.click()
|
|
1088
|
+
})
|
|
1089
|
+
// teleported 下拉:Escape 再兜底關閉
|
|
1090
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/** 點選版本 / 語言下拉:立刻收起詳情 */
|
|
1094
|
+
function onVersionSelectMouseDown() {
|
|
1095
|
+
closeChapterDetailPopover()
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/** 打開版本 / 語言下拉時關閉章節詳情 */
|
|
1099
|
+
function onChapterVersionVisibleChange(open: boolean) {
|
|
1100
|
+
if (open) closeChapterDetailPopover()
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function hasChapterRowEllipsis(preset: ChapterPreset) {
|
|
1104
|
+
const titleOverflow = !!labelOverflowMap[preset.key]
|
|
1105
|
+
const generatorOverflow =
|
|
1106
|
+
!isChapterUnavailable(preset) && isGeneratorNameTruncated(preset.key)
|
|
1107
|
+
return titleOverflow || generatorOverflow
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function toggleChapterDetailPopover(key: string) {
|
|
1111
|
+
const preset = orderedPresets.value.find((item) => item.key === key)
|
|
1112
|
+
if (!preset) {
|
|
1113
|
+
closeChapterDetailPopover()
|
|
1114
|
+
return
|
|
1115
|
+
}
|
|
1116
|
+
// 可編輯:僅標題/生成人截斷時可開詳情;只讀檢查頁:任意行可查看完整資訊
|
|
1117
|
+
if (!props.readonly && !hasChapterRowEllipsis(preset)) {
|
|
1118
|
+
closeChapterDetailPopover()
|
|
1119
|
+
return
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (openChapterDetailKey.value === key) {
|
|
1123
|
+
closeChapterDetailPopover()
|
|
1124
|
+
return
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const detail = buildChapterDetail(key)
|
|
1128
|
+
if (!detail) return
|
|
1129
|
+
// 打開詳情前先收起下拉,避免與版本選擇同時彈開
|
|
1130
|
+
closeOpenSelectDropdowns()
|
|
1131
|
+
openChapterDetailKey.value = key
|
|
1132
|
+
openChapterDetail.value = detail
|
|
1133
|
+
// 確保詳情不被滾動區裁切:把當前行滾入可視區
|
|
1134
|
+
void nextTick(() => {
|
|
1135
|
+
const wrap = settingsPanelRef.value?.querySelector(
|
|
1136
|
+
`.chatbot-report-chapter-row-wrap[data-chapter-key="${key}"]`
|
|
1137
|
+
) as HTMLElement | null
|
|
1138
|
+
wrap?.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
|
1139
|
+
})
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function onChapterRowClick(key: string, event: MouseEvent) {
|
|
1143
|
+
if (chapterRowDragMoved) return
|
|
1144
|
+
const target = event.target as HTMLElement | null
|
|
1145
|
+
// 可編輯:點勾選/版本下拉不開詳情
|
|
1146
|
+
// 只讀:下拉已禁用,點版本/生成人/標題區均可打開 popover 查看完整資訊
|
|
1147
|
+
if (props.readonly) {
|
|
1148
|
+
if (target?.closest('.chatbot-report-chapter-row__check')) return
|
|
1149
|
+
toggleChapterDetailPopover(key)
|
|
1150
|
+
return
|
|
1151
|
+
}
|
|
1152
|
+
if (
|
|
1153
|
+
target?.closest(
|
|
1154
|
+
'.chatbot-report-chapter-row__check, .chatbot-report-chapter-row__version, .el-select, .el-select__wrapper'
|
|
1155
|
+
)
|
|
1156
|
+
) {
|
|
1157
|
+
return
|
|
1158
|
+
}
|
|
1159
|
+
toggleChapterDetailPopover(key)
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function onDocPointerDown(event: MouseEvent) {
|
|
1163
|
+
if (!openChapterDetailKey.value) return
|
|
1164
|
+
const target = event.target as HTMLElement | null
|
|
1165
|
+
// 點擊版本 / 語言下拉(含 teleport 的 dropdown)→ 收起詳情
|
|
1166
|
+
if (
|
|
1167
|
+
target?.closest(
|
|
1168
|
+
'.chatbot-report-chapter-row__version, .chatbot-report-settings__language, .el-select-dropdown, .el-popper'
|
|
1169
|
+
)
|
|
1170
|
+
) {
|
|
1171
|
+
closeChapterDetailPopover()
|
|
1172
|
+
return
|
|
1173
|
+
}
|
|
1174
|
+
// 點擊詳情本身不關閉
|
|
1175
|
+
if (target?.closest('.chatbot-report-chapter-title-popover-local')) return
|
|
1176
|
+
// 點擊當前打開詳情的章節行:由 toggle 處理
|
|
1177
|
+
if (target?.closest('.chatbot-report-chapter-row.is-detail-open')) return
|
|
1178
|
+
// 點擊其他章節行:由該行 click 切換;其餘空白區域關閉
|
|
1179
|
+
if (target?.closest('.chatbot-report-chapter-row')) return
|
|
1180
|
+
closeChapterDetailPopover()
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
onMounted(() => {
|
|
1184
|
+
labelResizeObserver = new ResizeObserver(() => remeasureAllChapterLabels())
|
|
1185
|
+
window.addEventListener('resize', remeasureAllChapterLabels)
|
|
1186
|
+
document.addEventListener('mousedown', onDocPointerDown, true)
|
|
1187
|
+
void nextTick(() => {
|
|
1188
|
+
requestAnimationFrame(() => remeasureAllChapterLabels())
|
|
1189
|
+
})
|
|
1190
|
+
// 報告頁掛載:拉報告歷史(失敗置空,不阻斷)
|
|
1191
|
+
if (!props.readonly) {
|
|
1192
|
+
void loadGenAiInstanceHistForReportPage()
|
|
1193
|
+
}
|
|
1194
|
+
})
|
|
1195
|
+
|
|
1196
|
+
onBeforeUnmount(() => {
|
|
1197
|
+
chartAsStreamAbort?.abort()
|
|
1198
|
+
chartAsStreamAbort = null
|
|
1199
|
+
window.removeEventListener('resize', remeasureAllChapterLabels)
|
|
1200
|
+
document.removeEventListener('mousedown', onDocPointerDown, true)
|
|
1201
|
+
labelResizeObserver?.disconnect()
|
|
1202
|
+
labelResizeObserver = null
|
|
1203
|
+
labelElMap.clear()
|
|
1204
|
+
closeChapterDetailPopover()
|
|
1205
|
+
})
|
|
1206
|
+
|
|
1207
|
+
/** AdjustInstance 回寫 existsInstanceVersionNo 後,對照報告歷史選中第一個匹配 card */
|
|
1208
|
+
watch(existsInstanceVersionNo, (versionNo) => {
|
|
1209
|
+
if (props.readonly) return
|
|
1210
|
+
// 有值 = 已通過連貫性檢查 → 顯示結果 / 導出 Word;清空則回落首次生成態
|
|
1211
|
+
syncCoherenceReadyByExistsVersionNo(versionNo)
|
|
1212
|
+
if (suppressHistorySelectByExistsVersion) return
|
|
1213
|
+
if (!reportHistoryItems.value.length) return
|
|
1214
|
+
selectHistoryMatchingExistsVersionNo()
|
|
1215
|
+
})
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* existsInstanceVersionNo 有值:已通過連貫性檢查(非首次生成)
|
|
1219
|
+
* 無值:需點「報告連貫性檢查」走 CoherenceCheck
|
|
1220
|
+
*/
|
|
1221
|
+
function syncCoherenceReadyByExistsVersionNo(versionNo: string | null | undefined) {
|
|
1222
|
+
const hasVersion = !!String(versionNo ?? '').trim()
|
|
1223
|
+
if (hasVersion) {
|
|
1224
|
+
setCoherenceCheckResultReady(true, coherenceCheckContentText.value)
|
|
1225
|
+
const templateId = selectedTemplate.value?.id
|
|
1226
|
+
const selection = buildCurrentChapterSelection()
|
|
1227
|
+
if (templateId && selection.length) {
|
|
1228
|
+
markCoherenceCheckCompleted(
|
|
1229
|
+
buildGenerationSignature(
|
|
1230
|
+
templateId,
|
|
1231
|
+
selection,
|
|
1232
|
+
draft.language,
|
|
1233
|
+
draft.chapterContentLanguage
|
|
1234
|
+
)
|
|
1235
|
+
)
|
|
1236
|
+
}
|
|
1237
|
+
return
|
|
1238
|
+
}
|
|
1239
|
+
// 無版本號時不強制清 Y(可能剛由 CoherenceCheck 返回 Y、尚未定稿)
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
watch(
|
|
1243
|
+
() => orderedPresets.value.map((item) => item.key).join('|'),
|
|
1244
|
+
() => {
|
|
1245
|
+
void nextTick(() => {
|
|
1246
|
+
requestAnimationFrame(() => remeasureAllChapterLabels())
|
|
1247
|
+
})
|
|
1248
|
+
}
|
|
1249
|
+
)
|
|
1250
|
+
|
|
1251
|
+
watch(settingsOpen, (open) => {
|
|
1252
|
+
if (!open) {
|
|
1253
|
+
closeChapterDetailPopover()
|
|
1254
|
+
return
|
|
1255
|
+
}
|
|
1256
|
+
void nextTick(() => {
|
|
1257
|
+
requestAnimationFrame(() => remeasureAllChapterLabels())
|
|
1258
|
+
})
|
|
1259
|
+
})
|
|
1260
|
+
|
|
1261
|
+
/** AdjustInstance 寫入章節後:生成人由 settingsChapterGenerators 同步 */
|
|
1262
|
+
watch(
|
|
1263
|
+
settingsChapterGenerators,
|
|
1264
|
+
() => {
|
|
1265
|
+
void nextTick(() => {
|
|
1266
|
+
requestAnimationFrame(() => remeasureAllChapterLabels())
|
|
1267
|
+
})
|
|
1268
|
+
},
|
|
1269
|
+
{ deep: true }
|
|
1270
|
+
)
|
|
1271
|
+
|
|
1272
|
+
watch(
|
|
1273
|
+
() => props.coherenceSession,
|
|
1274
|
+
(session) => {
|
|
1275
|
+
if (!props.readonly || !session) return
|
|
1276
|
+
applyCoherenceSessionLayout(session)
|
|
1277
|
+
},
|
|
1278
|
+
{ immediate: true, deep: true }
|
|
1279
|
+
)
|
|
1280
|
+
|
|
1281
|
+
watch(
|
|
1282
|
+
() => historyPreviewApplyToken.value,
|
|
1283
|
+
() => {
|
|
1284
|
+
applyHistoryPreviewLayout()
|
|
1285
|
+
}
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
watch(
|
|
1289
|
+
[chapterOrder, selectedChapters, chapterVersions],
|
|
1290
|
+
() => {
|
|
1291
|
+
checkHistoryLayoutDrift()
|
|
1292
|
+
// 章節組合變更後需重新連貫性檢查
|
|
1293
|
+
clearCoherenceCheckResultReady()
|
|
1294
|
+
const session = loadCoherenceCheckSession()
|
|
1295
|
+
if (session && String(session.beenFinalizedFlag ?? '').toUpperCase() === 'Y') {
|
|
1296
|
+
saveCoherenceCheckSession({
|
|
1297
|
+
...session,
|
|
1298
|
+
beenFinalizedFlag: undefined,
|
|
1299
|
+
coherenceCheckContentText: undefined,
|
|
1300
|
+
})
|
|
1301
|
+
}
|
|
1302
|
+
// 勾選 / 拖動 / 切換章節版本 → AdjustInstanceSetting(全部不選不調)
|
|
1303
|
+
void loadAdjustInstanceSettingOnChapterChange()
|
|
1304
|
+
},
|
|
1305
|
+
{ deep: true }
|
|
1306
|
+
)
|
|
1307
|
+
|
|
1308
|
+
function applyHistoryPreviewLayout() {
|
|
1309
|
+
if (props.readonly) return
|
|
1310
|
+
const recordId = historyPreviewRecordId.value
|
|
1311
|
+
if (!recordId) return
|
|
1312
|
+
|
|
1313
|
+
const record = findFinalizedReportById(recordId)
|
|
1314
|
+
const templateChapters = selectedTemplate.value?.chapters
|
|
1315
|
+
if (!record || !templateChapters?.length) return
|
|
1316
|
+
|
|
1317
|
+
isApplyingHistoryLayout.value = true
|
|
1318
|
+
suppressAdjustInstanceSetting = true
|
|
1319
|
+
const layout = buildChapterOrderForHistoryRecord(templateChapters, record.chapters, (label) =>
|
|
1320
|
+
UNAVAILABLE_CHAPTER_LABELS.has(label)
|
|
1321
|
+
)
|
|
1322
|
+
chapterPresets.value = layout.presets
|
|
1323
|
+
chapterOrder.value = layout.chapterOrder
|
|
1324
|
+
selectedChapters.value = layout.selectedChapters
|
|
1325
|
+
chapterVersions.value = layout.chapterVersions
|
|
1326
|
+
chapterGenerators.value = Object.fromEntries(layout.presets.map((preset) => [preset.key, '']))
|
|
1327
|
+
isApplyingHistoryLayout.value = false
|
|
1328
|
+
void nextTick(() => {
|
|
1329
|
+
suppressAdjustInstanceSetting = false
|
|
1330
|
+
})
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function applyCoherenceSessionLayout(session: CoherenceCheckSessionPayload) {
|
|
1334
|
+
const chapters = session.chapters ?? []
|
|
1335
|
+
const presets = chapters.map((chapter, index) => ({
|
|
1336
|
+
key: `coherence-chapter-${index}`,
|
|
1337
|
+
label: String(chapter.label ?? '')
|
|
1338
|
+
.trim()
|
|
1339
|
+
.replace(/^\d+\.\s*/, '') || `章節${index + 1}`,
|
|
1340
|
+
}))
|
|
1341
|
+
const order = presets.map((preset) => preset.key)
|
|
1342
|
+
const selectedMap = Object.fromEntries(presets.map((preset) => [preset.key, true]))
|
|
1343
|
+
const versionMap = Object.fromEntries(
|
|
1344
|
+
presets.map((preset, index) => [preset.key, String(chapters[index]?.version ?? '').trim()])
|
|
1345
|
+
)
|
|
1346
|
+
const generatorMap = Object.fromEntries(
|
|
1347
|
+
presets.map((preset, index) => [
|
|
1348
|
+
preset.key,
|
|
1349
|
+
String(chapters[index]?.generator ?? '').trim(),
|
|
1350
|
+
])
|
|
1351
|
+
)
|
|
1352
|
+
const optionsMap = Object.fromEntries(
|
|
1353
|
+
presets.map((preset, index) => {
|
|
1354
|
+
const text = versionMap[preset.key] || ''
|
|
1355
|
+
const uploadUsrNameDisplay = generatorMap[preset.key] || ''
|
|
1356
|
+
return [
|
|
1357
|
+
preset.key,
|
|
1358
|
+
text
|
|
1359
|
+
? [{ text, value: text, selected: true, uploadUsrNameDisplay }]
|
|
1360
|
+
: [],
|
|
1361
|
+
]
|
|
1362
|
+
})
|
|
1363
|
+
)
|
|
1364
|
+
const paint = () => {
|
|
1365
|
+
chapterPresets.value = presets
|
|
1366
|
+
chapterOrder.value = order
|
|
1367
|
+
selectedChapters.value = selectedMap
|
|
1368
|
+
chapterVersions.value = { ...versionMap }
|
|
1369
|
+
chapterGenerators.value = { ...generatorMap }
|
|
1370
|
+
settingsChapterVersionOptions.value = { ...optionsMap }
|
|
1371
|
+
}
|
|
1372
|
+
paint()
|
|
1373
|
+
void nextTick(paint)
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function buildCurrentChapterSelection(): ReportChapterSelectionItem[] {
|
|
1377
|
+
return orderedPresets.value
|
|
1378
|
+
.filter((preset) => selectedChapters.value[preset.key] && !isChapterUnavailable(preset))
|
|
1379
|
+
.map((preset) => ({
|
|
1380
|
+
label: preset.label,
|
|
1381
|
+
version: getChapterVersionDisplayText(preset.key),
|
|
1382
|
+
generator: getGeneratorNameChars(preset.key),
|
|
1383
|
+
}))
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
function checkHistoryLayoutDrift() {
|
|
1387
|
+
if (props.readonly) return
|
|
1388
|
+
if (!historyPreviewLayoutBaseline.value || isApplyingHistoryLayout.value) return
|
|
1389
|
+
|
|
1390
|
+
const currentLayout = serializeChapterLayoutFromState(
|
|
1391
|
+
chapterOrder.value,
|
|
1392
|
+
selectedChapters.value,
|
|
1393
|
+
chapterPresets.value,
|
|
1394
|
+
(label) => UNAVAILABLE_CHAPTER_LABELS.has(label)
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
if (currentLayout !== historyPreviewLayoutBaseline.value) {
|
|
1398
|
+
markHistoryPreviewDirty()
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
function toggleAllChapters() {
|
|
1403
|
+
if (props.readonly) return
|
|
1404
|
+
const selectable = chapterPresets.value.filter((preset) => !isChapterUnavailable(preset))
|
|
1405
|
+
const next = !selectable.every((preset) => selectedChapters.value[preset.key])
|
|
1406
|
+
selectable.forEach((preset) => {
|
|
1407
|
+
selectedChapters.value[preset.key] = next
|
|
1408
|
+
})
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function onChapterDragStart(event: DragEvent, key: string) {
|
|
1412
|
+
if (props.readonly) {
|
|
1413
|
+
event.preventDefault()
|
|
1414
|
+
return
|
|
1415
|
+
}
|
|
1416
|
+
if (
|
|
1417
|
+
(event.target as HTMLElement).closest(
|
|
1418
|
+
'.chatbot-report-chapter-row__check, .chatbot-report-chapter-row__version, .chatbot-report-chapter-row__label, .el-select'
|
|
1419
|
+
)
|
|
1420
|
+
) {
|
|
1421
|
+
event.preventDefault()
|
|
1422
|
+
return
|
|
1423
|
+
}
|
|
1424
|
+
chapterRowDragMoved = true
|
|
1425
|
+
dragChapterKey.value = key
|
|
1426
|
+
dragOverChapterKey.value = key
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
function handleChapterDragEnd() {
|
|
1430
|
+
dragChapterKey.value = null
|
|
1431
|
+
dragOverChapterKey.value = null
|
|
1432
|
+
window.setTimeout(() => {
|
|
1433
|
+
chapterRowDragMoved = false
|
|
1434
|
+
}, 0)
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
function handleChapterDrop(targetKey: string) {
|
|
1438
|
+
if (props.readonly) return
|
|
1439
|
+
const draggingKey = dragChapterKey.value
|
|
1440
|
+
if (draggingKey && draggingKey !== targetKey) {
|
|
1441
|
+
const newOrder = [...chapterOrder.value]
|
|
1442
|
+
const fromIdx = newOrder.indexOf(draggingKey)
|
|
1443
|
+
const toIdx = newOrder.indexOf(targetKey)
|
|
1444
|
+
if (fromIdx !== -1 && toIdx !== -1) {
|
|
1445
|
+
newOrder.splice(fromIdx, 1)
|
|
1446
|
+
newOrder.splice(toIdx, 0, draggingKey)
|
|
1447
|
+
chapterOrder.value = newOrder
|
|
1448
|
+
// 釋放後:跳到新位置並從該章起重新列印
|
|
1449
|
+
void nextTick(() => {
|
|
1450
|
+
requestSettingsTypewriterReplay(draggingKey)
|
|
1451
|
+
})
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
handleChapterDragEnd()
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
function handleCoherenceCheck() {
|
|
1458
|
+
void loadCoherenceCheckWithApi()
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
async function handleGenerate() {
|
|
1462
|
+
const selection = buildCurrentChapterSelection()
|
|
1463
|
+
if (selection.length === 0) {
|
|
1464
|
+
ElMessage.warning('請先勾選章節')
|
|
1465
|
+
return
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
const templateId = selectedTemplate.value?.id
|
|
1469
|
+
if (!templateId) {
|
|
1470
|
+
ElMessage.warning('請先選擇模板')
|
|
1471
|
+
return
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// 原有「已生成」校驗(報告歷史章節版本匹配 / 本地定稿簽名)
|
|
1475
|
+
const existingTitle = await findAlreadyGeneratedReportTitle(templateId, selection)
|
|
1476
|
+
if (existingTitle) {
|
|
1477
|
+
ElMessage.info(`所選章節版本已生成報告《${existingTitle}》`)
|
|
1478
|
+
return
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
const signature = buildGenerationSignature(
|
|
1482
|
+
templateId,
|
|
1483
|
+
selection,
|
|
1484
|
+
draft.language,
|
|
1485
|
+
draft.chapterContentLanguage
|
|
1486
|
+
)
|
|
1487
|
+
|
|
1488
|
+
// existsInstanceVersionNo 有值 = 已通過連貫性檢查;否則看本地 signature
|
|
1489
|
+
const coherencePassed =
|
|
1490
|
+
!!String(existsInstanceVersionNo.value ?? '').trim() ||
|
|
1491
|
+
coherenceResultReady.value ||
|
|
1492
|
+
isCoherenceCheckCompleted(signature)
|
|
1493
|
+
if (!coherencePassed) {
|
|
1494
|
+
ElMessage.warning('請先進行報告連貫性檢查後再生成')
|
|
1495
|
+
return
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
try {
|
|
1499
|
+
await ElMessageBox.confirm('檢測到該報告已完成連貫性檢查,確認生成報告?', '提示', {
|
|
1500
|
+
confirmButtonText: '確認',
|
|
1501
|
+
cancelButtonText: '取消',
|
|
1502
|
+
type: 'info',
|
|
1503
|
+
})
|
|
1504
|
+
} catch {
|
|
1505
|
+
return
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
const streamed = await streamChartAsStreamToEditor(selection)
|
|
1509
|
+
if (!streamed) return
|
|
1510
|
+
|
|
1511
|
+
ElMessage.success('已生成')
|
|
1512
|
+
emit('generated', {
|
|
1513
|
+
historyKey: reportHistoryItems.value[0]?.key ?? '',
|
|
1514
|
+
displayTitle: draft.reportName?.trim() || '報告',
|
|
1515
|
+
})
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
const coherenceChecking = ref(false)
|
|
1519
|
+
let chartAsStreamAbort: AbortController | null = null
|
|
1520
|
+
let chartAsStreamSeq = 0
|
|
1521
|
+
|
|
1522
|
+
/** 圖中已勾選章節 + 當前版本(對齊 AdjustInstance.instanceSections) */
|
|
1523
|
+
function buildSelectedInstanceSections(): GenAiAdjustInstanceSection[] {
|
|
1524
|
+
return orderedPresets.value
|
|
1525
|
+
.filter((preset) => selectedChapters.value[preset.key] && !isChapterUnavailable(preset))
|
|
1526
|
+
.map((preset) => {
|
|
1527
|
+
const currentValue = String(chapterVersions.value[preset.key] ?? '').trim()
|
|
1528
|
+
const versions = getChapterVersionOptions(preset.key).map((item) => ({
|
|
1529
|
+
text: item.text,
|
|
1530
|
+
value: item.value,
|
|
1531
|
+
selected: item.value === currentValue,
|
|
1532
|
+
uploadUsrNameDisplay: String(item.uploadUsrNameDisplay ?? '').trim(),
|
|
1533
|
+
}))
|
|
1534
|
+
return {
|
|
1535
|
+
sectionTitle: preset.label,
|
|
1536
|
+
selected: true,
|
|
1537
|
+
createUserName: getGeneratorNameChars(preset.key) || undefined,
|
|
1538
|
+
versions: versions.length
|
|
1539
|
+
? versions
|
|
1540
|
+
: currentValue
|
|
1541
|
+
? [
|
|
1542
|
+
{
|
|
1543
|
+
text: getChapterVersionDisplayText(preset.key),
|
|
1544
|
+
value: currentValue,
|
|
1545
|
+
selected: true,
|
|
1546
|
+
uploadUsrNameDisplay: getGeneratorNameChars(preset.key),
|
|
1547
|
+
},
|
|
1548
|
+
]
|
|
1549
|
+
: [],
|
|
1550
|
+
}
|
|
1551
|
+
})
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
/**
|
|
1555
|
+
* 報告連貫性檢查:AdjustInstance promptType=COHERENCE_CHECK_PROMPT
|
|
1556
|
+
* 傳遞圖中已勾選章節 + 版本,成功後跳轉結果頁打字機展示
|
|
1557
|
+
*/
|
|
1558
|
+
async function loadCoherenceCheckWithApi() {
|
|
1559
|
+
const selection = buildCurrentChapterSelection().map((item) => ({
|
|
1560
|
+
label: item.label,
|
|
1561
|
+
version: item.version,
|
|
1562
|
+
generator: String(item.generator ?? '').trim(),
|
|
1563
|
+
}))
|
|
1564
|
+
const versionIdList = buildSelectedVersionIdList()
|
|
1565
|
+
const instanceSections = buildSelectedInstanceSections()
|
|
1566
|
+
const templateId = selectedTemplate.value?.id
|
|
1567
|
+
const instanceId = resolveInstanceId()
|
|
1568
|
+
|
|
1569
|
+
if (!selection.length || !versionIdList.length) {
|
|
1570
|
+
ElMessage.warning('請先勾選章節')
|
|
1571
|
+
return
|
|
1572
|
+
}
|
|
1573
|
+
if (!templateId) {
|
|
1574
|
+
ElMessage.warning('請先選擇模板')
|
|
1575
|
+
return
|
|
1576
|
+
}
|
|
1577
|
+
if (!instanceId) {
|
|
1578
|
+
ElMessage.warning('缺少報告實例,無法進行連貫性檢查')
|
|
1579
|
+
return
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
coherenceChecking.value = true
|
|
1583
|
+
try {
|
|
1584
|
+
const res: any = await genAiReportEditorApi
|
|
1585
|
+
.adjustGenAiInstance({
|
|
1586
|
+
instanceId,
|
|
1587
|
+
promptType: 'COHERENCE_CHECK_PROMPT',
|
|
1588
|
+
versionIdList,
|
|
1589
|
+
instanceSections,
|
|
1590
|
+
})
|
|
1591
|
+
.catch((err) => {
|
|
1592
|
+
console.error('接口异常', err)
|
|
1593
|
+
return null
|
|
1594
|
+
})
|
|
1595
|
+
|
|
1596
|
+
if (!isGenAiResponseOk(res)) {
|
|
1597
|
+
ElMessage.warning(res?.message || '連貫性檢查失敗')
|
|
1598
|
+
return
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
clearCoherenceCheckResultReady()
|
|
1602
|
+
openReportCoherenceCheck(router, {
|
|
1603
|
+
templateId,
|
|
1604
|
+
reportName: draft.reportName,
|
|
1605
|
+
year: draft.year,
|
|
1606
|
+
chapters: selection,
|
|
1607
|
+
chapterContentLanguage: draft.chapterContentLanguage,
|
|
1608
|
+
reportLanguage: draft.language,
|
|
1609
|
+
versionIdList,
|
|
1610
|
+
instanceId,
|
|
1611
|
+
coherenceCheckContentText: pickAdjustInstanceText(res?.data) || undefined,
|
|
1612
|
+
})
|
|
1613
|
+
} finally {
|
|
1614
|
+
coherenceChecking.value = false
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/** 點「生成報告」→ chartAsStream SSE,中欄打字機逐塊打印,[DONE] 結束 */
|
|
1619
|
+
async function streamChartAsStreamToEditor(selection: ReportChapterSelectionItem[]) {
|
|
1620
|
+
chartAsStreamAbort?.abort()
|
|
1621
|
+
chartAsStreamAbort = new AbortController()
|
|
1622
|
+
const seq = ++chartAsStreamSeq
|
|
1623
|
+
const signal = chartAsStreamAbort.signal
|
|
1624
|
+
|
|
1625
|
+
const promptDetail = [
|
|
1626
|
+
draft.reportName?.trim(),
|
|
1627
|
+
draft.researchFocus?.trim(),
|
|
1628
|
+
selection.map((item) => item.label).join('\n'),
|
|
1629
|
+
]
|
|
1630
|
+
.filter(Boolean)
|
|
1631
|
+
.join('\n')
|
|
1632
|
+
|
|
1633
|
+
let started = false
|
|
1634
|
+
|
|
1635
|
+
try {
|
|
1636
|
+
clearChapterChartStreamRefs()
|
|
1637
|
+
for await (const chunk of genAiReportEditorApi.streamChartAsStream(
|
|
1638
|
+
{
|
|
1639
|
+
modelName: String(draft.model || '').trim(),
|
|
1640
|
+
promptDetail: promptDetail || 'who are you?',
|
|
1641
|
+
promptType: 'AI_PROMPT',
|
|
1642
|
+
contentLengthLimit: null,
|
|
1643
|
+
tableInfo: null,
|
|
1644
|
+
chartInfo: null,
|
|
1645
|
+
fileIdList: sourceFiles.value.filter((file) => file.selected).map((file) => file.id),
|
|
1646
|
+
instanceId: resolveInstanceId(),
|
|
1647
|
+
categoryId: 1,
|
|
1648
|
+
temperature: null,
|
|
1649
|
+
maxTokens: null,
|
|
1650
|
+
stream: true,
|
|
1651
|
+
},
|
|
1652
|
+
signal,
|
|
1653
|
+
{
|
|
1654
|
+
//0918:event:REF → SSERefList.push
|
|
1655
|
+
onRefEvent: (ref) => upsertChapterChartStreamRef(ref),
|
|
1656
|
+
}
|
|
1657
|
+
)) {
|
|
1658
|
+
if (seq !== chartAsStreamSeq) return false
|
|
1659
|
+
if (!started) {
|
|
1660
|
+
beginReportGenerateStream()
|
|
1661
|
+
requestSettingsTypewriterReplay('report-generate-stream')
|
|
1662
|
+
started = true
|
|
1663
|
+
}
|
|
1664
|
+
appendReportGenerateStream(chunk)
|
|
1665
|
+
}
|
|
1666
|
+
if (seq !== chartAsStreamSeq) return false
|
|
1667
|
+
if (!started) return false
|
|
1668
|
+
finishReportGenerateStream()
|
|
1669
|
+
return true
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
if (signal.aborted || seq !== chartAsStreamSeq) return false
|
|
1672
|
+
console.error('接口异常', err)
|
|
1673
|
+
if (started) clearReportGenerateStream()
|
|
1674
|
+
ElMessage.warning(err instanceof Error && err.message.trim() ? err.message : '生成報告失敗')
|
|
1675
|
+
return false
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
</script>
|
|
1679
|
+
|
|
1680
|
+
<style scoped>
|
|
1681
|
+
.chatbot-report-panel--settings {
|
|
1682
|
+
--report-accent: #c53355;
|
|
1683
|
+
--report-accent-hover: #a82b48;
|
|
1684
|
+
--report-accent-soft: #fff5f6;
|
|
1685
|
+
--report-border: #ebeef5;
|
|
1686
|
+
--report-card-bg: #f5f5f5;
|
|
1687
|
+
display: flex;
|
|
1688
|
+
flex-direction: column;
|
|
1689
|
+
gap: 15px;
|
|
1690
|
+
min-width: 0;
|
|
1691
|
+
height: 100%;
|
|
1692
|
+
box-sizing: border-box;
|
|
1693
|
+
overflow: visible;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
.chatbot-report-panel--settings.is-compact {
|
|
1697
|
+
height: auto;
|
|
1698
|
+
align-self: flex-start;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/* 報告歷史:與章節歷史相同,展開時吃掉欄內剩餘高度 */
|
|
1702
|
+
.chatbot-report-panel--settings > :deep(.report-collapsible-section:last-child:not(.is-collapsed)) {
|
|
1703
|
+
flex: 1;
|
|
1704
|
+
display: flex;
|
|
1705
|
+
flex-direction: column;
|
|
1706
|
+
min-height: 0;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
.chatbot-report-panel--settings
|
|
1710
|
+
> :deep(
|
|
1711
|
+
.report-collapsible-section:last-child:not(.is-collapsed) .report-collapsible-section__body
|
|
1712
|
+
) {
|
|
1713
|
+
flex: 1;
|
|
1714
|
+
display: flex;
|
|
1715
|
+
flex-direction: column;
|
|
1716
|
+
min-height: 0;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
.chatbot-report-panel--settings :deep(.report-collapsible-section__toggle) {
|
|
1720
|
+
border-bottom: none;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.chatbot-report-panel--settings
|
|
1724
|
+
:deep(.report-collapsible-section.is-collapsed .report-collapsible-section__toggle) {
|
|
1725
|
+
padding-bottom: 0;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
.chatbot-report-panel--settings :deep(.report-collapsible-section__body) {
|
|
1729
|
+
padding-left: 0;
|
|
1730
|
+
padding-right: 0;
|
|
1731
|
+
padding-bottom: 10px;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
.chatbot-report-panel--settings :deep(.report-collapsible-section__arrow) {
|
|
1735
|
+
border-top-color: var(--report-accent);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
.chatbot-report-panel--settings.is-readonly {
|
|
1739
|
+
height: 100%;
|
|
1740
|
+
min-height: 0;
|
|
1741
|
+
gap: 0;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
.chatbot-report-panel--settings.is-readonly.is-compact {
|
|
1745
|
+
height: auto;
|
|
1746
|
+
align-self: flex-start;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
/* 僅展開時撐滿欄高;收起後隨標題收縮,避免留下空白卡片 */
|
|
1750
|
+
.chatbot-report-panel--settings.is-readonly
|
|
1751
|
+
> :deep(.report-collapsible-section:not(.is-collapsed)) {
|
|
1752
|
+
flex: 1;
|
|
1753
|
+
display: flex;
|
|
1754
|
+
flex-direction: column;
|
|
1755
|
+
min-height: 0;
|
|
1756
|
+
overflow: hidden;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
.chatbot-report-panel--settings.is-readonly > :deep(.report-collapsible-section.is-collapsed) {
|
|
1760
|
+
flex: 0 0 auto;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
.chatbot-report-panel--settings.is-readonly
|
|
1764
|
+
> :deep(.report-collapsible-section:not(.is-collapsed) .report-collapsible-section__body) {
|
|
1765
|
+
flex: 1;
|
|
1766
|
+
display: flex;
|
|
1767
|
+
flex-direction: column;
|
|
1768
|
+
min-height: 0;
|
|
1769
|
+
overflow: hidden;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
.chatbot-report-settings__content {
|
|
1773
|
+
position: relative;
|
|
1774
|
+
display: flex;
|
|
1775
|
+
flex-direction: column;
|
|
1776
|
+
min-width: 0;
|
|
1777
|
+
/* 內容自動撐開,不在設置區內滾動 */
|
|
1778
|
+
min-height: auto;
|
|
1779
|
+
overflow: visible;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
.chatbot-report-chapter-select {
|
|
1783
|
+
min-height: auto;
|
|
1784
|
+
overflow: visible;
|
|
1785
|
+
--chapter-sort-size: 16px;
|
|
1786
|
+
--chapter-check-size: 14px;
|
|
1787
|
+
--chapter-version-width: 48px;
|
|
1788
|
+
/* 足夠放下 3 個整字,或「兩字...」,避免 overflow 裁出半字 */
|
|
1789
|
+
--chapter-generator-width: 48px;
|
|
1790
|
+
/* 五列共用:拖拽 | 勾選 | 標題(吃剩餘) | 版本 | 生成人 —— 右側略收,標題多一點空間 */
|
|
1791
|
+
--chapter-grid-cols: var(--chapter-sort-size) var(--chapter-check-size) minmax(72px, 1fr)
|
|
1792
|
+
var(--chapter-version-width) var(--chapter-generator-width);
|
|
1793
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
1794
|
+
font-size: 14px;
|
|
1795
|
+
font-weight: 500;
|
|
1796
|
+
color: #303133;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
.chatbot-report-chapter-header,
|
|
1800
|
+
.chatbot-report-chapter-select-all,
|
|
1801
|
+
.chatbot-report-chapter-row {
|
|
1802
|
+
display: grid;
|
|
1803
|
+
grid-template-columns: var(--chapter-grid-cols);
|
|
1804
|
+
column-gap: 6px;
|
|
1805
|
+
align-items: center;
|
|
1806
|
+
padding: 0;
|
|
1807
|
+
box-sizing: border-box;
|
|
1808
|
+
width: 100%;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
.chatbot-report-chapter-header {
|
|
1812
|
+
padding-bottom: 6px;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
.chatbot-report-chapter-header__title,
|
|
1816
|
+
.chatbot-report-chapter-select-all__label,
|
|
1817
|
+
.chatbot-report-chapter-row__label {
|
|
1818
|
+
min-width: 0;
|
|
1819
|
+
text-align: left;
|
|
1820
|
+
justify-self: stretch;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
.chatbot-report-chapter-header__title {
|
|
1824
|
+
/* 與下方 sort icon 左對齊,橫跨拖拽 + 勾選 + 標題三列 */
|
|
1825
|
+
grid-column: 1 / 4;
|
|
1826
|
+
font-family: inherit;
|
|
1827
|
+
font-size: 14px;
|
|
1828
|
+
font-weight: 500;
|
|
1829
|
+
color: #303133;
|
|
1830
|
+
white-space: nowrap;
|
|
1831
|
+
overflow: hidden;
|
|
1832
|
+
text-overflow: ellipsis;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
.chatbot-report-chapter-header__col {
|
|
1836
|
+
font-family: inherit;
|
|
1837
|
+
font-size: 14px;
|
|
1838
|
+
font-weight: 500;
|
|
1839
|
+
color: #303133;
|
|
1840
|
+
text-align: left;
|
|
1841
|
+
white-space: nowrap;
|
|
1842
|
+
justify-self: stretch;
|
|
1843
|
+
width: 100%;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
.chatbot-report-chapter-header__col--version {
|
|
1847
|
+
grid-column: 4;
|
|
1848
|
+
text-align: left;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
.chatbot-report-chapter-header__col--generator {
|
|
1852
|
+
grid-column: 5;
|
|
1853
|
+
text-align: left;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
.chatbot-report-chapter-select-all {
|
|
1857
|
+
margin: 8px 0 6px;
|
|
1858
|
+
font-family: inherit;
|
|
1859
|
+
font-size: 14px;
|
|
1860
|
+
font-weight: 500;
|
|
1861
|
+
color: #303133;
|
|
1862
|
+
user-select: none;
|
|
1863
|
+
cursor: pointer;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
.chatbot-report-chapter-select-all:has(.chatbot-report-chapter-row__check:disabled) {
|
|
1867
|
+
cursor: default;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
.chatbot-report-chapter-select-all__label {
|
|
1871
|
+
font-family: inherit;
|
|
1872
|
+
font-size: 14px;
|
|
1873
|
+
font-weight: 500;
|
|
1874
|
+
color: #303133;
|
|
1875
|
+
cursor: inherit;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
.chatbot-report-chapter-row__sort {
|
|
1879
|
+
display: inline-flex;
|
|
1880
|
+
align-items: center;
|
|
1881
|
+
justify-content: center;
|
|
1882
|
+
box-sizing: border-box;
|
|
1883
|
+
width: var(--chapter-sort-size);
|
|
1884
|
+
height: var(--chapter-sort-size);
|
|
1885
|
+
margin: 0;
|
|
1886
|
+
padding: 0;
|
|
1887
|
+
color: #86909c;
|
|
1888
|
+
flex-shrink: 0;
|
|
1889
|
+
cursor: grab;
|
|
1890
|
+
justify-self: start;
|
|
1891
|
+
font-size: var(--chapter-sort-size);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
.chatbot-report-chapter-row__sort :deep(svg) {
|
|
1895
|
+
width: var(--chapter-sort-size);
|
|
1896
|
+
height: var(--chapter-sort-size);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
.chatbot-report-chapter-row__sort--placeholder {
|
|
1900
|
+
visibility: hidden;
|
|
1901
|
+
pointer-events: none;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
.chatbot-report-chapter-row__check {
|
|
1905
|
+
box-sizing: border-box;
|
|
1906
|
+
width: var(--chapter-check-size);
|
|
1907
|
+
height: var(--chapter-check-size);
|
|
1908
|
+
margin: 0;
|
|
1909
|
+
padding: 0;
|
|
1910
|
+
accent-color: var(--report-accent);
|
|
1911
|
+
cursor: pointer;
|
|
1912
|
+
flex-shrink: 0;
|
|
1913
|
+
justify-self: start;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
.chatbot-report-chapter-row__check--spacer {
|
|
1917
|
+
visibility: hidden;
|
|
1918
|
+
pointer-events: none;
|
|
1919
|
+
appearance: none;
|
|
1920
|
+
border: 0;
|
|
1921
|
+
background: transparent;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
.chatbot-report-chapter-list {
|
|
1925
|
+
display: flex;
|
|
1926
|
+
flex-direction: column;
|
|
1927
|
+
gap: 2px;
|
|
1928
|
+
overflow: visible;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
.chatbot-report-chapter-row-wrap {
|
|
1932
|
+
position: relative;
|
|
1933
|
+
z-index: 0;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
.chatbot-report-chapter-row-wrap.is-popover-open {
|
|
1937
|
+
z-index: 30;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
.chatbot-report-chapter-row {
|
|
1941
|
+
padding: 6px 0;
|
|
1942
|
+
margin: 0;
|
|
1943
|
+
font-family: inherit;
|
|
1944
|
+
font-size: 14px;
|
|
1945
|
+
font-weight: 500;
|
|
1946
|
+
color: #303133;
|
|
1947
|
+
border-radius: 8px;
|
|
1948
|
+
border: 1px solid transparent;
|
|
1949
|
+
box-sizing: border-box;
|
|
1950
|
+
cursor: grab;
|
|
1951
|
+
user-select: none;
|
|
1952
|
+
align-items: center;
|
|
1953
|
+
transition:
|
|
1954
|
+
background 0.15s ease,
|
|
1955
|
+
box-shadow 0.15s ease,
|
|
1956
|
+
opacity 0.15s ease,
|
|
1957
|
+
border-color 0.15s ease;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
.chatbot-report-chapter-row__sort,
|
|
1961
|
+
.chatbot-report-chapter-row__check {
|
|
1962
|
+
margin-top: 0;
|
|
1963
|
+
align-self: center;
|
|
1964
|
+
justify-self: start;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
/* 鼠標移入:灰底 + 圓角(對齊章節列表交互) */
|
|
1968
|
+
.chatbot-report-chapter-row:hover {
|
|
1969
|
+
background: #f5f7fa;
|
|
1970
|
+
border-radius: 8px;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/* 拖動中 / 落點 */
|
|
1974
|
+
.chatbot-report-chapter-row.is-dragging {
|
|
1975
|
+
opacity: 0.5;
|
|
1976
|
+
cursor: grabbing;
|
|
1977
|
+
background: #fff;
|
|
1978
|
+
border-color: #c53355;
|
|
1979
|
+
border-radius: 8px;
|
|
1980
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
.chatbot-report-chapter-row.is-drag-over {
|
|
1984
|
+
background: var(--report-accent-soft);
|
|
1985
|
+
border-color: rgba(197, 51, 85, 0.35);
|
|
1986
|
+
border-radius: 8px;
|
|
1987
|
+
box-shadow: inset 0 0 0 1px rgba(197, 51, 85, 0.35);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
.chatbot-report-chapter-row__label {
|
|
1991
|
+
min-width: 0;
|
|
1992
|
+
max-width: 100%;
|
|
1993
|
+
overflow: hidden;
|
|
1994
|
+
text-align: left;
|
|
1995
|
+
align-self: center;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
.chatbot-report-chapter-row__label-text {
|
|
1999
|
+
display: flex;
|
|
2000
|
+
align-items: center;
|
|
2001
|
+
width: 100%;
|
|
2002
|
+
max-width: 100%;
|
|
2003
|
+
min-width: 0;
|
|
2004
|
+
margin: 0;
|
|
2005
|
+
padding: 0;
|
|
2006
|
+
border: none;
|
|
2007
|
+
background: transparent;
|
|
2008
|
+
font-family: inherit;
|
|
2009
|
+
font-size: 14px;
|
|
2010
|
+
line-height: 1.45;
|
|
2011
|
+
font-weight: 500;
|
|
2012
|
+
color: #303133;
|
|
2013
|
+
text-align: left;
|
|
2014
|
+
cursor: default;
|
|
2015
|
+
pointer-events: auto;
|
|
2016
|
+
position: relative;
|
|
2017
|
+
z-index: 1;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
.chatbot-report-chapter-row__label-main {
|
|
2021
|
+
flex: 1 1 auto;
|
|
2022
|
+
min-width: 0;
|
|
2023
|
+
overflow: hidden;
|
|
2024
|
+
white-space: nowrap;
|
|
2025
|
+
color: #303133;
|
|
2026
|
+
font-family: inherit;
|
|
2027
|
+
font-weight: 500;
|
|
2028
|
+
font-size: 14px;
|
|
2029
|
+
line-height: 1.45;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
.chatbot-report-chapter-row__label-ellipsis {
|
|
2033
|
+
flex: 0 0 auto;
|
|
2034
|
+
margin-left: 1px;
|
|
2035
|
+
color: #c53355;
|
|
2036
|
+
font-weight: 900;
|
|
2037
|
+
font-size: 15px;
|
|
2038
|
+
line-height: 1.2;
|
|
2039
|
+
letter-spacing: 0;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
.chatbot-report-chapter-row__label-text.is-truncated {
|
|
2043
|
+
cursor: pointer;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
.chatbot-report-chapter-row.is-unavailable {
|
|
2047
|
+
cursor: default;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
.chatbot-report-chapter-row.is-unavailable .chatbot-report-chapter-row__label-text {
|
|
2051
|
+
color: #909399;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
.chatbot-report-chapter-row__check:disabled {
|
|
2055
|
+
cursor: not-allowed;
|
|
2056
|
+
opacity: 0.45;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
.chatbot-report-chapter-row__no-version {
|
|
2060
|
+
grid-column: 4 / 6;
|
|
2061
|
+
font-family: inherit;
|
|
2062
|
+
font-size: 14px;
|
|
2063
|
+
font-weight: 500;
|
|
2064
|
+
line-height: 1.45;
|
|
2065
|
+
color: #909399;
|
|
2066
|
+
text-align: left;
|
|
2067
|
+
white-space: nowrap;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
.chatbot-report-chapter-row__version {
|
|
2071
|
+
width: 100%;
|
|
2072
|
+
min-width: 0;
|
|
2073
|
+
max-width: none;
|
|
2074
|
+
flex-shrink: 0;
|
|
2075
|
+
justify-self: stretch;
|
|
2076
|
+
align-self: center;
|
|
2077
|
+
margin-top: 0;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
.chatbot-report-chapter-row__version :deep(.el-select__wrapper) {
|
|
2081
|
+
display: flex;
|
|
2082
|
+
align-items: center;
|
|
2083
|
+
justify-content: space-between;
|
|
2084
|
+
gap: 4px;
|
|
2085
|
+
min-height: 24px;
|
|
2086
|
+
padding: 0 6px;
|
|
2087
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
2088
|
+
font-size: 14px;
|
|
2089
|
+
font-weight: 500;
|
|
2090
|
+
box-shadow: 0 0 0 1px #dcdfe6 inset;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
.chatbot-report-chapter-row__version :deep(.el-select__selection) {
|
|
2094
|
+
flex: 1 1 auto;
|
|
2095
|
+
flex-wrap: nowrap;
|
|
2096
|
+
min-width: 0;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
.chatbot-report-chapter-row__version :deep(.el-select__selected-item) {
|
|
2100
|
+
color: #303133;
|
|
2101
|
+
font-family: inherit;
|
|
2102
|
+
font-size: 14px;
|
|
2103
|
+
font-weight: 500;
|
|
2104
|
+
line-height: 1.3;
|
|
2105
|
+
white-space: nowrap;
|
|
2106
|
+
overflow: hidden;
|
|
2107
|
+
text-overflow: ellipsis;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
.chatbot-report-chapter-row__version :deep(.el-select__suffix) {
|
|
2111
|
+
flex: 0 0 auto;
|
|
2112
|
+
margin-left: auto;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
.chatbot-report-chapter-row__version :deep(.el-select__caret) {
|
|
2116
|
+
font-size: 14px;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
.chatbot-report-chapter-row__version :deep(.el-select__wrapper:hover) {
|
|
2120
|
+
box-shadow: 0 0 0 1px var(--report-accent) inset;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
.chatbot-report-chapter-row__version :deep(.el-select__wrapper.is-focused) {
|
|
2124
|
+
box-shadow: 0 0 0 1px var(--report-accent) inset;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
.chatbot-report-chapter-row__generator {
|
|
2128
|
+
display: inline-flex;
|
|
2129
|
+
align-items: center;
|
|
2130
|
+
min-width: 0;
|
|
2131
|
+
width: 100%;
|
|
2132
|
+
max-width: 100%;
|
|
2133
|
+
justify-self: stretch;
|
|
2134
|
+
align-self: center;
|
|
2135
|
+
font-family: inherit;
|
|
2136
|
+
font-size: 14px;
|
|
2137
|
+
font-weight: 500;
|
|
2138
|
+
line-height: 1.45;
|
|
2139
|
+
color: #303133;
|
|
2140
|
+
text-align: left;
|
|
2141
|
+
overflow: hidden;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
.chatbot-report-chapter-row__generator-main {
|
|
2145
|
+
flex: 0 0 auto;
|
|
2146
|
+
max-width: none;
|
|
2147
|
+
overflow: visible;
|
|
2148
|
+
white-space: nowrap;
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
.chatbot-report-chapter-row__generator-ellipsis {
|
|
2152
|
+
flex: 0 0 auto;
|
|
2153
|
+
margin-left: 1px;
|
|
2154
|
+
color: #c53355;
|
|
2155
|
+
font-weight: 900;
|
|
2156
|
+
font-size: 13px;
|
|
2157
|
+
line-height: 1.2;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
.chatbot-report-chapter-row.is-ellipsis-clickable {
|
|
2161
|
+
cursor: pointer;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
.chatbot-report-chapter-row.is-ellipsis-clickable .chatbot-report-chapter-row__label-text,
|
|
2165
|
+
.chatbot-report-chapter-row.is-ellipsis-clickable .chatbot-report-chapter-row__generator {
|
|
2166
|
+
cursor: pointer;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
.chatbot-report-settings__toolbar {
|
|
2170
|
+
display: flex;
|
|
2171
|
+
justify-content: center;
|
|
2172
|
+
flex-shrink: 0;
|
|
2173
|
+
margin-top: 16px;
|
|
2174
|
+
width: 100%;
|
|
2175
|
+
max-width: 100%;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
/* 左列:下拉與「報告連貫性檢查」同寬左對齊;右列:「生成報告」與左列按鈕底對齊 */
|
|
2179
|
+
.chatbot-report-settings__toolbar-inner {
|
|
2180
|
+
display: flex;
|
|
2181
|
+
align-items: flex-end;
|
|
2182
|
+
gap: 10px;
|
|
2183
|
+
width: max-content;
|
|
2184
|
+
max-width: 100%;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
.chatbot-report-settings__col-coherence {
|
|
2188
|
+
display: inline-flex;
|
|
2189
|
+
flex-direction: column;
|
|
2190
|
+
align-items: stretch;
|
|
2191
|
+
gap: 12px;
|
|
2192
|
+
width: max-content;
|
|
2193
|
+
max-width: 100%;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
.chatbot-report-settings__language {
|
|
2197
|
+
/* 不撐開父級,寬度跟隨「報告連貫性檢查」按鈕 */
|
|
2198
|
+
width: 0;
|
|
2199
|
+
min-width: 100%;
|
|
2200
|
+
max-width: none;
|
|
2201
|
+
flex-shrink: 0;
|
|
2202
|
+
align-self: stretch;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
.chatbot-report-settings__language :deep(.el-select__wrapper) {
|
|
2206
|
+
width: 100%;
|
|
2207
|
+
min-width: 0;
|
|
2208
|
+
min-height: 32px;
|
|
2209
|
+
padding: 0 10px;
|
|
2210
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
2211
|
+
font-size: 16px;
|
|
2212
|
+
font-weight: 500;
|
|
2213
|
+
color: #303133;
|
|
2214
|
+
box-shadow: 0 0 0 1px #dcdfe6 inset;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
.chatbot-report-settings__language :deep(.el-select__wrapper:hover),
|
|
2218
|
+
.chatbot-report-settings__language :deep(.el-select__wrapper.is-focused) {
|
|
2219
|
+
box-shadow: 0 0 0 1px var(--report-accent) inset;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
.chatbot-report-settings__language :deep(.el-select__caret) {
|
|
2223
|
+
color: #dcdfe6;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
.chatbot-report-settings__btn--coherence.el-button {
|
|
2227
|
+
width: max-content;
|
|
2228
|
+
white-space: nowrap;
|
|
2229
|
+
margin-left: 0 !important;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
.chatbot-report-settings__btn--generate.el-button {
|
|
2233
|
+
margin-left: 0 !important;
|
|
2234
|
+
width: 100px;
|
|
2235
|
+
min-width: 100px;
|
|
2236
|
+
max-width: 100px;
|
|
2237
|
+
padding: 0 8px;
|
|
2238
|
+
font-size: 16px;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
.chatbot-report-settings__btn--primary.el-button {
|
|
2242
|
+
height: 32px;
|
|
2243
|
+
padding: 0 14px;
|
|
2244
|
+
border: 1px solid var(--report-accent) !important;
|
|
2245
|
+
background: var(--report-accent) !important;
|
|
2246
|
+
color: #fff !important;
|
|
2247
|
+
border-radius: 4px;
|
|
2248
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
2249
|
+
font-size: 16px;
|
|
2250
|
+
font-weight: 500;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
.chatbot-report-settings__btn--primary.el-button:hover:not(.is-disabled) {
|
|
2254
|
+
background: var(--report-accent-hover) !important;
|
|
2255
|
+
border-color: var(--report-accent-hover) !important;
|
|
2256
|
+
color: #fff !important;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
.chatbot-report-settings__btn--primary.el-button.is-disabled {
|
|
2260
|
+
border-color: #dcdfe6 !important;
|
|
2261
|
+
background: #f5f7fa !important;
|
|
2262
|
+
color: #c0c4cc !important;
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
.chatbot-report-panel--settings.is-readonly :deep(.chatbot-report-chapter-row__sort) {
|
|
2266
|
+
opacity: 0.45;
|
|
2267
|
+
cursor: default;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
.chatbot-report-panel--settings.is-readonly :deep(.chatbot-report-chapter-row__check:disabled) {
|
|
2271
|
+
cursor: not-allowed;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
/* 只讀:版本下拉禁用,點擊穿透到行,便於打開詳情 popover */
|
|
2275
|
+
.chatbot-report-panel--settings.is-readonly .chatbot-report-chapter-row__version {
|
|
2276
|
+
pointer-events: none;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
.chatbot-report-panel--settings.is-readonly .chatbot-report-chapter-row {
|
|
2280
|
+
cursor: pointer;
|
|
2281
|
+
}
|
|
2282
|
+
</style>
|
|
2283
|
+
|
|
2284
|
+
<style>
|
|
2285
|
+
.chatbot-report-panel--settings .el-select-dropdown__item.is-selected {
|
|
2286
|
+
color: #c53355;
|
|
2287
|
+
font-weight: 600;
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
.chatbot-report-panel--settings .el-select-dropdown__item.is-hovering {
|
|
2291
|
+
background-color: #fff5f6;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
/* 章節詳情:浮動在當前行下方,absolute 不佔文檔流空間 */
|
|
2295
|
+
.chatbot-report-chapter-title-popover-local {
|
|
2296
|
+
position: absolute;
|
|
2297
|
+
top: calc(100% + 2px);
|
|
2298
|
+
left: 0;
|
|
2299
|
+
right: 0;
|
|
2300
|
+
z-index: 40;
|
|
2301
|
+
width: 100%;
|
|
2302
|
+
max-width: 100%;
|
|
2303
|
+
margin: 0;
|
|
2304
|
+
padding: 12px 14px;
|
|
2305
|
+
border: 1px solid #ebeef5;
|
|
2306
|
+
border-radius: 8px;
|
|
2307
|
+
background: #fff;
|
|
2308
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
2309
|
+
box-sizing: border-box;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
/* 檢查頁只讀:避免被父級 overflow 裁切詳情 */
|
|
2313
|
+
.chatbot-report-panel--settings.is-readonly
|
|
2314
|
+
.chatbot-report-chapter-row-wrap.is-popover-open
|
|
2315
|
+
.chatbot-report-chapter-title-popover-local {
|
|
2316
|
+
z-index: 60;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
.chatbot-report-chapter-title-popover__title {
|
|
2320
|
+
margin: 0 0 10px;
|
|
2321
|
+
color: #303133;
|
|
2322
|
+
font-size: 14px;
|
|
2323
|
+
font-weight: 600;
|
|
2324
|
+
line-height: 1.4;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
.chatbot-report-chapter-title-popover__body {
|
|
2328
|
+
margin: 0;
|
|
2329
|
+
color: #303133;
|
|
2330
|
+
font-size: 13px;
|
|
2331
|
+
line-height: 1.5;
|
|
2332
|
+
word-break: break-word;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
.chatbot-report-chapter-detail-row {
|
|
2336
|
+
display: grid;
|
|
2337
|
+
grid-template-columns: 64px minmax(0, 1fr);
|
|
2338
|
+
column-gap: 10px;
|
|
2339
|
+
align-items: start;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
.chatbot-report-chapter-detail-row + .chatbot-report-chapter-detail-row {
|
|
2343
|
+
margin-top: 10px;
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
.chatbot-report-chapter-detail-row__label {
|
|
2347
|
+
color: #909399;
|
|
2348
|
+
font-size: 12px;
|
|
2349
|
+
font-weight: 600;
|
|
2350
|
+
line-height: 1.5;
|
|
2351
|
+
white-space: nowrap;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
.chatbot-report-chapter-detail-row__value {
|
|
2355
|
+
min-width: 0;
|
|
2356
|
+
color: #303133;
|
|
2357
|
+
font-size: 13px;
|
|
2358
|
+
line-height: 1.5;
|
|
2359
|
+
word-break: break-word;
|
|
2360
|
+
}
|
|
2361
|
+
</style>
|