@liujitcn/kratos-admin-bi 0.0.0-stage → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +43 -2
  2. package/dist/package/declarations/index.d.ts +1 -0
  3. package/dist/package/declarations/src/api/bi/admin/v1/bi_card.d.ts +18 -0
  4. package/dist/package/declarations/src/api/bi/admin/v1/bi_card.js +46 -0
  5. package/dist/package/declarations/src/api/bi/admin/v1/bi_dashboard.d.ts +17 -0
  6. package/dist/package/declarations/src/api/bi/admin/v1/bi_dashboard.js +30 -0
  7. package/dist/package/declarations/src/api/bi/admin/v1/bi_query.d.ts +11 -0
  8. package/dist/package/declarations/src/api/bi/admin/v1/bi_query.js +41 -0
  9. package/dist/package/declarations/src/api/bi/admin/v1/bi_query_record.d.ts +10 -0
  10. package/dist/package/declarations/src/api/bi/admin/v1/bi_query_record.js +14 -0
  11. package/dist/package/declarations/src/api/bi/admin/v1/bi_source.d.ts +26 -0
  12. package/dist/package/declarations/src/api/bi/admin/v1/bi_source.js +46 -0
  13. package/dist/package/declarations/src/bi-core.d.ts +17 -0
  14. package/dist/package/declarations/src/bi-core.js +22 -0
  15. package/dist/package/declarations/src/bi-dashboard-schema.d.ts +31 -0
  16. package/dist/package/declarations/src/bi-dashboard-schema.js +39 -0
  17. package/dist/package/declarations/src/bi-messages.d.ts +247 -0
  18. package/dist/package/declarations/src/bi-messages.js +247 -0
  19. package/dist/package/declarations/src/bi-response.d.ts +10 -0
  20. package/dist/package/declarations/src/bi-response.js +33 -0
  21. package/dist/package/declarations/src/bi-vue.d.ts +10 -0
  22. package/dist/package/declarations/src/bi-vue.js +10 -0
  23. package/dist/package/declarations/src/card-schema.d.ts +30 -0
  24. package/dist/package/declarations/src/card-schema.js +87 -0
  25. package/dist/package/declarations/src/components/BiCardChartEditor.vue.d.ts +17 -0
  26. package/dist/package/declarations/src/components/BiCardChartEditor.vue.js +527 -0
  27. package/dist/package/declarations/src/components/BiCardFormDialog.vue.d.ts +20 -0
  28. package/dist/package/declarations/src/components/BiCardFormDialog.vue.js +495 -0
  29. package/dist/package/declarations/src/components/BiCardLibrary.vue.d.ts +3 -0
  30. package/dist/package/declarations/src/components/BiCardLibrary.vue.js +807 -0
  31. package/dist/package/declarations/src/components/BiCardVisualization.vue.d.ts +10 -0
  32. package/dist/package/declarations/src/components/BiCardVisualization.vue.js +97 -0
  33. package/dist/package/declarations/src/components/BiChart.vue.d.ts +20 -0
  34. package/dist/package/declarations/src/components/BiChart.vue.js +112 -0
  35. package/dist/package/declarations/src/components/BiDashboard.vue.d.ts +9 -0
  36. package/dist/package/declarations/src/components/BiDashboard.vue.js +81 -0
  37. package/dist/package/declarations/src/components/BiDashboardMenuDialog.vue.d.ts +12 -0
  38. package/dist/package/declarations/src/components/BiDashboardMenuDialog.vue.js +252 -0
  39. package/dist/package/declarations/src/components/BiDataCard.vue.d.ts +36 -0
  40. package/dist/package/declarations/src/components/BiDataCard.vue.js +372 -0
  41. package/dist/package/declarations/src/components/BiDesigner.vue.d.ts +17 -0
  42. package/dist/package/declarations/src/components/BiDesigner.vue.js +666 -0
  43. package/dist/package/declarations/src/components/BiSourceModelDialog.vue.d.ts +17 -0
  44. package/dist/package/declarations/src/components/BiSourceModelDialog.vue.js +2118 -0
  45. package/dist/package/declarations/src/components/BiWorkbenchPreview.vue.d.ts +7 -0
  46. package/dist/package/declarations/src/components/BiWorkbenchPreview.vue.js +176 -0
  47. package/dist/package/declarations/src/components/source-model-layout.d.ts +3 -0
  48. package/dist/package/declarations/src/components/source-model-layout.js +66 -0
  49. package/dist/package/declarations/src/dataset/index.d.ts +7 -0
  50. package/dist/package/declarations/src/dataset/index.js +25 -0
  51. package/dist/package/declarations/src/dataset/labels.d.ts +2 -0
  52. package/dist/package/declarations/src/dataset/labels.js +21 -0
  53. package/dist/package/declarations/src/dataset/transform.d.ts +20 -0
  54. package/dist/package/declarations/src/dataset/transform.js +173 -0
  55. package/dist/package/declarations/src/engine/chart-types.d.ts +14 -0
  56. package/dist/package/declarations/src/engine/chart-types.js +37 -0
  57. package/dist/package/declarations/src/engine/echarts.d.ts +16 -0
  58. package/dist/package/declarations/src/engine/echarts.js +491 -0
  59. package/dist/package/declarations/src/engine/types.d.ts +36 -0
  60. package/dist/package/declarations/src/engine/types.js +20 -0
  61. package/dist/package/declarations/src/env.d.ts +17 -0
  62. package/dist/package/declarations/src/env.js +67 -0
  63. package/dist/package/declarations/src/index.d.ts +1 -0
  64. package/dist/package/declarations/src/index.js +1 -0
  65. package/dist/package/declarations/src/locales/en-US.json +254 -0
  66. package/dist/package/declarations/src/locales/generated.d.ts +1021 -0
  67. package/dist/package/declarations/src/locales/generated.js +13 -0
  68. package/dist/package/declarations/src/locales/ja-JP.json +254 -0
  69. package/dist/package/declarations/src/locales/zh-CN.json +254 -0
  70. package/dist/package/declarations/src/locales/zh-TW.json +254 -0
  71. package/dist/package/declarations/src/module.d.ts +2 -0
  72. package/dist/package/declarations/src/module.js +9 -0
  73. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_card.d.ts +145 -0
  74. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_card.js +6 -0
  75. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_dashboard.d.ts +107 -0
  76. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_dashboard.js +6 -0
  77. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query.d.ts +157 -0
  78. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query.js +6 -0
  79. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query_record.d.ts +83 -0
  80. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_query_record.js +15 -0
  81. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_source.d.ts +301 -0
  82. package/dist/package/declarations/src/rpc/bi/admin/v1/bi_source.js +6 -0
  83. package/dist/package/declarations/src/rpc/common/v1/types.d.ts +60 -0
  84. package/dist/package/declarations/src/rpc/common/v1/types.js +6 -0
  85. package/dist/package/declarations/src/schema/layout.d.ts +7 -0
  86. package/dist/package/declarations/src/schema/layout.js +52 -0
  87. package/dist/package/declarations/src/schema/types.d.ts +83 -0
  88. package/dist/package/declarations/src/schema/types.js +1 -0
  89. package/dist/package/declarations/src/theme/index.d.ts +29 -0
  90. package/dist/package/declarations/src/theme/index.js +83 -0
  91. package/dist/package/declarations/src/theme/palettes.d.ts +7 -0
  92. package/dist/package/declarations/src/theme/palettes.js +28 -0
  93. package/dist/package/declarations/src/theme/presets.d.ts +73 -0
  94. package/dist/package/declarations/src/theme/presets.js +235 -0
  95. package/dist/package/declarations/src/views/card/index.vue.d.ts +3 -0
  96. package/dist/package/declarations/src/views/card/index.vue.js +18 -0
  97. package/dist/package/declarations/src/views/dashboard/index.vue.d.ts +3 -0
  98. package/dist/package/declarations/src/views/dashboard/index.vue.js +107 -0
  99. package/dist/package/declarations/src/views/designer/index.vue.d.ts +3 -0
  100. package/dist/package/declarations/src/views/designer/index.vue.js +332 -0
  101. package/dist/package/declarations/src/views/manage/index.vue.d.ts +3 -0
  102. package/dist/package/declarations/src/views/manage/index.vue.js +325 -0
  103. package/dist/package/declarations/src/views/query/index.vue.d.ts +3 -0
  104. package/dist/package/declarations/src/views/query/index.vue.js +876 -0
  105. package/dist/package/declarations/src/views/source/index.vue.d.ts +3 -0
  106. package/dist/package/declarations/src/views/source/index.vue.js +343 -0
  107. package/dist/package/declarations/src/views/workbench/index.vue.d.ts +3 -0
  108. package/dist/package/declarations/src/views/workbench/index.vue.js +362 -0
  109. package/dist/package/src/api/bi/admin/v1/bi_card.ts +67 -0
  110. package/dist/package/src/api/bi/admin/v1/bi_dashboard.ts +51 -0
  111. package/dist/package/src/api/bi/admin/v1/bi_query.ts +55 -0
  112. package/dist/package/src/api/bi/admin/v1/bi_query_record.ts +25 -0
  113. package/dist/package/src/api/bi/admin/v1/bi_source.ts +80 -0
  114. package/dist/package/src/bi-core.ts +43 -0
  115. package/dist/package/src/bi-dashboard-schema.ts +69 -0
  116. package/dist/package/src/bi-messages.ts +249 -0
  117. package/dist/package/src/bi-response.ts +39 -0
  118. package/dist/package/src/bi-vue.ts +13 -0
  119. package/dist/package/src/card-schema.ts +111 -0
  120. package/dist/package/src/components/BiCardChartEditor.vue +139 -0
  121. package/dist/package/src/components/BiCardFormDialog.vue +289 -0
  122. package/dist/package/src/components/BiCardLibrary.vue +310 -0
  123. package/dist/package/src/components/BiCardVisualization.vue +95 -0
  124. package/dist/package/src/components/BiChart.vue +134 -0
  125. package/dist/package/src/components/BiDashboard.vue +53 -0
  126. package/dist/package/src/components/BiDashboardMenuDialog.vue +177 -0
  127. package/dist/package/src/components/BiDataCard.vue +231 -0
  128. package/dist/package/src/components/BiDesigner.vue +287 -0
  129. package/dist/package/src/components/BiSourceModelDialog.vue +846 -0
  130. package/dist/package/src/components/BiWorkbenchPreview.vue +131 -0
  131. package/dist/package/src/components/source-model-layout.ts +62 -0
  132. package/dist/package/src/dataset/index.ts +28 -0
  133. package/dist/package/src/dataset/labels.ts +19 -0
  134. package/dist/package/src/dataset/transform.ts +189 -0
  135. package/dist/package/src/engine/chart-types.ts +44 -0
  136. package/dist/package/src/engine/echarts.ts +568 -0
  137. package/dist/package/src/engine/types.ts +60 -0
  138. package/dist/package/src/env.ts +82 -0
  139. package/dist/package/src/index.ts +1 -0
  140. package/dist/package/src/locales/en-US.json +254 -0
  141. package/dist/package/src/locales/generated.ts +16 -0
  142. package/dist/package/src/locales/ja-JP.json +254 -0
  143. package/dist/package/src/locales/zh-CN.json +254 -0
  144. package/dist/package/src/locales/zh-TW.json +254 -0
  145. package/dist/package/src/module.ts +12 -0
  146. package/dist/package/src/rpc/bi/admin/v1/bi_card.ts +169 -0
  147. package/dist/package/src/rpc/bi/admin/v1/bi_dashboard.ts +126 -0
  148. package/dist/package/src/rpc/bi/admin/v1/bi_query.ts +187 -0
  149. package/dist/package/src/rpc/bi/admin/v1/bi_query_record.ts +98 -0
  150. package/dist/package/src/rpc/bi/admin/v1/bi_source.ts +338 -0
  151. package/dist/package/src/rpc/common/v1/types.ts +77 -0
  152. package/dist/package/src/schema/layout.ts +54 -0
  153. package/dist/package/src/schema/types.ts +103 -0
  154. package/dist/package/src/styles.scss +1 -0
  155. package/dist/package/src/theme/index.ts +108 -0
  156. package/dist/package/src/theme/palettes.ts +34 -0
  157. package/dist/package/src/theme/presets.ts +278 -0
  158. package/dist/package/src/views/card/index.vue +5 -0
  159. package/dist/package/src/views/dashboard/index.vue +58 -0
  160. package/dist/package/src/views/designer/index.vue +164 -0
  161. package/dist/package/src/views/manage/index.vue +276 -0
  162. package/dist/package/src/views/query/index.vue +593 -0
  163. package/dist/package/src/views/source/index.vue +214 -0
  164. package/dist/package/src/views/workbench/index.vue +208 -0
  165. package/package.json +67 -3
  166. package/tsconfig.json +12 -0
