vuepress-theme-uniapp-official 1.6.4 → 1.6.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.
@@ -1,11 +1,9 @@
1
1
  <template>
2
2
  <div class="chat-wrapper">
3
- <header class="chat-header">
4
- <div class="title">DCloud 文档 AI 助手</div>
5
- <SelectPlatform :currentCategory="currentCategory" :platforms="aiPlatforms" @change="platformChange" />
6
- </header>
7
-
8
- <main ref="msgList" class="chat-messages">
3
+ <main ref="msgList" class="chat-messages" :style="{ 'padding-bottom': msgListPaddingBottom + 'px' }">
4
+ <div class="title">
5
+ <span>DCloud 文档 AI 助手</span>
6
+ </div>
9
7
  <transition-group name="fade-up" tag="div">
10
8
 
11
9
  <div v-for="m in messages" :key="m.id" :class="['msg', m.role]">
@@ -16,12 +14,8 @@
16
14
  <span class="time">{{ m.time }}</span>
17
15
 
18
16
  <!-- <div class="actions" v-if="m.role === 'assistant'">
19
- <span class="icon" :class="{ active: m.like === 1 }" @click="setLike(m, 1)">
20
- 👍
21
- </span>
22
- <span class="icon" :class="{ active: m.like === -1 }" @click="setLike(m, -1)">
23
- 👎
24
- </span>
17
+ <LikeButton :active="!!m.like" type="like" @click.stop="like(m)" />
18
+ <LikeButton :active="!!m.dislike" type="dislike" @click.stop="dislike(m)" />
25
19
  </div> -->
26
20
  </div>
27
21
 
@@ -30,27 +24,46 @@
30
24
 
31
25
  <Skeleton style="width: 60%" v-if="sending" />
32
26
  </main>
33
-
34
- <footer class="chat-input-bar">
35
- <div class="input-container">
36
- <textarea ref="input" v-model="inputText" class="chat-input" rows="1" placeholder="请输入内容…" @input="autoGrow"
37
- @keydown.enter.exact.prevent="send" inputmode="text" enterkeyhint="newline"></textarea>
38
-
39
- <button class="send-btn" :disabled="sending" @click="send">
40
- 发送
41
- </button>
27
+ <footer ref="footer" class="chat-input-bar"
28
+ :style="{ top: !hasMessage ? '50%' : 'auto', bottom: !hasMessage ? 'auto' : '0' }">
29
+ <div class="input-container"
30
+ :class="{ 'not-support-backdrop-filter': notSupportBackdrop, 'error-border': inputError }">
31
+ <form>
32
+ <textarea ref="input" v-model="inputText" name="input-container_input" class="chat-input" required rows="1"
33
+ :minlength="MAX_AI_ANSWER_LENGTH" placeholder="请输入内容…" @input="answerInput"
34
+ @keydown.enter.exact.prevent="send" inputmode="text" enterkeyhint="newline"></textarea>
35
+ </form>
36
+
37
+ <div class="footer-toolbar" @click.self="inputBottomClick">
38
+ <div class="footer-toolbar_left">
39
+ <SelectPlatform :currentCategory="currentCategory" :platforms="aiPlatforms" @change="platformChange" />
40
+ </div>
41
+ <div class="footer-toolbar_right">
42
+ <span v-if="inputError" class="error-tips">
43
+ 输入内容不少于 {{ MAX_AI_ANSWER_LENGTH }} 个字符
44
+ </span>
45
+ <span class="tips">
46
+ ↵ 发送 / shift + ↵ 换行
47
+ </span>
48
+ <button class="send-btn" :disabled="sending" @click="send">
49
+ 发送
50
+ </button>
51
+ </div>
52
+ </div>
42
53
  </div>
43
54
  </footer>
44
55
  </div>
45
56
  </template>
46
57
 
47
58
  <script setup>
