vuepress-theme-uniapp-official 1.6.3 → 1.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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'
59
+ import { ref, nextTick, watchEffect, onMounted, computed } from 'vue'
49
60
  import { renderMarkdown } from "./markdown-loader";
50
- import SelectPlatform from './SelectPlatform.vue';
51
- import { ajax } from '../../utils/postDcloudServer';
52
61
  import searchPageConfig from '@theme-config/searchPage';
53
- import Skeleton from '../Skeleton';
62
+ import { MAX_AI_ANSWER_LENGTH } from '../constants';
63
+ import SelectPlatform from '../components/SelectPlatform.vue';
64
+ import LikeButton from '../components/LikeButton.vue';
65
+ import Skeleton from '../components/Skeleton.vue';
66
+ import { ajax } from '../utils/postDcloudServer';
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
 
@@ -197,7 +239,7 @@ async function send() {
197
239
  if (res.errorCode === 0) {
198
240
  fakeReply = res.chunk
199
241
  } else {
200
- fakeReply = `抱歉,AI 助手出错了:${res.errorMessage || '未知错误'}`
242
+ fakeReply = `抱歉,AI 助手出错了:${res.errorMsg || '未知错误'}`
201
243
  }
202
244
  } catch (error) {
203
245
  fakeReply = `抱歉,AI 助手出错了:${error.message || '未知错误'}`
@@ -212,8 +254,10 @@ 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
  }
260
+
217
261
  messages.value.push(aiMsg)
218
262
 
219
263
  // 动态打字
@@ -223,33 +267,58 @@ async function send() {
223
267
  scrollToBottom()
224
268
  }
225
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
+
226
286
  window.addEventListener('resize', scrollToBottom)
227
287
  </script>
228
288
 
229
289
  <style lang="stylus">
290
+ .__backdrop-filter__
291
+ backdrop-filter blur(15px)
292
+ .__not-support-backdrop-filter__
293
+ background-color: #fff
294
+
230
295
  .chat-wrapper
231
296
  display flex
297
+ position relative
232
298
  flex-direction column
233
299
  height calc(100vh - 56px - 40px)
234
300
  background #f9fafb
235
301
  overflow hidden
236
302
 
237
- .chat-header
303
+ /* .chat-header
238
304
  height 56px
239
305
  display flex
240
306
  align-items center
241
307
  padding 0 16px
242
308
  background white
243
- border-bottom 1px solid #eee
309
+ border-bottom 1px solid #eee */
244
310
 
245
311
  .title
246
- font-size 18px
312
+ margin-top 30px
313
+ text-align center
314
+ font-size 30px
247
315
  font-weight 600
316
+ color #cecece
248
317
 
249
318
  .chat-messages
250
319
  flex 1
251
320
  overflow-y auto
252
- padding 16px
321
+ padding 16px 16px 120px
253
322
  box-sizing border-box
254
323
 
255
324
  .msg
@@ -290,6 +359,8 @@ window.addEventListener('resize', scrollToBottom)
290
359
  word-break break-word
291
360
  box-shadow 0 1px 3px rgba(0,0,0,0.08)
292
361
  pre
362
+ margin: 0
363
+ padding: 5px
293
364
  border-radius 10px
294
365
  & + pre
295
366
  margin-top 8px
@@ -297,7 +368,7 @@ window.addEventListener('resize', scrollToBottom)
297
368
  white-space: pre-wrap; /* 允许换行 */
298
369
  word-wrap: break-word; /* 允许长行断开 */
299
370
  word-break: break-word;
300
- h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote, pre
371
+ h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote
301
372
  margin: 0
302
373
  padding: 0
303
374
  ul, ol
@@ -311,54 +382,72 @@ window.addEventListener('resize', scrollToBottom)
311
382
  align-items center
312
383
 
313
384
  .actions
385
+ margin-left 20px
314
386
  display flex
315
- gap 10px
316
-
317
- .icon
318
- cursor pointer
319
- opacity .5
320
- transition .2s
321
- &.active
322
- opacity 1
323
- color $accentColor
324
387
 
325
388
  .chat-input-bar
326
389
  padding 10px
327
- background white
328
- border-top 1px solid #eee
390
+ position absolute
391
+ left 10px
392
+ right 10px
393
+ bottom 0
329
394
  display flex
330
395
  align-items flex-end
331
396
  justify-content center
332
397
 
333
398
  .input-container
334
- display flex
335
- align-items flex-end
336
399
  width 100%
337
- background #fff
338
- border 1px solid rgba(0,0,0,.1)
400
+ border 1px solid rgba(0,0,0,.2)
339
401
  border-radius 20px
340
402
  padding 8px 12px