@@ -0,0 +1,593 @@
1
+ <template>
2
+ <main class="bi-query-page">
3
+ <header class="bi-query-page__header">
4
+ <div>
5
+ <h2>{{ t("bi.query.subtitle") }}</h2>
6
+ <p>{{ t("bi.query.guide") }}</p>
7
+ </div>
8
+ <div>
9
+ <el-button v-if="historyCollapsed" @click="historyCollapsed = false">{{ t("bi.query.history") }}</el-button
10
+ ><el-button @click="router.push('/bi/card')">{{ t("bi.card.library") }}</el-button>
11
+ </div>
12
+ </header>
13
+ <div class="bi-query-layout" :class="{ 'is-history-collapsed': historyCollapsed }">
14
+ <SessionPanel
15
+ v-if="!historyCollapsed"
16
+ v-model:keyword="keyword"
17
+ :active="activeRecord"
18
+ :sessions="sessions"
19
+ :editable="false" :title="t('bi.query.history')" :search-placeholder="t('bi.query.search_history')"
20
+ @change="item => selectRecord(Number(item.id))"
21
+ @create="newQuestion"
22
+ @toggle-collapse="historyCollapsed = true"
23
+ />
24
+ <section class="bi-query-chat" :aria-label="t('bi.query.title')">
25
+ <div class="bi-query-messages" aria-live="polite">
26
+ <div v-if="submittedQuestion" class="bi-query-bubble">{{ submittedQuestion }}</div>
27
+ <el-skeleton v-if="loading" :rows="5" animated /><el-alert
28
+ v-else-if="error"
29
+ :title="error"
30
+ type="error"
31
+ show-icon
32
+ :closable="false"
33
+ /><template v-else-if="response"
34
+ ><div class="bi-query-assistant">{{ t("bi.query.assistant") }}</div>
35
+ <h3>{{ response.title }}</h3>
36
+ <p>{{ t("bi.query.result_ready") }}</p>
37
+ <div class="bi-query-facts">
38
+ <p>
39
+ {{ t("bi.card.source") }}:{{
40
+ sources.find(item => item.code === response?.query?.source_code)?.name || response.query?.source_code
41
+ }}
42
+ </p>
43
+ <p>{{ t("bi.card.dimensions") }}:{{ response.query?.dimensions.join("、") || t("bi.card.ungrouped") }}</p>
44
+ <p>{{ t("bi.card.measure") }}:{{ response.query?.measures.map(item => item.code).join("、") }}</p>
45
+ </div>
46
+ <el-button link type="primary" @click="showTable = !showTable">{{
47
+ t(showTable ? "bi.query.hide_details" : "bi.card.table_tab")
48
+ }}</el-button
49
+ ><el-table v-if="showTable" :data="tableRows" border stripe
50
+ ><el-table-column
51
+ v-for="(column, index) in response.result?.columns || []"
52
+ :key="column.code"
53
+ :prop="String(index)"
54
+ :label="column.name"
55
+ min-width="120" /></el-table></template
56
+ ><el-empty v-else :description="t('bi.query.empty')" />
57
+ </div>
58
+ <div class="bi-query-compose">
59
+ <div class="bi-query-compose__input">
60
+ <el-input
61
+ v-model="question"
62
+ type="textarea"
63
+ :autosize="{ minRows: 2, maxRows: 5 }"
64
+ :placeholder="t('bi.query.placeholder')"
65
+ :aria-label="t('bi.query.placeholder')"
66
+ :disabled="loading"
67
+ :readonly="recording"
68
+ @keydown.enter="handleInputEnter($event as KeyboardEvent)"
69
+ />
70
+ <div class="bi-query-compose__footer">
71
+ <span role="status">{{
72
+ recording
73
+ ? t("bi.query.recognizing_voice")
74
+ : sources.find(source => source.code === response?.query?.source_code)?.name
75
+ }}</span>
76
+ <div class="bi-query-compose__actions">
77
+ <AiModelSelector
78
+ :model-providers="modelProviders"
79
+ :provider-id="selectedModel.provider_id"
80
+ :model-name="selectedModel.model_name"
81
+ :disabled="loading || loadingModels"
82
+ @change="handleModelChange"
83
+ />
84
+ <el-tooltip :content="t(recording ? 'bi.query.stop_voice_input' : 'bi.query.voice_input')" placement="top">
85
+ <button
86
+ class="bi-query-compose__button"
87
+ :class="{ 'is-active': recording }"
88
+ type="button"
89
+ :disabled="loading"
90
+ :aria-pressed="recording"
91
+ :aria-label="t(recording ? 'bi.query.stop_voice_input' : 'bi.query.voice_input')"
92
+ @click="recording ? stopRecord() : startRecord()"
93
+ >
94
+ <el-icon :class="{ 'is-loading': recording }"
95
+ ><component :is="recording ? icons.Loading : icons.Microphone"
96
+ /></el-icon>
97
+ </button>
98
+ </el-tooltip>
99
+ <el-tooltip :content="t('bi.query.ask')" placement="top">
100
+ <button
101
+ class="bi-query-compose__button bi-query-compose__button--send"
102
+ type="button"
103
+ :disabled="loading || loadingModels || recording || !question.trim()"
104
+ :aria-label="t('bi.query.ask')"
105
+ @click="ask"
106
+ >
107
+ <el-icon :class="{ 'is-loading': loading }"
108
+ ><component :is="loading ? icons.Loading : icons.Promotion"
109
+ /></el-icon>
110
+ </button>
111
+ </el-tooltip>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </section>
117
+ <aside class="bi-query-preview">
118
+ <header>
119
+ <h3>{{ t("bi.card.preview") }}</h3>
120
+ </header>
121
+ <BiDataCard
122
+ v-if="response?.result && response.query"
123
+ :card="previewCard"
124
+ :preview-result="response.result"
125
+ :source-name="sources.find(item => item.code === previewCard.source_code)?.name || previewCard.source_code"
126
+ ><template #footer
127
+ ><el-button link type="primary" @click="cardDialog = true">{{ t("bi.card.edit") }}</el-button
128
+ ><el-button type="primary" @click="cardDialog = true">{{ t("bi.card.save_result") }}</el-button></template
129
+ ></BiDataCard
130
+ ><el-empty v-else :description="t('bi.card.preview_empty')" />
131
+ </aside>
132
+ </div>
133
+ <BiCardFormDialog
134
+ v-model="cardDialog"
135
+ :sources="sources"
136
+ :initial-query="response?.query"
137
+ :initial-result="response?.result"
138
+ :initial-title="response?.title"
139
+ :initial-chart-type="previewCard.chart_type"
140
+ @saved="loadHistory"
141
+ />
142
+ </main>
143
+ </template>
144
+ <script setup lang="ts">
145
+ import { computed, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from "vue";
146
+ import { useRoute, useRouter } from "vue-router";
147
+ import { t, useSpeechRecognition } from "@liujitcn/kratos-admin-core";
148
+ import AiModelSelector from "@liujitcn/kratos-admin-system/components/AiModelSelector/index.vue";
149
+ import {
150
+ resolveModelSelection,
151
+ saveModelSelection
152
+ } from "@liujitcn/kratos-admin-system/components/AiModelSelector/modelSelection";
153
+ import type { AiModelSelection } from "@liujitcn/kratos-admin-system/components/AiModelSelector/modelSelection";
154
+ import { defAiModelService } from "@liujitcn/kratos-admin-system/api/base/v1/ai_provider";
155
+ import type { AiProviderModelOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_provider";
156
+ import SessionPanel from "@liujitcn/kratos-admin-system/components/AiSessionPanel/index.vue";
157
+ import BiDataCard from "../../components/BiDataCard.vue";
158
+ import BiCardFormDialog from "../../components/BiCardFormDialog.vue";
159
+ import { normalizeBiQueryResult, normalizeBiQuerySpec } from "../../bi-response";
160
+ import { defBiQueryService } from "../../api/bi/admin/v1/bi_query";
161
+ import { defBiQueryRecordService } from "../../api/bi/admin/v1/bi_query_record";
162
+ import type { BiQueryRecord } from "../../rpc/bi/admin/v1/bi_query_record";
163
+ import type { AskBiQueryResponse, BiQuerySpec, BiSourceDescriptor } from "../../rpc/bi/admin/v1/bi_query";
164
+ import type { BiCard } from "../../rpc/bi/admin/v1/bi_card";
165
+ defineOptions({ name: "BiQuery", inheritAttrs: false });
166
+ const route = useRoute(),
167
+ router = useRouter();
168
+ const icons = { Promotion, Microphone, Loading };
169
+ const question = ref(""),
170
+ submittedQuestion = ref(""),
171
+ loading = ref(false),
172
+ error = ref(""),
173
+ sources = ref<BiSourceDescriptor[]>([]),
174
+ response = ref<AskBiQueryResponse>(),
175
+ records = ref<BiQueryRecord[]>([]),
176
+ keyword = ref(""),
177
+ activeRecord = ref(""),
178
+ historyCollapsed = ref(false),
179
+ cardDialog = ref(false),
180
+ showTable = ref(false);
181
+ const modelProviders = ref<AiProviderModelOption[]>([]),
182
+ selectedModel = ref<AiModelSelection>({ provider_id: 0, model_name: "" }),
183
+ loadingModels = ref(true);
184
+ let request = 0,
185
+ historyRequest = 0,
186
+ voiceBaseText = "";
187
+ let searchTimer: ReturnType<typeof setTimeout> | undefined;
188
+ const sessions = computed(() =>
189
+ records.value.map(item => ({
190
+ id: String(item.id),
191
+ title: item.question,
192
+ summary: `${sources.value.find(source => source.code === item.source_code)?.name || item.source_code || ""} · ${item.created_at || ""}`
193
+ }))
194
+ );
195
+ const {
196
+ loading: recording,
197
+ start: startRecord,
198
+ stop: stopRecord
199
+ } = useSpeechRecognition({
200
+ onStart: () => {
201
+ voiceBaseText = question.value;
202
+ },
203
+ onResult: text => {
204
+ question.value = [voiceBaseText, text].filter(Boolean).join(" ");
205
+ },
206
+ onError: cause => {
207
+ const key =
208
+ cause.code === -1
209
+ ? "bi.query.voice_unsupported"
210
+ : ["not-allowed", "service-not-allowed", "permission-denied"].includes(cause.error ?? "")
211
+ ? "bi.query.microphone_denied"
212
+ : "bi.query.voice_failed";
213
+ ElMessage.warning(t(key));
214
+ }
215
+ });
216
+
217
+ const previewCard = computed<BiCard>(() => ({
218
+ id: 0,
219
+ tenant_id: 0,
220
+ project_id: 0,
221
+ owner_id: 0,
222
+ name: response.value?.title || "BI",
223
+ source_code: response.value?.query?.source_code || "",
224
+ query: response.value?.query,
225
+ chart_type: response.value?.query?.dimensions.length ? response.value.chart_type || "bar" : "metric",
226
+ chart_json: "",
227
+ query_record_id: response.value?.result?.query_record_id || 0,
228
+ refresh_seconds: 0,
229
+ is_shared: false,
230
+ updated_at: "",
231
+ category: "mine",
232
+ can_edit: true
233
+ }));
234
+ const tableRows = computed(() =>
235
+ (response.value?.result?.rows || []).map(row => Object.fromEntries(row.values.map((value, index) => [String(index), value])))
236
+ );
237
+ /** Enter发送、Shift+Enter换行,输入法选字不提交。 */
238
+ function handleInputEnter(event: KeyboardEvent) {
239
+ if (event.shiftKey || event.isComposing || event.keyCode === 229) return;
240
+ event.preventDefault();
241
+ void ask();
242
+ }
243
+ /** 发起真实语义问数并关联服务端生成的结果记录。 */
244
+ async function ask() {
245
+ if (!question.value.trim() || loading.value || loadingModels.value || recording.value) return;
246
+ const current = ++request;
247
+ submittedQuestion.value = question.value;
248
+ question.value = "";
249
+ response.value = undefined;
250
+ error.value = "";
251
+ loading.value = true;
252
+ showTable.value = false;
253
+ try {
254
+ const result = await defBiQueryService.AskBiQuery({ question: submittedQuestion.value, ...selectedModel.value });
255
+ if (current !== request) return;
256
+ response.value = result;
257
+ activeRecord.value = String(result.result?.query_record_id || "");
258
+ await loadHistory();
259
+ } catch (cause) {
260
+ if (current === request) error.value = resolveAskError(cause);
261
+ } finally {
262
+ if (current === request) loading.value = false;
263
+ }
264
+ }
265
+ /** 左侧历史仅展示当前用户的自然语言问数,搜索仍由服务端过滤。 */
266
+ async function loadHistory() {
267
+ const current = ++historyRequest;
268
+ try {
269
+ const result = await defBiQueryRecordService.PageBiQuery({
270
+ question: keyword.value,
271
+ status: undefined,
272
+ source_code: "",
273
+ page_num: 1,
274
+ page_size: 100
275
+ });
276
+ if (current === historyRequest) records.value = result.queries || [];
277
+ } catch (cause) {
278
+ ElMessage.error(resolveAskError(cause));
279
+ }
280
+ }
281
+ /** 恢复历史问题和完整类型化结果,切换记录时丢弃旧请求。 */
282
+ async function selectRecord(id: number) {
283
+ if (!Number.isSafeInteger(id) || id <= 0) return;
284
+ const current = ++request;
285
+ stopRecord();
286
+ loading.value = true;
287
+ error.value = "";
288
+ response.value = undefined;
289
+ activeRecord.value = String(id);
290
+ showTable.value = false;
291
+ try {
292
+ const item = await defBiQueryRecordService.GetBiQuery({ id });
293
+ if (current !== request) return;
294
+ submittedQuestion.value = item.question;
295
+ if (!item.result) {
296
+ error.value = item.error_msg || t("bi.query.error");
297
+ return;
298
+ }
299
+ const spec = normalizeBiQuerySpec(JSON.parse(item.query_json) as BiQuerySpec);
300
+ response.value = {
301
+ query: spec,
302
+ result: normalizeBiQueryResult(item.result),
303
+ title: item.question || t("bi.query.result"),
304
+ chart_type: spec.dimensions.length ? "bar" : "metric"
305
+ };
306
+ } catch (cause) {
307
+ if (current === request) error.value = resolveAskError(cause);
308
+ } finally {
309
+ if (current === request) loading.value = false;
310
+ }
311
+ }
312
+ /** 新问题只清空当前输入与预览,不删除已有记录。 */
313
+ function newQuestion() {
314
+ request++;
315
+ stopRecord();
316
+ loading.value = false;
317
+ submittedQuestion.value = "";
318
+ question.value = "";
319
+ response.value = undefined;
320
+ activeRecord.value = "";
321
+ error.value = "";
322
+ }
323
+ /** 加载与 AI 助手一致的模型选项,并恢复仍可用的本地选择。 */
324
+ async function loadModels() {
325
+ loadingModels.value = true;
326
+ try {
327
+ const result = await defAiModelService.ListAiProviderModelOptions({});
328
+ modelProviders.value = result.providers ?? [];
329
+ selectedModel.value = resolveModelSelection(modelProviders.value);
330
+ } catch (cause) {
331
+ error.value = resolveAskError(cause);
332
+ } finally {
333
+ loadingModels.value = false;
334
+ }
335
+ }
336
+
337
+ /** 更新本次问数的模型,同时保存为 AI 助手和 BI 共用的本地选择。 */
338
+ function handleModelChange(providerId: number, modelName: string) {
339
+ selectedModel.value = { provider_id: providerId, model_name: modelName };
340
+ saveModelSelection(selectedModel.value);
341
+ }
342
+
343
+ /** 优先展示服务端返回的业务错误信息,请求失败时回退到通用错误文案。 */
344
+ function resolveAskError(cause: unknown) {
345
+ const responseData = (cause as { response?: { data?: { message?: string } } })?.response?.data;
346
+ if (typeof responseData?.message === "string" && responseData.message) return responseData.message;
347
+ return cause instanceof Error ? cause.message : t("bi.query.error");
348
+ }
349
+
350
+ watch(keyword, () => {
351
+ clearTimeout(searchTimer);
352
+ searchTimer = setTimeout(() => void loadHistory(), 250);
353
+ });
354
+ watch(
355
+ () => route.query.record_id,
356
+ value => {
357
+ if (value) void selectRecord(Number(value));
358
+ },
359
+ { immediate: true }
360
+ );
361
+ onMounted(async () => {
362
+ await Promise.all([loadHistory(), loadModels()]);
363
+ try {
364
+ sources.value = (await defBiQueryService.ListBiSources({})).sources;
365
+ } catch (cause) {
366
+ error.value = resolveAskError(cause);
367
+ }
368
+ });
369
+ onDeactivated(() => {
370
+ request++;
371
+ loading.value = false;
372
+ stopRecord();
373
+ });
374
+ onBeforeUnmount(() => {
375
+ request++;
376
+ historyRequest++;
377
+ clearTimeout(searchTimer);
378
+ stopRecord();
379
+ });
380
+ </script>
381
+ <style scoped>
382
+ .bi-query-page {
383
+ padding: 20px 22px;
384
+ background: var(--admin-page-card-bg);
385
+ border: 1px solid var(--el-border-color-lighter);
386
+ border-radius: var(--admin-page-radius);
387
+ min-width: 0;
388
+ }
389
+ .bi-query-page__header {
390
+ display: flex;
391
+ justify-content: space-between;
392
+ align-items: center;
393
+ gap: 16px;
394
+ flex-wrap: wrap;
395
+ margin-bottom: 20px;
396
+ }
397
+ .bi-query-page__header h2 {
398
+ margin: 0;
399
+ font-size: 22px;
400
+ font-weight: 500;
401
+ }
402
+ .bi-query-page__header p {
403
+ margin: 8px 0 0;
404
+ font-size: 13px;
405
+ color: var(--el-text-color-secondary);
406
+ }
407
+ .bi-query-page__header > div:last-child {
408
+ display: flex;
409
+ gap: 8px;
410
+ }
411
+ .bi-query-layout {
412
+ display: grid;
413
+ grid-template-columns: 260px minmax(0, 7fr) minmax(0, 3fr);
414
+ height: calc(100vh - 250px);
415
+ min-height: 580px;
416
+ border: 1px solid var(--el-border-color-lighter);
417
+ border-radius: var(--admin-page-radius);
418
+ }
419
+ .bi-query-layout.is-history-collapsed {
420
+ grid-template-columns: minmax(0, 7fr) minmax(0, 3fr);
421
+ }
422
+ .bi-query-chat {
423
+ display: flex;
424
+ min-width: 0;
425
+ min-height: 0;
426
+ flex-direction: column;
427
+ padding: 18px;
428
+ }
429
+ .bi-query-messages {
430
+ flex: 1;
431
+ min-height: 0;
432
+ overflow: auto;
433
+ font-size: 14px;
434
+ }
435
+ .bi-query-bubble {
436
+ padding: 12px 15px;
437
+ border-radius: var(--admin-page-radius);
438
+ background: var(--el-fill-color-light);
439
+ margin-bottom: 22px;
440
+ }
441
+ .bi-query-assistant {
442
+ font-size: 12px;
443
+ color: var(--el-text-color-secondary);
444
+ }
445
+ .bi-query-messages h3 {
446
+ font-size: 16px;
447
+ font-weight: 500;
448
+ }
449
+ .bi-query-facts {
450
+ border-left: 2px solid var(--el-border-color);
451
+ padding-left: 12px;
452
+ color: var(--el-text-color-secondary);
453
+ font-size: 13px;
454
+ margin: 16px 0;
455
+ }
456
+ .bi-query-facts p {
457
+ margin: 7px 0;
458
+ }
459
+ .bi-query-preview {
460
+ padding: 18px 14px;
461
+ border-left: 1px solid var(--el-border-color-lighter);
462
+ background: var(--el-fill-color-lighter);
463
+ overflow: auto;
464
+ min-width: 0;
465
+ }
466
+ .bi-query-preview h3 {
467
+ font-size: 14px;
468
+ font-weight: 500;
469
+ margin: 0 0 18px;
470
+ }
471
+ .bi-query-compose {
472
+ flex-shrink: 0;
473
+ margin-top: auto;
474
+ padding-top: 24px;
475
+ }
476
+ .bi-query-compose__input {
477
+ padding: 8px 10px 10px;
478
+ background: var(--admin-page-card-bg);
479
+ border: 1px solid var(--el-border-color);
480
+ border-radius: var(--admin-page-radius);
481
+ transition: border-color 0.2s ease;
482
+ }
483
+ .bi-query-compose__input:focus-within {
484
+ border-color: var(--el-color-primary);
485
+ }
486
+ .bi-query-compose__input :deep(.el-textarea__inner) {
487
+ min-height: 64px;
488
+ padding: 8px 10px;
489
+ font-size: 14px;
490
+ line-height: 22px;
491
+ resize: none;
492
+ background: transparent;
493
+ box-shadow: none;
494
+ }
495
+ .bi-query-compose__footer {
496
+ display: flex;
497
+ align-items: center;
498
+ justify-content: space-between;
499
+ gap: 12px;
500
+ margin-top: 10px;
501
+ color: var(--el-text-color-secondary);
502
+ font-size: 12px;
503
+ }
504
+ .bi-query-compose__actions {
505
+ display: inline-flex;
506
+ flex: 0 1 auto;
507
+ min-width: 0;
508
+ align-items: center;
509
+ gap: 8px;
510
+ }
511
+ .bi-query-compose__footer > span {
512
+ flex: 1;
513
+ min-width: 0;
514
+ overflow: hidden;
515
+ text-overflow: ellipsis;
516
+ white-space: nowrap;
517
+ }
518
+ .bi-query-compose__button {
519
+ display: inline-flex;
520
+ flex-shrink: 0;
521
+ align-items: center;
522
+ justify-content: center;
523
+ width: 34px;
524
+ height: 34px;
525
+ color: var(--admin-page-text-secondary);
526
+ cursor: pointer;
527
+ background: var(--admin-page-card-bg);
528
+ border: 1px solid var(--el-border-color);
529
+ border-radius: var(--admin-page-radius);
530
+ transition:
531
+ color 0.2s ease,
532
+ border-color 0.2s ease,
533
+ background-color 0.2s ease,
534
+ opacity 0.2s ease;
535
+ }
536
+ .bi-query-compose__button:hover,
537
+ .bi-query-compose__button.is-active,
538
+ .bi-query-compose__button--send {
539
+ color: var(--el-color-primary);
540
+ background: var(--el-color-primary-light-9);
541
+ border-color: var(--el-color-primary-light-5);
542
+ }
543
+ .bi-query-compose__button:disabled {
544
+ cursor: not-allowed;
545
+ opacity: 0.55;
546
+ }
547
+
548
+ @media (max-width: 1180px) {
549
+ .bi-query-layout {
550
+ grid-template-columns: 240px minmax(0, 1fr);
551
+ height: auto;
552
+ }
553
+ .bi-query-layout.is-history-collapsed {
554
+ grid-template-columns: 1fr;
555
+ }
556
+ .bi-query-layout :deep(.agent-session-panel) {
557
+ display: flex;
558
+ max-height: 560px;
559
+ }
560
+ .bi-query-chat {
561
+ min-height: 500px;
562
+ }
563
+ .bi-query-preview {
564
+ grid-column: 1/-1;
565
+ border-left: 0;
566
+ border-top: 1px solid var(--el-border-color-light);
567
+ }
568
+ }
569
+ @media (max-width: 650px) {
570
+ .bi-query-page {
571
+ padding: 14px;
572
+ }
573
+ .bi-query-layout {
574
+ grid-template-columns: 1fr;
575
+ }
576
+ .bi-query-layout :deep(.agent-session-panel) {
577
+ max-height: 220px;
578
+ border-right: 0;
579
+ border-bottom: 1px solid var(--el-border-color-light);
580
+ }
581
+ .bi-query-chat {
582
+ padding: 14px;
583
+ min-height: 450px;
584
+ }
585
+ .bi-query-compose__actions {
586
+ min-width: 0;
587
+ flex-shrink: 1;
588
+ }
589
+ .bi-query-compose__button {
590
+ flex-shrink: 0;
591
+ }
592
+ }
593
+ </style>