48
- import { ref, nextTick, watchEffect, onMounted } from 'vue'
49
- import { renderMarkdown } from "./markdown-loader";
50
- import SelectPlatform from './SelectPlatform.vue';
51
- import { ajax } from '../../utils/postDcloudServer';
59
+ import { ref, nextTick, watchEffect, onMounted, computed } from 'vue'
52
60
  import searchPageConfig from '@theme-config/searchPage';
53
- import Skeleton from '../Skeleton';
61
+ import { renderMarkdown } from "./markdown-loader";
62
+ import { MAX_AI_ANSWER_LENGTH } from '../constants';
63
+ import { ajax } from '../utils/postDcloudServer';
64
+ import SelectPlatform from '../components/SelectPlatform.vue';
65
+ import LikeButton from '../components/LikeButton.vue';
66
+ import Skeleton from '../components/Skeleton.vue';
54
67
 
55
68
  const { aiPlatforms = [], aiChatForDocSearch = 'https://ai-assist-api.dcloud.net.cn/tbox/chatForDocSearch' } = searchPageConfig;
56
69
 
@@ -74,6 +87,18 @@ const sendPlatform = ref(props.currentCategory)
74
87
 
75
88
  const msgList = ref(null)
76
89
  const input = ref(null)
90
+ const footer = ref(null)
91
+
92
+ const hasMessage = computed(() => messages.value.length > 0)
93
+ const inputError = computed(() => {
94
+ return inputText.value.length > 0 && inputText.value.trim().length < MAX_AI_ANSWER_LENGTH;
95
+ })
96
+
97
+ const notSupportBackdrop = ref(false);
98
+ if (!(CSS && typeof CSS.supports === 'function' && CSS.supports('backdrop-filter', 'blur(10px)'))) {
99
+ // 不支持 backdrop-filter,则使用更简单的样式
100
+ notSupportBackdrop.value = true;
101
+ }
77
102
 
78
103
  function setSessionMessages(value) {
79
104
  sessionStorage.setItem('__UNIDOC_MESSAGES__', JSON.stringify(value));
@@ -97,6 +122,14 @@ watchEffect(() => {
97
122
  }
98
123
  })
99
124
 
