@syfei49/mini-dynamic-renderer 1.0.0 → 1.0.2
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.
Potentially problematic release.
This version of @syfei49/mini-dynamic-renderer might be problematic. Click here for more details.
- package/README.md +84 -102
- package/components/ly-ai-assistant/ly-ai-assistant.vue +523 -0
- package/index.js +16 -14
- package/package.json +27 -18
- package/src/adapters/request.js +106 -0
- package/src/chat/parser.js +55 -0
- package/src/chat/service.js +296 -0
- package/src/config.js +51 -0
- package/src/event.js +43 -0
- package/src/handler.js +43 -49
- package/src/init.js +38 -57
- package/src/interceptor.js +105 -104
package/README.md
CHANGED
|
@@ -1,102 +1,84 @@
|
|
|
1
|
-
# @syfei49/mini-dynamic-renderer
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## 接入
|
|
15
|
-
|
|
16
|
-
### 1.
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
|
63
|
-
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# 4. 预览将要发布的文件
|
|
86
|
-
npm pack --dry-run
|
|
87
|
-
|
|
88
|
-
# 5. 正式发布(作用域包首次需 public)
|
|
89
|
-
npm publish --access public
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
后续版本更新:
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
npm version patch # 或 minor / major
|
|
96
|
-
npm publish --access public
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## 版本说明
|
|
100
|
-
|
|
101
|
-
- **v1.0.0**:拦截 `wx.request` 成功响应,弹出 `hello world` 测试弹窗
|
|
102
|
-
- 后续版本将支持根据后端 `renderConfig` 动态渲染图片上传、文本等组件
|
|
1
|
+
# @syfei49/mini-dynamic-renderer
|
|
2
|
+
|
|
3
|
+
uni-app 浮层 AI 客服插件(v1.0.1)。在指定页面右下角显示 AI 浮层图标,点击后从底部弹出抽屉聊天面板,支持文字、图片、拍照/相册上传及 `[NEED_ICON]` 分类图标引导。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd your-uniapp-project
|
|
9
|
+
npm install @syfei49/mini-dynamic-renderer
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
微信开发者工具 → **工具** → **构建 npm**
|
|
13
|
+
|
|
14
|
+
## 接入
|
|
15
|
+
|
|
16
|
+
### 1. App.vue 初始化并挂载组件
|
|
17
|
+
|
|
18
|
+
```vue
|
|
19
|
+
<script setup>
|
|
20
|
+
import LyAiAssistant from '@syfei49/mini-dynamic-renderer/components/ly-ai-assistant/ly-ai-assistant.vue'
|
|
21
|
+
import { init } from '@syfei49/mini-dynamic-renderer'
|
|
22
|
+
|
|
23
|
+
init({
|
|
24
|
+
baseUrl: 'https://your-api.com/prod-api',
|
|
25
|
+
chatApiPath: '/mini/ai/chat',
|
|
26
|
+
uploadApiPath: '/mini/common/upload',
|
|
27
|
+
getToken: () => uni.getStorageSync('token'),
|
|
28
|
+
visiblePages: ['pages/user/user'],
|
|
29
|
+
requireLogin: true,
|
|
30
|
+
loginPage: '/pages/login/login?noauto=1'
|
|
31
|
+
})
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<view>
|
|
36
|
+
<!-- 页面内容 -->
|
|
37
|
+
<LyAiAssistant />
|
|
38
|
+
</view>
|
|
39
|
+
</template>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> `LyAiAssistant` 只需在 App.vue 全局挂载一次。
|
|
43
|
+
|
|
44
|
+
### 2. 在任意页面打开抽屉(可选)
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
import { openChat } from '@syfei49/mini-dynamic-renderer'
|
|
48
|
+
|
|
49
|
+
openChat()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 配置项
|
|
53
|
+
|
|
54
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
55
|
+
|------|------|--------|------|
|
|
56
|
+
| `baseUrl` | `string` | `''` | API 根地址 |
|
|
57
|
+
| `chatApiPath` | `string` | `'/mini/ai/chat'` | 对话接口路径 |
|
|
58
|
+
| `uploadApiPath` | `string` | `'/mini/common/upload'` | 图片上传路径 |
|
|
59
|
+
| `getToken` | `Function` | `() => ''` | 返回 Authorization |
|
|
60
|
+
| `getHeaders` | `Function` | `() => ({})` | 额外请求头 |
|
|
61
|
+
| `visiblePages` | `string[]` | `['pages/user/user']` | 显示浮层图标的页面路由 |
|
|
62
|
+
| `floatIcon` | `string` | 内置默认图标 | 浮层按钮图片 URL |
|
|
63
|
+
| `requireLogin` | `boolean` | `true` | 打开前检查登录 |
|
|
64
|
+
| `loginPage` | `string` | `'/pages/login/login'` | 未登录跳转页 |
|
|
65
|
+
| `debug` | `boolean` | `false` | 调试日志 |
|
|
66
|
+
| `enableInterceptor` | `boolean` | `false` | 是否启用 wx.request 拦截(预留) |
|
|
67
|
+
|
|
68
|
+
## API
|
|
69
|
+
|
|
70
|
+
| 方法 | 说明 |
|
|
71
|
+
|------|------|
|
|
72
|
+
| `init(config?)` | 初始化插件 |
|
|
73
|
+
| `openChat()` | 打开聊天抽屉 |
|
|
74
|
+
| `closeChat()` | 关闭聊天抽屉 |
|
|
75
|
+
| `getConfig()` | 获取当前配置 |
|
|
76
|
+
| `isInitialized()` | 是否已初始化 |
|
|
77
|
+
| `uninstall()` | 卸载拦截器,恢复原始 `wx.request` |
|
|
78
|
+
| `parseRenderConfig(data)` | 解析响应中的渲染配置(预留) |
|
|
79
|
+
|
|
80
|
+
## 版本说明
|
|
81
|
+
|
|
82
|
+
- **v1.0.1**:uni-app 浮层 AI 插件、抽屉聊天、迁移 aiChat 全能力、文档拆分
|
|
83
|
+
- v1.0.0:wx.request 拦截 + hello world 弹窗(测试版)
|
|
84
|
+
- 后续版本将根据后端 `renderConfig` 动态渲染更多组件类型
|
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view>
|
|
3
|
+
<view v-if="visible" class="ly-ai-float" :style="floatStyle" @click="openChat">
|
|
4
|
+
<image v-if="floatIconUrl" class="ly-ai-float-icon" :src="floatIconUrl" mode="aspectFit" />
|
|
5
|
+
<view v-else class="ly-ai-float-text">AI</view>
|
|
6
|
+
</view>
|
|
7
|
+
|
|
8
|
+
<view v-if="opened" class="ly-ai-overlay" @click.self="closeChat">
|
|
9
|
+
<view class="ly-ai-drawer">
|
|
10
|
+
<view class="ly-ai-header">
|
|
11
|
+
<text class="ly-ai-title">AI 智能客服</text>
|
|
12
|
+
<view class="ly-ai-close" @click="closeChat">×</view>
|
|
13
|
+
</view>
|
|
14
|
+
|
|
15
|
+
<scroll-view class="ly-ai-list" scroll-y :scroll-into-view="scrollIntoView" scroll-with-animation>
|
|
16
|
+
<view v-if="messages.length === 0" class="ly-ai-welcome">
|
|
17
|
+
<view class="ly-ai-welcome-title">AI 智能客服</view>
|
|
18
|
+
<view class="ly-ai-welcome-desc">我可以帮你处理商品分类相关操作。新增一级分类时,若未提供图标,会引导你拍照或从相册选择。</view>
|
|
19
|
+
<view v-for="(item, index) in quickQuestions" :key="index" class="ly-ai-quick" @click="sendQuick(item)">
|
|
20
|
+
{{ item }}
|
|
21
|
+
</view>
|
|
22
|
+
</view>
|
|
23
|
+
|
|
24
|
+
<view v-for="(item, index) in messages" :key="index" :id="'msg-' + index"
|
|
25
|
+
class="ly-ai-msg-row" :class="item.role === 'user' ? 'ly-ai-msg-row-user' : 'ly-ai-msg-row-ai'">
|
|
26
|
+
<view class="ly-ai-bubble" :class="item.role === 'user' ? 'ly-ai-bubble-user' : 'ly-ai-bubble-ai'">
|
|
27
|
+
<image v-for="(img, imgIndex) in item.imageUrls" :key="imgIndex" class="ly-ai-msg-image"
|
|
28
|
+
:src="img" mode="aspectFill" @click="previewImage(img, item.imageUrls)" />
|
|
29
|
+
<text v-if="item.content" class="ly-ai-msg-text">{{ item.content }}</text>
|
|
30
|
+
</view>
|
|
31
|
+
</view>
|
|
32
|
+
|
|
33
|
+
<view v-if="loading" class="ly-ai-msg-row ly-ai-msg-row-ai">
|
|
34
|
+
<view class="ly-ai-bubble ly-ai-bubble-ai ly-ai-loading-bubble">
|
|
35
|
+
<text class="ly-ai-msg-text">正在思考中...</text>
|
|
36
|
+
</view>
|
|
37
|
+
</view>
|
|
38
|
+
</scroll-view>
|
|
39
|
+
|
|
40
|
+
<view v-if="iconPickMode" class="ly-ai-icon-pick-bar">
|
|
41
|
+
<view class="ly-ai-icon-pick-title">请为「{{ pendingIconCategory && pendingIconCategory.name }}」选择分类图标</view>
|
|
42
|
+
<view class="ly-ai-icon-pick-actions">
|
|
43
|
+
<view class="ly-ai-icon-pick-btn" :class="{ disabled: loading || uploading }" @click="pickCategoryIcon('camera')">拍照</view>
|
|
44
|
+
<view class="ly-ai-icon-pick-btn" :class="{ disabled: loading || uploading }" @click="pickCategoryIcon('album')">从相册选择</view>
|
|
45
|
+
<view class="ly-ai-icon-pick-btn cancel" :class="{ disabled: loading || uploading }" @click="cancelIconPick">取消</view>
|
|
46
|
+
</view>
|
|
47
|
+
</view>
|
|
48
|
+
|
|
49
|
+
<template v-else>
|
|
50
|
+
<view v-if="pendingImageUrl" class="ly-ai-image-preview-bar">
|
|
51
|
+
<image class="ly-ai-preview-image" :src="pendingImageUrl" mode="aspectFill" />
|
|
52
|
+
<view class="ly-ai-preview-remove" @click="clearPendingImage">×</view>
|
|
53
|
+
</view>
|
|
54
|
+
|
|
55
|
+
<view class="ly-ai-input-bar">
|
|
56
|
+
<view class="ly-ai-attach-btn" :class="{ disabled: loading || uploading }" @click="chooseImage">+</view>
|
|
57
|
+
<input class="ly-ai-chat-input" :value="inputText" placeholder="请输入您的问题" confirm-type="send"
|
|
58
|
+
:disabled="loading || uploading" @input="onInput" @confirm="handleSend" />
|
|
59
|
+
<view class="ly-ai-send-btn" :class="{ disabled: loading || uploading || !canSend }" @click="handleSend">发送</view>
|
|
60
|
+
</view>
|
|
61
|
+
</template>
|
|
62
|
+
</view>
|
|
63
|
+
</view>
|
|
64
|
+
</view>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script setup>
|
|
68
|
+
import {
|
|
69
|
+
ref,
|
|
70
|
+
computed,
|
|
71
|
+
nextTick,
|
|
72
|
+
onMounted,
|
|
73
|
+
onUnmounted
|
|
74
|
+
} from 'vue'
|
|
75
|
+
import {
|
|
76
|
+
onShow
|
|
77
|
+
} from '@dcloudio/uni-app'
|
|
78
|
+
import chatService from '../../src/chat/service.js'
|
|
79
|
+
import configModule from '../../src/config.js'
|
|
80
|
+
|
|
81
|
+
const visible = ref(false)
|
|
82
|
+
const opened = ref(false)
|
|
83
|
+
const messages = ref([])
|
|
84
|
+
const loading = ref(false)
|
|
85
|
+
const uploading = ref(false)
|
|
86
|
+
const iconPickMode = ref(false)
|
|
87
|
+
const pendingIconCategory = ref(null)
|
|
88
|
+
const pendingImageUrl = ref('')
|
|
89
|
+
const inputText = ref('')
|
|
90
|
+
const quickQuestions = ref([])
|
|
91
|
+
const scrollIntoView = ref('')
|
|
92
|
+
const floatIconUrl = ref('')
|
|
93
|
+
|
|
94
|
+
const canSend = computed(() => {
|
|
95
|
+
return !!(inputText.value && inputText.value.trim()) || !!pendingImageUrl.value
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const floatStyle = computed(() => {
|
|
99
|
+
return {
|
|
100
|
+
right: '32rpx',
|
|
101
|
+
bottom: '180rpx'
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
let updateHandler = null
|
|
106
|
+
let openHandler = null
|
|
107
|
+
let closeHandler = null
|
|
108
|
+
|
|
109
|
+
function syncState(snapshot) {
|
|
110
|
+
messages.value = snapshot.messages || []
|
|
111
|
+
loading.value = snapshot.loading || false
|
|
112
|
+
uploading.value = snapshot.uploading || false
|
|
113
|
+
iconPickMode.value = snapshot.iconPickMode || false
|
|
114
|
+
pendingIconCategory.value = snapshot.pendingIconCategory || null
|
|
115
|
+
pendingImageUrl.value = snapshot.pendingImageUrl || ''
|
|
116
|
+
inputText.value = snapshot.inputText || ''
|
|
117
|
+
quickQuestions.value = snapshot.quickQuestions || []
|
|
118
|
+
scrollToBottom()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function scrollToBottom() {
|
|
122
|
+
nextTick(() => {
|
|
123
|
+
scrollIntoView.value = 'msg-' + (messages.value.length - 1)
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
scrollIntoView.value = ''
|
|
126
|
+
}, 300)
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function checkVisible() {
|
|
131
|
+
const config = configModule.getConfig()
|
|
132
|
+
const pages = getCurrentPages()
|
|
133
|
+
const current = pages[pages.length - 1]
|
|
134
|
+
const route = current ? current.route : ''
|
|
135
|
+
const visiblePages = config.visiblePages || []
|
|
136
|
+
const shouldShow = visiblePages.some(function (page) {
|
|
137
|
+
return route === page || route.indexOf(page) === 0
|
|
138
|
+
})
|
|
139
|
+
visible.value = shouldShow
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function openChat() {
|
|
143
|
+
if (!chatService.openConversation()) {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
opened.value = true
|
|
147
|
+
scrollToBottom()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function closeChat() {
|
|
151
|
+
opened.value = false
|
|
152
|
+
chatService.closeConversation()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function onInput(e) {
|
|
156
|
+
inputText.value = e.detail.value
|
|
157
|
+
chatService.setInputText(e.detail.value)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function handleSend() {
|
|
161
|
+
chatService.handleSend().then(() => {
|
|
162
|
+
scrollToBottom()
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function sendQuick(text) {
|
|
167
|
+
chatService.sendQuick(text).then(() => {
|
|
168
|
+
scrollToBottom()
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function chooseImage() {
|
|
173
|
+
chatService.chooseImage().catch(function (error) {
|
|
174
|
+
uni.showToast({
|
|
175
|
+
title: error && (error.message || error.msg) ? (error.message || error.msg) : '图片上传失败',
|
|
176
|
+
icon: 'none'
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function pickCategoryIcon(source) {
|
|
182
|
+
chatService.pickCategoryIcon(source).catch(function (error) {
|
|
183
|
+
uni.showToast({
|
|
184
|
+
title: error && (error.message || error.msg) ? (error.message || error.msg) : '图片上传失败',
|
|
185
|
+
icon: 'none'
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function cancelIconPick() {
|
|
191
|
+
chatService.cancelIconPick()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function clearPendingImage() {
|
|
195
|
+
chatService.clearPendingImage()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function previewImage(current, urls) {
|
|
199
|
+
const list = (urls && urls.length ? urls : [current]).filter(Boolean)
|
|
200
|
+
if (!list.length) {
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
uni.previewImage({
|
|
204
|
+
current: current,
|
|
205
|
+
urls: list
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
onMounted(() => {
|
|
210
|
+
const config = configModule.getConfig()
|
|
211
|
+
floatIconUrl.value = config.floatIcon || ''
|
|
212
|
+
updateHandler = function (snapshot) {
|
|
213
|
+
syncState(snapshot)
|
|
214
|
+
}
|
|
215
|
+
openHandler = function () {
|
|
216
|
+
opened.value = true
|
|
217
|
+
scrollToBottom()
|
|
218
|
+
}
|
|
219
|
+
closeHandler = function () {
|
|
220
|
+
opened.value = false
|
|
221
|
+
}
|
|
222
|
+
chatService.onUpdate(updateHandler)
|
|
223
|
+
chatService.onOpen(openHandler)
|
|
224
|
+
chatService.onClose(closeHandler)
|
|
225
|
+
syncState(chatService.getSnapshot())
|
|
226
|
+
checkVisible()
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
onUnmounted(() => {
|
|
230
|
+
if (updateHandler) {
|
|
231
|
+
chatService.offUpdate(updateHandler)
|
|
232
|
+
}
|
|
233
|
+
if (openHandler) {
|
|
234
|
+
chatService.offOpen(openHandler)
|
|
235
|
+
}
|
|
236
|
+
if (closeHandler) {
|
|
237
|
+
chatService.offClose(closeHandler)
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
onShow(() => {
|
|
242
|
+
checkVisible()
|
|
243
|
+
})
|
|
244
|
+
</script>
|
|
245
|
+
|
|
246
|
+
<style scoped>
|
|
247
|
+
.ly-ai-float {
|
|
248
|
+
position: fixed;
|
|
249
|
+
z-index: 9999;
|
|
250
|
+
width: 96rpx;
|
|
251
|
+
height: 96rpx;
|
|
252
|
+
border-radius: 50%;
|
|
253
|
+
background: #f84616;
|
|
254
|
+
display: flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
justify-content: center;
|
|
257
|
+
box-shadow: 0 4rpx 16rpx rgba(248, 70, 22, 0.35);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.ly-ai-float-icon {
|
|
261
|
+
width: 56rpx;
|
|
262
|
+
height: 56rpx;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.ly-ai-float-text {
|
|
266
|
+
color: #fff;
|
|
267
|
+
font-size: 28rpx;
|
|
268
|
+
font-weight: 600;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.ly-ai-overlay {
|
|
272
|
+
position: fixed;
|
|
273
|
+
top: 0;
|
|
274
|
+
left: 0;
|
|
275
|
+
right: 0;
|
|
276
|
+
bottom: 0;
|
|
277
|
+
z-index: 10000;
|
|
278
|
+
background: rgba(0, 0, 0, 0.4);
|
|
279
|
+
display: flex;
|
|
280
|
+
flex-direction: column;
|
|
281
|
+
justify-content: flex-end;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.ly-ai-drawer {
|
|
285
|
+
height: 82vh;
|
|
286
|
+
background: #f5f6f8;
|
|
287
|
+
border-radius: 32rpx 32rpx 0 0;
|
|
288
|
+
display: flex;
|
|
289
|
+
flex-direction: column;
|
|
290
|
+
overflow: hidden;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.ly-ai-header {
|
|
294
|
+
display: flex;
|
|
295
|
+
align-items: center;
|
|
296
|
+
justify-content: center;
|
|
297
|
+
padding: 24rpx 32rpx;
|
|
298
|
+
background: #fff;
|
|
299
|
+
position: relative;
|
|
300
|
+
border-bottom: 1rpx solid #eee;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.ly-ai-title {
|
|
304
|
+
font-size: 32rpx;
|
|
305
|
+
font-weight: 600;
|
|
306
|
+
color: #333;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.ly-ai-close {
|
|
310
|
+
position: absolute;
|
|
311
|
+
right: 32rpx;
|
|
312
|
+
top: 50%;
|
|
313
|
+
transform: translateY(-50%);
|
|
314
|
+
width: 56rpx;
|
|
315
|
+
height: 56rpx;
|
|
316
|
+
line-height: 52rpx;
|
|
317
|
+
text-align: center;
|
|
318
|
+
font-size: 40rpx;
|
|
319
|
+
color: #999;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.ly-ai-list {
|
|
323
|
+
flex: 1;
|
|
324
|
+
padding: 24rpx;
|
|
325
|
+
box-sizing: border-box;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.ly-ai-welcome {
|
|
329
|
+
background: #fff;
|
|
330
|
+
border-radius: 16rpx;
|
|
331
|
+
padding: 32rpx;
|
|
332
|
+
margin-bottom: 24rpx;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.ly-ai-welcome-title {
|
|
336
|
+
font-size: 32rpx;
|
|
337
|
+
font-weight: 600;
|
|
338
|
+
color: #333;
|
|
339
|
+
margin-bottom: 12rpx;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.ly-ai-welcome-desc {
|
|
343
|
+
font-size: 26rpx;
|
|
344
|
+
color: #666;
|
|
345
|
+
line-height: 1.6;
|
|
346
|
+
margin-bottom: 20rpx;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.ly-ai-quick {
|
|
350
|
+
background: #fff5f2;
|
|
351
|
+
color: #f84616;
|
|
352
|
+
font-size: 24rpx;
|
|
353
|
+
padding: 18rpx 24rpx;
|
|
354
|
+
border-radius: 12rpx;
|
|
355
|
+
margin-top: 16rpx;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.ly-ai-msg-row {
|
|
359
|
+
display: flex;
|
|
360
|
+
margin-bottom: 20rpx;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.ly-ai-msg-row-user {
|
|
364
|
+
justify-content: flex-end;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.ly-ai-msg-row-ai {
|
|
368
|
+
justify-content: flex-start;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.ly-ai-bubble {
|
|
372
|
+
max-width: 78%;
|
|
373
|
+
padding: 20rpx 24rpx;
|
|
374
|
+
border-radius: 16rpx;
|
|
375
|
+
word-break: break-all;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.ly-ai-bubble-user {
|
|
379
|
+
background: #f84616;
|
|
380
|
+
color: #fff;
|
|
381
|
+
border-bottom-right-radius: 4rpx;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.ly-ai-bubble-ai {
|
|
385
|
+
background: #fff;
|
|
386
|
+
color: #333;
|
|
387
|
+
border-bottom-left-radius: 4rpx;
|
|
388
|
+
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.ly-ai-loading-bubble {
|
|
392
|
+
color: #999;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.ly-ai-msg-image {
|
|
396
|
+
width: 200rpx;
|
|
397
|
+
height: 200rpx;
|
|
398
|
+
border-radius: 12rpx;
|
|
399
|
+
display: block;
|
|
400
|
+
margin-bottom: 12rpx;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.ly-ai-msg-text {
|
|
404
|
+
font-size: 28rpx;
|
|
405
|
+
line-height: 1.6;
|
|
406
|
+
white-space: pre-wrap;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.ly-ai-icon-pick-bar {
|
|
410
|
+
padding: 24rpx;
|
|
411
|
+
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
412
|
+
background: #fff;
|
|
413
|
+
border-top: 1rpx solid #eee;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.ly-ai-icon-pick-title {
|
|
417
|
+
font-size: 28rpx;
|
|
418
|
+
color: #333;
|
|
419
|
+
margin-bottom: 20rpx;
|
|
420
|
+
text-align: center;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.ly-ai-icon-pick-actions {
|
|
424
|
+
display: flex;
|
|
425
|
+
gap: 16rpx;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.ly-ai-icon-pick-btn {
|
|
429
|
+
flex: 1;
|
|
430
|
+
height: 80rpx;
|
|
431
|
+
line-height: 80rpx;
|
|
432
|
+
text-align: center;
|
|
433
|
+
background: #f84616;
|
|
434
|
+
color: #fff;
|
|
435
|
+
border-radius: 12rpx;
|
|
436
|
+
font-size: 28rpx;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.ly-ai-icon-pick-btn.cancel {
|
|
440
|
+
background: #f5f6f8;
|
|
441
|
+
color: #666;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.ly-ai-icon-pick-btn.disabled {
|
|
445
|
+
opacity: 0.5;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.ly-ai-image-preview-bar {
|
|
449
|
+
display: flex;
|
|
450
|
+
align-items: center;
|
|
451
|
+
padding: 12rpx 24rpx;
|
|
452
|
+
background: #fff;
|
|
453
|
+
border-top: 1rpx solid #eee;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.ly-ai-preview-image {
|
|
457
|
+
width: 96rpx;
|
|
458
|
+
height: 96rpx;
|
|
459
|
+
border-radius: 12rpx;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.ly-ai-preview-remove {
|
|
463
|
+
margin-left: 16rpx;
|
|
464
|
+
width: 48rpx;
|
|
465
|
+
height: 48rpx;
|
|
466
|
+
line-height: 44rpx;
|
|
467
|
+
text-align: center;
|
|
468
|
+
font-size: 36rpx;
|
|
469
|
+
color: #999;
|
|
470
|
+
background: #f5f6f8;
|
|
471
|
+
border-radius: 50%;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.ly-ai-input-bar {
|
|
475
|
+
display: flex;
|
|
476
|
+
align-items: center;
|
|
477
|
+
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
|
478
|
+
background: #fff;
|
|
479
|
+
border-top: 1rpx solid #eee;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.ly-ai-attach-btn {
|
|
483
|
+
width: 72rpx;
|
|
484
|
+
height: 72rpx;
|
|
485
|
+
line-height: 68rpx;
|
|
486
|
+
text-align: center;
|
|
487
|
+
font-size: 44rpx;
|
|
488
|
+
color: #666;
|
|
489
|
+
background: #f5f6f8;
|
|
490
|
+
border-radius: 50%;
|
|
491
|
+
margin-right: 16rpx;
|
|
492
|
+
flex-shrink: 0;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.ly-ai-attach-btn.disabled {
|
|
496
|
+
opacity: 0.5;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.ly-ai-chat-input {
|
|
500
|
+
flex: 1;
|
|
501
|
+
height: 72rpx;
|
|
502
|
+
background: #f5f6f8;
|
|
503
|
+
border-radius: 36rpx;
|
|
504
|
+
padding: 0 28rpx;
|
|
505
|
+
font-size: 28rpx;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.ly-ai-send-btn {
|
|
509
|
+
margin-left: 16rpx;
|
|
510
|
+
min-width: 120rpx;
|
|
511
|
+
height: 72rpx;
|
|
512
|
+
line-height: 72rpx;
|
|
513
|
+
text-align: center;
|
|
514
|
+
background: #f84616;
|
|
515
|
+
color: #fff;
|
|
516
|
+
border-radius: 36rpx;
|
|
517
|
+
font-size: 28rpx;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.ly-ai-send-btn.disabled {
|
|
521
|
+
opacity: 0.5;
|
|
522
|
+
}
|
|
523
|
+
</style>
|