341
- box-shadow 0 2px 8px rgba(0,0,0,.06)
403
+ box-shadow 0 2px 10px rgba(0,0,0,.2)
342
404
  transition border-color .2s
405
+ @extend .__backdrop-filter__
343
406
  &:focus-within
344
407
  border-color $accentColor
345
408
  box-shadow 0 0 0 2px rgba($accentColor, .2)
409
+ &.error-border, &.error-border:focus-within
410
+ border-color: #e00 !important;
411
+ box-shadow 0 0 0 2px rgba(#e00, .2)
412
+ &.not-support-backdrop-filter
413
+ @extend .__not-support-backdrop-filter__
414
+
415
+ .footer-toolbar
416
+ display flex
417
+ align-items center
418
+ justify-content space-between
419
+ .footer-toolbar_left, .footer-toolbar_right
420
+ display flex
421
+ align-items center
422
+ justify-content center
423
+ .error-tips
424
+ margin-right 12px
425
+ font-size 12px
426
+ color #e00
427
+ .tips
428
+ font-size 12px
429
+ color #888
346
430
 
347
431
  .chat-input
348
- flex 1
432
+ width 100%
433
+ box-sizing border-box
349
434
  resize none
350
435
  outline none
351
436
  border none
352
437
  padding 8px 10px
353
- line-height 1.5
438
+ line-height 1.2
354
439
  max-height 160px
355
440
  overflow-y auto
356
441
  font-size 15px
357
442
  transition: height 0.2s;
443
+ background-color transparent
444
+ -webkit-user-select: auto;
445
+ -webkit-touch-callout: auto;
446
+ word-break: break-word;
358
447
 
359
448
  .send-btn
360
449
  margin-left 8px
361
- padding 8px 14px
450
+ padding 6px 10px
362
451
  border none
363
452
  border-radius 8px
364
453
  background $accentColor
@@ -1,25 +1,30 @@
1
1
  // markdown-loader.js
2
2
  let markedInstance = null;
3
- let hljsInstance = null;
4
3
 
5
- function getLangCodeFromExtension (extension) {
6
- const extensionMap = {
7
- vue: 'markup',
8
- html: 'markup',
9
- md: 'markdown',
10
- rb: 'ruby',
11
- ts: 'typescript',
12
- py: 'python',
13
- sh: 'bash',
14
- yml: 'yaml',
15
- styl: 'stylus',
16
- kt: 'kotlin',
17
- rs: 'rust',
4
+ // `1.` 转义为 `1、`,防止 marked 解析失败
5
+ function escapeMD(str) {
6
+ return str
7
+ .replace(/(\s*\b)(\d+)\./g, '$1$2、')
8
+ }
9
+
10
+ function getLangCodeFromExtension(extension) {
11
+ const extensionMap = {
12
+ vue: 'markup',
13
+ html: 'markup',
14
+ md: 'markdown',
15
+ rb: 'ruby',
16
+ ts: 'typescript',
17
+ py: 'python',
18
+ sh: 'bash',
19
+ yml: 'yaml',
20
+ styl: 'stylus',
21
+ kt: 'kotlin',
22
+ rs: 'rust',
18
23
  uts: 'typescript',
19
24
  json5: 'json',
20
- }
25
+ };
21
26
 
22
- return extensionMap[extension] || extension
27
+ return extensionMap[extension] || extension;
23
28
  }
24
29
 
25
30
  export async function renderMarkdown(md) {
@@ -39,8 +44,6 @@ export async function renderMarkdown(md) {
39
44
  });
40
45
 
41
46
  markedInstance = marked;
42
- hljsInstance = hljs;
43
47
  }
44
-
45
- return markedInstance.parse(md || '');
48
+ return markedInstance.parse(escapeMD(md || ''));
46
49
  }
