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.
- package/components/DcloudSearchPage/{components/AIChat → AIChat}/index.vue +150 -61
- package/components/DcloudSearchPage/{components/AIChat → AIChat}/markdown-loader.js +22 -19
- package/components/DcloudSearchPage/components/AIAnswer.vue +146 -0
- package/components/DcloudSearchPage/components/LikeButton.vue +60 -0
- package/components/DcloudSearchPage/components/{AIChat/SelectPlatform.vue → SelectPlatform.vue} +12 -8
- package/components/DcloudSearchPage/components/Skeleton.vue +7 -7
- package/components/DcloudSearchPage/constants.js +2 -0
- package/components/DcloudSearchPage/index.vue +17 -19
- package/package.json +3 -3
- package/components/DcloudSearchPage/ai-result.styl +0 -54
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="chat-wrapper">
|
|
3
|
-
<
|
|
4
|
-
<div class="title">
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
35
|
-
<div class="input-container"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
</
|
|
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
|
|
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
|
-
|
|
116
|
-
el.
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
328
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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>
|
package/components/DcloudSearchPage/components/{AIChat/SelectPlatform.vue → SelectPlatform.vue}
RENAMED
|
@@ -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
|
|
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="
|
|
4
|
-
<div class="
|
|
5
|
-
<div class="
|
|
6
|
-
<div class="
|
|
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 .
|
|
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 .
|
|
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
|
-
.
|
|
45
|
+
.chat-skeleton_content
|
|
46
46
|
flex: 1
|
|
47
47
|
display: flex
|
|
48
48
|
flex-direction: column
|
|
@@ -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: #
|
|
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
|
-
<
|
|
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 './
|
|
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
|
|
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 "./
|
|
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 =
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
+
"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)
|