125
+ const msgListPaddingBottom = ref(120);
126
+ function changeMsgListPadding() {
127
+ nextTick(() => {
128
+ const footerHeight = footer.value ? footer.value.offsetHeight : 0;
129
+ msgListPaddingBottom.value = footerHeight + 20; // 20px 额外空间
130
+ });
131
+ }
132
+
100
133
  function formatTime() {
101
134
  const d = new Date()
102
135
  return `${d.getHours().toString().padStart(2, '0')}:${d
@@ -112,8 +145,20 @@ function autoGrow() {
112
145
  if (inputText.value.length === 0) {
113
146
  return
114
147
  }
115
- el.style.height = el.scrollHeight + 'px'
116
- el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' })
148
+ // TODO +2 是解决在输入第一行时有滚动条的问题,需进一步优化
149
+ el.style.height = el.scrollHeight + 2 + 'px'
150
+ }
151
+
152
+ function answerInput() {
153
+ autoGrow()
154
+ changeMsgListPadding()
155
+ }
156
+
157
+ function inputBottomClick() {
158
+ nextTick(() => {
159
+ input.value.focus()
160
+ input.value.scrollTo({ top: input.value.scrollHeight, behavior: 'smooth' })
161
+ })
117
162
  }
118
163
 
119
164
  function scrollToBottom() {
@@ -129,10 +174,6 @@ async function renderHTML(raw) {
129
174
  return rendered
130
175
  }
131
176
 
132
- function setLike(msg, val) {
133
- msg.like = msg.like === val ? 0 : val
134
- }
135
-
136
177
  function fakeAITyping(text, msgObj) {
137
178
  return new Promise(resolve => {
138
179
  let i = 0
@@ -168,11 +209,13 @@ function getChatHistory() {
168
209
  }
169
210
 
170
211
  async function send() {
171
- if (!inputText.value.trim() || sending.value) return
172
-
173
212
  const userText = inputText.value.trim()
213
+
214
+ if (!userText || sending.value) return
215
+ if (inputError.value) return
216
+
174
217
  inputText.value = ''
175
- autoGrow()
218
+ answerInput()
176
219
 
177
220
  // 用户消息
178
221
  messages.value.push({
@@ -181,7 +224,6 @@ async function send() {
181
224
  raw: userText,
182
225
  rendered: await renderHTML(userText),
183
226
  time: formatTime(),
184
- like: 0
185
227
  })
186
228
  scrollToBottom()
187
229
 
@@ -212,7 +254,8 @@ async function send() {
212
254
  rendered: await renderHTML(fakeReply),
213
255
  time: formatTime(),
214
256
  isTyping: false,
215
- like: 0
257
+ like: false,
258
+ dislike: false
216
259
  }
217
260
 
218
261
  messages.value.push(aiMsg)
@@ -224,33 +267,60 @@ async function send() {
224
267
  scrollToBottom()
225
268
  }
226
269
 
270
+ function like(m) {
271
+ console.log('like');
272
+ m.like = !m.like;
273
+ if (m.like) {
274
+ m.dislike = false;
275
+ }
276
+ }
277
+
278
+ function dislike(m) {
279
+ console.log('dislike');
280
+ m.dislike = !m.dislike;
281
+ if (m.dislike) {
282
+ m.like = false;
283
+ }
284
+ }
285
+
227
286
  window.addEventListener('resize', scrollToBottom)
228
287
  </script>
229
288
 
230
289
  <style lang="stylus">
290
+ @import '../ai-answer-style-reset.styl'
291
+
292
+ .__backdrop-filter__
293
+ backdrop-filter blur(15px)
294
+ .__not-support-backdrop-filter__
295
+ background-color: #fff
296
+
231
297
  .chat-wrapper
232
298
  display flex
299
+ position relative
233
300
  flex-direction column
234
301
  height calc(100vh - 56px - 40px)
235
302
  background #f9fafb
236
303
  overflow hidden
237
304
 
238
- .chat-header
305
+ /* .chat-header
239
306
  height 56px
240
307
  display flex
241
308
  align-items center
242
309
  padding 0 16px
243
310
  background white
244
- border-bottom 1px solid #eee
311
+ border-bottom 1px solid #eee */
245
312
 
246
313
  .title
247
- font-size 18px
314
+ margin 30px auto
315
+ text-align center
316
+ font-size 30px
248
317
  font-weight 600
318
+ color #cecece
249
319
 
250
320
  .chat-messages
251
321
  flex 1
252
322
  overflow-y auto
253
- padding 16px
323
+ padding 16px 16px 120px
254
324
  box-sizing border-box
255
325
 
256
326
  .msg
@@ -284,27 +354,13 @@ window.addEventListener('resize', scrollToBottom)
284
354
  .bubble
285
355
  display inline-block
286
356
  max-width 50%
287
- padding 10px 14px
357
+ padding 0 14px
288
358
  border-radius 14px
289
359
  line-height 1.5
290
360
  font-size 15px
291
361
  word-break break-word
292
362
  box-shadow 0 1px 3px rgba(0,0,0,0.08)
293
- pre
294
- margin: 0
295
- padding: 5px
296
- border-radius 10px
297
- & + pre
298
- margin-top 8px
299
- pre, code
300
- white-space: pre-wrap; /* 允许换行 */
301
- word-wrap: break-word; /* 允许长行断开 */
302
- word-break: break-word;
303
- h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote
304
- margin: 0
305
- padding: 0
306
- ul, ol
307
- list-style: none
363
+ @extend .ai-answer-style-reset
308
364
 
309
365
  .meta
310
366
  font-size 12px
@@ -314,54 +370,72 @@ window.addEventListener('resize', scrollToBottom)
314
370
  align-items center
315
371
 
316
372
  .actions
373
+ margin-left 20px
317
374
  display flex
318
- gap 10px
319
-
320
- .icon
321
- cursor pointer
322
- opacity .5
323
- transition .2s
324
- &.active
325
- opacity 1
326
- color $accentColor
327
375
 
328
376
  .chat-input-bar
329
377
  padding 10px
330
- background white
331
- border-top 1px solid #eee
378
+ position absolute
379
+ left 10px
380
+ right 10px
381
+ bottom 0
332
382
  display flex
333
383
  align-items flex-end
334
384
  justify-content center
335
385
 
336
386
  .input-container
337
- display flex
338
- align-items flex-end
339
387
  width 100%
340
- background #fff
341
- border 1px solid rgba(0,0,0,.1)
388
+ border 1px solid rgba(0,0,0,.2)
342
389
  border-radius 20px
343
390
  padding 8px 12px
344
- box-shadow 0 2px 8px rgba(0,0,0,.06)
391
+ box-shadow 0 2px 10px rgba(0,0,0,.2)
345
392
  transition border-color .2s
393
+ @extend .__backdrop-filter__
346
394
  &:focus-within
347
395
  border-color $accentColor
348
396
  box-shadow 0 0 0 2px rgba($accentColor, .2)
397
+ &.error-border, &.error-border:focus-within
398
+ border-color: #e00 !important;
399
+ box-shadow 0 0 0 2px rgba(#e00, .2)
400
+ &.not-support-backdrop-filter
401
+ @extend .__not-support-backdrop-filter__
402
+
403
+ .footer-toolbar
404
+ display flex
405
+ align-items center
406
+ justify-content space-between
407
+ .footer-toolbar_left, .footer-toolbar_right
408
+ display flex
409
+ align-items center
410
+ justify-content center
411
+ .error-tips
412
+ margin-right 12px
413
+ font-size 12px
414
+ color #e00
415
+ .tips
416
+ font-size 12px
417
+ color #888
349
418
 
350
419
  .chat-input
351
- flex 1
420
+ width 100%
421
+ box-sizing border-box
352
422
  resize none
353
423
  outline none
354
424
  border none
355
425
  padding 8px 10px
356
- line-height 1.5
426
+ line-height 1.2
357
427
  max-height 160px
358
428
  overflow-y auto
359
429
  font-size 15px
360
430
  transition: height 0.2s;
431
+ background-color transparent
432
+ -webkit-user-select: auto;
433
+ -webkit-touch-callout: auto;
434
+ word-break: break-word;
361
435
 
362
436
  .send-btn
363
437
  margin-left 8px
364
- padding 8px 14px
438
+ padding 6px 10px
365
439
  border none
366
440
  border-radius 8px
367
441
  background $accentColor
@@ -1,10 +1,10 @@
1
1
  // markdown-loader.js
2
2
  let markedInstance = null;
3
3
 
4
- // `1.` 转义为 `1、`,防止 marked 解析失败
4
+ // 防止 ``` 和上一行之前没有空行 marked 解析失败
5
5
  function escapeMD(str) {
6
6
  return str
7
- .replace(/(\s*\b)(\d+)\./g, '$1$2、')
7
+ .replace(/\n(```)/g, '\n\n$1')
8
8
  }
9
9
 
10
10
  function getLangCodeFromExtension(extension) {
@@ -0,0 +1,17 @@
1
+ .ai-answer-style-reset
2
+ pre
3
+ margin 0
4
+ padding 6px 8px
5
+ border-radius 10px
6
+ background #f6f8fa
7
+
8
+ & + pre
9
+ margin-top 8px
10
+
11
+ pre, code
12
+ white-space pre-wrap
13
+ word-break break-word
14
+ word-break break-word
15
+
16
+ p+p
17
+ margin-top 8px
@@ -0,0 +1,128 @@
1
+ <template>
2
+ <div class="ai-answer-card">
3
+ <div class="ai-answer-header">
4
+ <span class="ai-answer-icon">🤖</span>
5
+ <span class="ai-answer-title">{{ item.title }}</span>
6
+ </div>
7
+
8
+ <div v-if="hasMessage" class="ai-answer-msg" v-html="item.msg" />
9
+ <Skeleton v-else />
10
+
11
+ <div class="ai-answer-footer">
12
+ <div class="ai-answer-footer_left">
13
+ 本回答由 AI 生成,可能已过期、失效或不适用于当前情形,仅供参考
14
+ </div>
15
+
16
+ <!-- <div v-show="hasMessage" class="ai-answer-footer_right">
17
+ <LikeButton :active="status.like" type="like" @click.stop="like" />
18
+ <LikeButton :active="status.dislike" type="dislike" @click.stop="dislike" />
19
+ </div> -->
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup>
25
+ import searchPageConfig from '@theme-config/searchPage';
26
+ import Skeleton from './Skeleton.vue';
27
+ import LikeButton from './LikeButton.vue';
28
+ import { computed, reactive } from 'vue';
29
+
30
+ const {
31
+ aiChatForDocSearch = 'https://ai-assist-api.dcloud.net.cn/tbox/chatForDocSearch'
32
+ } = searchPageConfig;
33
+
34
+ const props = defineProps({
35
+ item: {
36
+ type: Object,
37
+ required: true
38
+ }
39
+ })
40
+
41
+ const hasMessage = computed(() => {
42
+ return props.item.msg && props.item.msg.length > 0;
43
+ })
44
+
45
+ const status = reactive({
46
+ like: false,
47
+ dislike: false
48
+ })
49
+
50
+ function like() {
51
+ console.log('like');
52
+ status.like = !status.like;
53
+ if (status.like) {
54
+ status.dislike = false;
55
+ }
56
+ }
57
+
58
+ function dislike() {
59
+ console.log('dislike');
60
+ status.dislike = !status.dislike;
61
+ if (status.dislike) {
62
+ status.like = false;
63
+ }
64
+ }
65
+ </script>
66
+
67
+ <style lang="stylus">
68
+ @import '../ai-answer-style-reset.styl'
69
+
70
+ .ai-answer-card
71
+ padding 14px 16px
72
+ margin-top 12px
73
+ background #fff
74
+ border-radius 12px
75
+ border 1px solid rgba(0,0,0,0.06)
76
+ box-shadow 0 2px 8px rgba(0,0,0,0.04)
77
+ transition box-shadow .25s //, transform .2s
78
+ cursor pointer
79
+
80
+ &:hover
81
+ box-shadow 0 4px 14px rgba(0,0,0,0.08)
82
+ // transform translateY(-1px)
83
+
84
+ .ai-answer-header
85
+ display flex
86
+ align-items center
87
+ margin-bottom 8px
88
+
89
+ .ai-answer-icon
90
+ font-size 18px
91
+ margin-right 6px
92
+
93
+ .ai-answer-title
94
+ font-weight 600
95
+ font-size 15px
96
+ color #333
97
+
98
+ .ai-answer-msg
99
+ font-size 14px
100
+ line-height 1.6
101
+ color #444
102
+ word-break break-word
103
+ animation fadeIn .25s ease
104
+
105
+ /* --- reset 内容区 --- */
106
+ @extend .ai-answer-style-reset
107
+
108
+ @keyframes fadeIn
109
+ from
110
+ opacity 0
111
+ transform translateY(4px)
112
+ to
113
+ opacity 1
114
+ transform translateY(0)
115
+
116
+ .ai-answer-footer
117
+ margin-top 12px
118
+ padding-top 10px
119
+ border-top 1px solid rgba(0,0,0,.06)
120
+ display flex
121
+ align-items center
122
+ justify-content space-between
123
+ font-size 12px
124
+ color #888
125
+
126
+ .ai-answer-footer_right
127
+ display flex
128
+ </style>
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <button class="ai-feedback-btn" :class="{ active: props.active }" @click="emit('click', $event)">
3
+ <svg viewBox="0 0 24 24" class="icon">
4
+ <path v-if="isLike"
5
+ d="M2 10h4v12H2V10zm6.31 0L11 4.06a1 1 0 0 1 1-.56h.09c.87 0 1.6.63 1.74 1.49L14.8 10H21a1 1 0 0 1 1 1v1.09c0 .27-.06.53-.17.77l-3.14 6.58A2 2 0 0 1 16.85 20H10a1 1 0 0 1-1-1V10h-.69z"
6
+ fill="currentColor" />
7
+ <path v-if="isDislike"
8
+ d="M22 14h-4V2h4v12zM15.69 14L13 19.94a1 1 0 0 1-.91.56H12c-.87 0-1.6-.63-1.74-1.49L9.2 14H3a1 1 0 0 1-1-1v-1.09c0-.27.06-.53.17-.77l3.14-6.58A2 2 0 0 1 7.15 4H14a1 1 0 0 1 1 1v9h.69z"
9
+ fill="currentColor" />
10
+ </svg>
11
+ </button>
12
+ </template>
13
+
14
+ <script setup>
15
+ import { computed } from 'vue';
16
+
17
+ const props = defineProps({
18
+ active: {
19
+ type: Boolean,
20
+ required: false,
21
+ default: false
22
+ },
23
+ type: {
24
+ type: String,
25
+ required: true
26
+ }
27
+ })
28
+
29
+ const emit = defineEmits(['click']);
30
+
31
+ const isLike = computed(() => props.type === 'like');
32
+ const isDislike = computed(() => props.type === 'dislike');
33
+ </script>
34
+
35
+ <style lang="stylus" scoped>
36
+ .ai-feedback-btn
37
+ border none
38
+ background transparent
39
+ cursor pointer
40
+ padding 6px
41
+ display flex
42
+ align-items center
43
+ justify-content center
44
+ border-radius 6px
45
+ transition background .2s, transform .2s, opacity .2s
46
+ opacity .75
47
+
48
+ &.active .icon
49
+ color $accentColor
50
+ &:hover
51
+ opacity 1
52
+ background rgba(0,0,0,0.06)
53
+ transform scale(1.12)
54
+
55
+ .icon
56
+ width 18px
57
+ height 18px
58
+ display block
59
+ color #666
60
+ </style>
@@ -14,6 +14,12 @@ const props = defineProps({
14
14
  }
15
15
  })
16
16
 
17
+ const notSupportBackdrop = ref(false);
18
+ if (!(CSS && typeof CSS.supports === 'function' && CSS.supports('backdrop-filter', 'blur(10px)'))) {
19
+ // 不支持 backdrop-filter,则使用更简单的样式
20
+ notSupportBackdrop.value = true;
21
+ }
22
+
17
23
  const emit = defineEmits(['change'])
18
24
 
19
25
  const platform = ref(
@@ -27,7 +33,7 @@ watch(platform, (v) => emit('change', v))
27
33
 
28
34
  <template>
29
35
  <div class="select-platform">
30
- <select name="select" v-model="platform">
36
+ <select name="select" v-model="platform" :class="{ 'not-support-backdrop-filter': notSupportBackdrop }">
31
37
  <template v-for="value in props.platforms">
32
38
  <option :key="value" :value="value">{{ value }}</option>
33
39
  </template>
@@ -37,7 +43,6 @@ watch(platform, (v) => emit('change', v))
37
43
 
38
44
  <style lang="stylus" scoped>
39
45
  .select-platform
40
- margin-left auto
41
46
 
42
47
  select
43
48
  color black
@@ -46,17 +51,16 @@ watch(platform, (v) => emit('change', v))
46
51
  border 1px solid rgba(0,0,0,.12)
47
52
  border-radius 8px
48
53
  font-size 14px
49
- background white
54
+ background-color: transparent
50
55
  cursor pointer
51
56
  outline none
52
57
  transition border .2s
58
+ &.not-support-backdrop-filter
59
+ background-color: #fff
53
60
 
54
61
  &:hover
55
- border-color $accentColor
56
-
57
- &:focus
58
- border-color $accentColor
59
- box-shadow 0 0 0 2px rgba($accentColor, .2)
62
+ // border-color $accentColor
63
+ box-shadow 0 0 10px 0px rgba($accentColor,0.3)
60
64
 
61
65
  &:active
62
66
  border-color $accentColor
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_ENABLE_AI = true; // 默认启用 AI 助手
2
+ export const MAX_AI_ANSWER_LENGTH = 6; // AI 消息最大长度
@@ -42,12 +42,6 @@ $svg-hover-color = #9b9b9b
42
42
  overflow-x: hidden;
43
43
  background-color: $search-container-color
44
44
 
45
- .result-wrap
46
- ul
47
- list-style none
48
- margin 0
49
- padding 0
50
-
51
45
  .algolia-logo
52
46
  display flex
53
47
  justify-content center
@@ -16,7 +16,7 @@
16
16
  <div class="tab" :class="{ active: !isAI }" @click="isAI = false">
17
17
  搜索文档
18
18
  </div>
19
- <div class="divider" style="width: 1px;height: 80%; background-color: #ccc; margin: 0 10px;"></div>
19
+ <div class="divider" style="width: 1px;height: 80%; background-color: #eee; margin: 0 10px;"></div>
20
20
  <div class="tab" :class="{ active: isAI }" @click="isAI = true">
21
21
  问 AI
22
22
  </div>
@@ -87,15 +87,7 @@
87
87
  <Results v-if="!item.isAI" :key="item.sourceId" :title="item.title" :results="item.items"
88
88
  :onSelect="item.onSelect" />
89
89
  <template v-else>
90
- <div class="ai-answer-card">
91
- <div class="ai-answer-header">
92
- <span class="ai-answer-icon">🤖</span>
93
- <span class="ai-answer-title">{{ item.title }}</span>
94
- </div>
95
-
96
- <div v-if="item.msg.length" class="ai-answer-msg" v-html="item.msg" />
97
- <Skeleton v-else />
98
- </div>
90
+ <AIAnswer :item="item" />
99
91
  </template>
100
92
  </template>
101
93
  </template>
@@ -146,20 +138,21 @@ import searchPageConfig from '@theme-config/searchPage';
146
138
  import NavbarLogo from '../NavbarLogo.vue';
147
139
  import Results from './components/Results.vue';
148
140
  import pagination from './components/pagination.vue';
149
- import AIChat from './components/AIChat/index.vue';
141
+ import AIChat from './AIChat/index.vue';
142
+ import AIAnswer from './components/AIAnswer.vue';
150
143
  import MainNavbarLink from '../MainNavbarLink.vue';
151
- import Skeleton from './components/Skeleton.vue';
152
144
  import { search as searchClient } from './utils/searchClient';
153
145
  import { postExt, postAsk } from './utils/postDcloudServer';
154
- import { forbidScroll, debounce } from '../../util';
146
+ import { forbidScroll } from '../../util';
155
147
  import { removeHighlightTags, isEditingContent } from './utils/searchUtils';
156
148
  import Base64 from './utils/Base64';
157
149
  import { ajax } from './utils/postDcloudServer';
158
- import { renderMarkdown } from "./components/AIChat/markdown-loader";
150
+ import { renderMarkdown } from "./AIChat/markdown-loader";
151
+ import { DEFAULT_ENABLE_AI, MAX_AI_ANSWER_LENGTH } from './constants';
159
152
  import 'highlight.js/styles/github.min.css'
160
153
 
161
154
  const {
162
- enableAI = true,
155
+ enableAI = DEFAULT_ENABLE_AI,
163
156
  category,
164
157
  translations: {
165
158
  searchBox: { placeholder, buttonText, searchBy },
@@ -185,7 +178,7 @@ export default {
185
178
 
186
179
  props: ['options'],
187
180
 
188
- components: { NavbarLogo, Results, pagination, MainNavbarLink, AIChat, Skeleton },
181
+ components: { NavbarLogo, Results, pagination, MainNavbarLink, AIChat, AIAnswer },
189
182
 
190
183
  data() {
191
184
  return {
@@ -250,6 +243,9 @@ export default {
250
243
  ? 'translateX(100%)'
251
244
  : 'translateX(0%)'
252
245
  }
246
+ },
247
+ showAIMessage() {
248
+ return this.searchValue.trim().length >= MAX_AI_ANSWER_LENGTH || this.aiMessage.msg.trim().length > 0
253
249
  }
254
250
  },
255
251
 
@@ -343,6 +339,8 @@ export default {
343
339
  if (this.isAI) {
344
340
  height = height - 200
345
341
  }
342
+ // 最小高度
343
+ if (height < 200) height = 200;
346
344
  searchResult.style.height = height + 'px';;
347
345
  }
348
346
  },
@@ -391,7 +389,7 @@ export default {
391
389
  this.totalPage = nbPages;
392
390
  this.curPage = page + 1;
393
391
 
394
- if (this.curPage === 1 && this.enableAI) {
392
+ if (this.enableAI && this.curPage === 1 && this.showAIMessage) {
395
393
  this.resultList.splice(1, 0, this.aiMessage);
396
394
  }
397
395
  })
@@ -442,6 +440,7 @@ export default {
442
440
 
443
441
  searchByAI() {
444
442
  try {
443
+ if (!this.showAIMessage) return;
445
444
  this.searchAIResult = ajax(aiChatForDocSearch, 'POST', {
446
445
  "question": this.searchValue,
447
446
  "group_name": this.currentCategory.text
@@ -580,5 +579,4 @@ export default {
580
579
 
581
580
  <style lang="stylus">
582
581
  @import './index'
583
- @import './ai-result.styl'
584
582
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vuepress-theme-uniapp-official",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "uni-app official website theme for vuepress",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -46,8 +46,8 @@
46
46
  "vuepress-plugin-named-chunks": "^1.1.4",
47
47
  "vuepress-plugin-zooming": "^1.1.8",
48
48
  "vuepress-plugin-check-md2": "^1.0.5",
49
- "vuepress-plugin-noscript-code": "^1.0.2",
50
- "vuepress-plugin-expandable-row": "^1.0.10"
49
+ "vuepress-plugin-expandable-row": "^1.0.10",
50
+ "vuepress-plugin-noscript-code": "^1.0.2"
51
51
  },
52
52
  "resolutions": {
53
53
  "terser-webpack-plugin": "1.4.6",
@@ -1,61 +0,0 @@
1
- .ai-answer-card
2
- padding 14px 16px
3
- margin-top 12px
4
- background #fff
5
- border-radius 12px
6
- border 1px solid rgba(0,0,0,0.06)
7
- box-shadow 0 2px 8px rgba(0,0,0,0.04)
8
- transition box-shadow .25s, transform .2s
9
- cursor pointer
10
-
11
- &:hover
12
- box-shadow 0 4px 14px rgba(0,0,0,0.08)
13
- transform translateY(-1px)
14
- /* 重置部分元素样式,防止样式冲突 */
15
- pre
16
- margin: 0
17
- padding: 5px
18
- border-radius 10px
19
- background #f6f8fa
20
- & + pre
21
- margin-top 8px
22
- pre, code
23
- white-space: pre-wrap; /* 允许换行 */
24
- word-wrap: break-word; /* 允许长行断开 */
25
- word-break: break-word;
26
- h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote
27
- line-height: normal
28
- margin: 0
29
- padding: 0
30
- ul, ol
31
- list-style: none
32
-
33
- .ai-answer-header
34
- display flex
35
- align-items center
36
- margin-bottom 8px
37
-
38
- .ai-answer-icon
39
- font-size 18px
40
- margin-right 6px
41
-
42
- .ai-answer-title
43
- font-weight 600
44
- font-size 15px
45
- color #333
46
-
47
- .ai-answer-msg
48
- font-size 14px
49
- line-height normal
50
- color #555
51
- // white-space pre-wrap
52
- word-break break-word
53
- animation fadeIn .3s ease
54
-
55
- @keyframes fadeIn
56
- from
57
- opacity 0
58
- transform translateY(4px)
59
- to
60
- opacity 1
61
- transform translateY(0)