@@ -0,0 +1,146 @@
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
+ .ai-answer-card
69
+ padding 14px 16px
70
+ margin-top 12px
71
+ background #fff
72
+ border-radius 12px
73
+ border 1px solid rgba(0,0,0,0.06)
74
+ box-shadow 0 2px 8px rgba(0,0,0,0.04)
75
+ transition box-shadow .25s //, transform .2s
76
+ cursor pointer
77
+
78
+ &:hover
79
+ box-shadow 0 4px 14px rgba(0,0,0,0.08)
80
+ // transform translateY(-1px)
81
+
82
+ .ai-answer-header
83
+ display flex
84
+ align-items center
85
+ margin-bottom 8px
86
+
87
+ .ai-answer-icon
88
+ font-size 18px
89
+ margin-right 6px
90
+
91
+ .ai-answer-title
92
+ font-weight 600
93
+ font-size 15px
94
+ color #333
95
+
96
+ .ai-answer-msg
97
+ font-size 14px
98
+ line-height 1.6
99
+ color #444
100
+ word-break break-word
101
+ animation fadeIn .25s ease
102
+
103
+ /* --- reset 内容区 --- */
104
+ pre
105
+ margin 0
106
+ padding 6px 8px
107
+ border-radius 10px
108
+ background #f6f8fa
109
+ white-space pre-wrap
110
+ word-break break-word
111
+
112
+ & + pre
113
+ margin-top 8px
114
+
115
+ code
116
+ white-space pre-wrap
117
+ word-break break-word
118
+
119
+ h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote
120
+ margin 0
121
+ padding 0
122
+
123
+ ul, ol
124
+ list-style none
125
+
126
+ @keyframes fadeIn
127
+ from
128
+ opacity 0
129
+ transform translateY(4px)
130
+ to
131
+ opacity 1
132
+ transform translateY(0)
133
+
134
+ .ai-answer-footer
135
+ margin-top 12px
136
+ padding-top 10px
137
+ border-top 1px solid rgba(0,0,0,.06)
138
+ display flex
139
+ align-items center
140
+ justify-content space-between
141
+ font-size 12px
142
+ color #888
143
+
144
+ .ai-answer-footer_right
145
+ display flex
146
+ </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
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
  <div class="chat-skeleton chat-skeleton-left">
3
- <div class="content">
4
- <div class="line"></div>
5
- <div class="line"></div>
6
- <div class="line short"></div>
3
+ <div class="chat-skeleton_content">
4
+ <div class="chat-skeleton_content_line"></div>
5
+ <div class="chat-skeleton_content_line"></div>
6
+ <div class="chat-skeleton_content_line short"></div>
7
7
  </div>
8
8
  </div>
9
9
  </template>
@@ -29,12 +29,12 @@
29
29
  gap: 12px
30
30
  padding: 12px 0
31
31
 
32
- .chat-skeleton .line
32
+ .chat-skeleton .chat-skeleton_content_line
33
33
  height: 14px
34
34
  border-radius: 6px
35
35
  @extend .skeleton
36
36
 
37
- .chat-skeleton .line.short
37
+ .chat-skeleton .chat-skeleton_content_line.short
38
38
  width: 40%
39
39
 
40
40
  /* 左侧消息骨架(AI) */
@@ -42,7 +42,7 @@
42
42
  display: flex
43
43
  flex-direction: row
44
44
  gap: 10px
45
- .content
45
+ .chat-skeleton_content
46
46
  flex: 1
47
47
  display: flex
48
48
  flex-direction: column
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_ENABLE_AI = true; // 默认启用 AI 助手
2
+ export const MAX_AI_ANSWER_LENGTH = 6; // AI 消息最大长度
@@ -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
@@ -449,7 +448,7 @@ export default {
449
448
  if (res.errorCode === 0) {
450
449
  return renderMarkdown(res.chunk)
451
450
  } else {
452
- this.aiMessage.msg = res.errorMessage || AIErrorMsg;
451
+ this.aiMessage.msg = res.errorMsg || AIErrorMsg;
453
452
  return ''
454
453
  }
455
454
  })
@@ -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.3",
3
+ "version": "1.6.5",
4
4
  "description": "uni-app official website theme for vuepress",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -45,9 +45,9 @@
45
45
  "vuepress-plugin-mermaidjs": "1.9.1",
46
46
  "vuepress-plugin-named-chunks": "^1.1.4",
47
47
  "vuepress-plugin-zooming": "^1.1.8",
48
- "vuepress-plugin-check-md2": "^1.0.5",
49
48
  "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-check-md2": "^1.0.5"
51
51
  },
52
52
  "resolutions": {
53
53
  "terser-webpack-plugin": "1.4.6",
@@ -1,54 +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, code
16
- white-space: pre-wrap; /* 允许换行 */
17
- word-wrap: break-word; /* 允许长行断开 */
18
- word-break: break-word;
19
- h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote, pre
20
- line-height: normal
21
- margin: 0
22
- padding: 0
23
- ul, ol
24
- list-style: none
25
-
26
- .ai-answer-header
27
- display flex
28
- align-items center
29
- margin-bottom 8px
30
-
31
- .ai-answer-icon
32
- font-size 18px
33
- margin-right 6px
34
-
35
- .ai-answer-title
36
- font-weight 600
37
- font-size 15px
38
- color #333
39
-
40
- .ai-answer-msg
41
- font-size 14px
42
- line-height normal
43
- color #555
44
- // white-space pre-wrap
45
- word-break break-word
46
- animation fadeIn .3s ease
47
-
48
- @keyframes fadeIn
49
- from
50
- opacity 0
51
- transform translateY(4px)
52
- to
53
- opacity 1
54
- transform translateY(0)