Date: Sat, 11 Jan 2025 00:06:19 +0800
Subject: [PATCH 4/5] =?UTF-8?q?perf:=20webchat=E6=94=AF=E6=8C=81=E4=BC=A0?=
=?UTF-8?q?=E5=9B=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sources/webchat/webchat_adapter.py | 22 ++-
.../core/provider/sources/openai_source.py | 2 +
astrbot/dashboard/routes/chat.py | 43 ++++--
dashboard/src/views/ChatPage.vue | 146 +++++++++++++-----
4 files changed, 162 insertions(+), 51 deletions(-)
diff --git a/astrbot/core/platform/sources/webchat/webchat_adapter.py b/astrbot/core/platform/sources/webchat/webchat_adapter.py
index d78bfeadf..87e9d5f7b 100644
--- a/astrbot/core/platform/sources/webchat/webchat_adapter.py
+++ b/astrbot/core/platform/sources/webchat/webchat_adapter.py
@@ -1,10 +1,11 @@
import time
import asyncio
import uuid
+import os
from typing import Awaitable, Any
from astrbot.api.platform import Platform, AstrBotMessage, MessageMember, MessageType, PlatformMetadata
from astrbot.api.event import MessageChain
-from astrbot.api.message_components import * # noqa: F403
+from astrbot.api.message_components import Plain, Image # noqa: F403
from astrbot.api import logger
from astrbot.core import web_chat_queue, web_chat_back_queue
from .webchat_event import WebChatMessageEvent
@@ -29,6 +30,7 @@ class WebChatAdapter(Platform):
self.config = platform_config
self.settings = platform_settings
self.unique_session = platform_settings['unique_session']
+ self.imgs_dir = "data/webchat/imgs"
self.metadata = PlatformMetadata(
"webchat",
@@ -45,7 +47,7 @@ class WebChatAdapter(Platform):
await super().send_by_session(session, message_chain)
async def convert_message(self, data: tuple) -> AstrBotMessage:
- username, cid, message = data
+ username, cid, payload = data
abm = AstrBotMessage()
@@ -58,8 +60,20 @@ class WebChatAdapter(Platform):
abm.session_id = f"webchat!{username}!{cid}"
abm.message_id = str(uuid.uuid4())
- abm.message = [Plain(message)]
- message_str = message
+ abm.message = []
+
+ if payload['message']:
+ abm.message.append(Plain(payload['message']))
+ if payload['image_url']:
+ if isinstance(payload['image_url'], list):
+ for img in payload['image_url']:
+ abm.message.append(Image.fromFileSystem(os.path.join(self.imgs_dir, img)))
+ else:
+ abm.message.append(Image.fromFileSystem(os.path.join(self.imgs_dir, payload['image_url'])))
+
+ logger.debug(f"WebChatAdapter: {abm.message}")
+
+ message_str = payload['message']
abm.timestamp = int(time.time())
abm.message_str = message_str
abm.raw_message = data
diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py
index b0c9f0a58..7e601e958 100644
--- a/astrbot/core/provider/sources/openai_source.py
+++ b/astrbot/core/provider/sources/openai_source.py
@@ -209,6 +209,8 @@ class ProviderOpenAIOfficial(Provider):
image_path = await download_image_by_url(image_url)
image_data = await self.encode_image_bs64(image_path)
else:
+ if image_url.startswith("file:///"):
+ image_url = image_url.replace("file:///", "")
image_data = await self.encode_image_bs64(image_url)
user_content["content"].append({"type": "image_url", "image_url": {"url": image_data}})
return user_content
diff --git a/astrbot/dashboard/routes/chat.py b/astrbot/dashboard/routes/chat.py
index 735673976..cef7e563f 100644
--- a/astrbot/dashboard/routes/chat.py
+++ b/astrbot/dashboard/routes/chat.py
@@ -16,7 +16,8 @@ class ChatRoute(Route):
'/chat/conversations': ('GET', self.get_conversations),
'/chat/get_conversation': ('GET', self.get_conversation),
'/chat/delete_conversation': ('GET', self.delete_conversation),
- '/chat/get_file': ('GET', self.get_file)
+ '/chat/get_file': ('GET', self.get_file),
+ '/chat/post_image': ('POST', self.post_image)
}
self.db = db
self.register_routes()
@@ -32,25 +33,43 @@ class ChatRoute(Route):
return QuartResponse(f.read(), mimetype="image/jpeg")
except FileNotFoundError:
return Response().error("File not found").__dict__
+
+ async def post_image(self):
+ post_data = await request.files
+ if 'file' not in post_data:
+ return Response().error("Missing key: file").__dict__
+
+ file = post_data['file']
+ filename = str(uuid.uuid4()) + ".jpg"
+ path = os.path.join(self.imgs_dir, filename)
+ await file.save(path)
+
+ return Response().ok(data={
+ 'filename': filename
+ }).__dict__
async def chat(self):
username = g.get('username', 'guest')
post_data = await request.json
- if 'message' not in post_data:
- return Response().error("Missing key: message").__dict__
+ if 'message' not in post_data and 'image_url' not in post_data:
+ return Response().error("Missing key: message or image_url").__dict__
if 'conversation_id' not in post_data:
return Response().error("Missing key: conversation_id").__dict__
message = post_data['message']
conversation_id = post_data['conversation_id']
- if not message:
- return Response().error("Message is empty").__dict__
+ image_url = post_data.get('image_url')
+ if not message and not image_url:
+ return Response().error("Message and image_url are empty").__dict__
if not conversation_id:
return Response().error("conversation_id is empty").__dict__
- await web_chat_queue.put((username, conversation_id, message))
+ await web_chat_queue.put((username, conversation_id, {
+ 'message': message,
+ 'image_url': image_url # list
+ }))
async def stream():
ret = []
@@ -72,14 +91,14 @@ class ChatRoute(Route):
except BaseException as e:
print(e)
history = []
- history.append({
+
+ new_his = {
'type': 'user',
'message': message
- })
- # history.append({
- # 'type': 'bot',
- # 'message': ret
- # })
+ }
+ if image_url:
+ new_his['image_url'] = image_url
+ history.append(new_his)
for r in ret:
history.append({
'type': 'bot',
diff --git a/dashboard/src/views/ChatPage.vue b/dashboard/src/views/ChatPage.vue
index 8362d15f4..f4cb9b0e5 100644
--- a/dashboard/src/views/ChatPage.vue
+++ b/dashboard/src/views/ChatPage.vue
@@ -16,32 +16,27 @@ marked.setOptions({
- + 创建对话
-
-
-
-
- 新对话
- {{ formatDate(item.updated_at) }}
+ + 创建对话
-
+
+
+
+ 新对话
+ {{ formatDate(item.updated_at) }}
+
+
- 删除此对话
+ 删除此对话
-
+
@@ -49,11 +44,12 @@ marked.setOptions({
AstrBot ⭐
- 输入
- /help
- 获取帮助 😊
+ 输入
+ /help
+ 获取帮助 😊
-
+
{{ msg.message }}
+
+
+
![]()
+
+
@@ -72,19 +75,38 @@ marked.setOptions({
-
-
-
-
+
+
+
+
+
+
+
+
+
+
![]()
+
mdi-close-circle
+
+
@@ -104,15 +126,52 @@ export default {
prompt: '',
messages: [],
conversations: [],
- currCid: ''
+ currCid: '',
+ stagedImagesUrl: [],
+ loadingChat: false
}
},
mounted() {
this.getConversations();
+ let inputField = document.getElementById('input-field');
+ inputField.addEventListener('paste', this.handlePaste);
+
},
methods: {
+ async handlePaste(event) {
+ console.log('Pasting image...');
+ const items = event.clipboardData.items;
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].type.indexOf('image') !== -1) {
+ const file = items[i].getAsFile();
+ const formData = new FormData();
+ formData.append('file', file);
+
+ try {
+ const response = await axios.post('/api/chat/post_image', formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ 'Authorization': 'Bearer ' + localStorage.getItem('token')
+ }
+ });
+
+ const img = response.data.data.filename;
+ this.stagedImagesUrl.push(`/api/chat/get_file?filename=${img}`);
+
+ scrollToBottom();
+ } catch (err) {
+ console.error('Error uploading image:', err);
+ }
+ }
+ }
+ },
+
+ removeImage(index) {
+ this.stagedImagesUrl.splice(index, 1);
+ },
+
clearMessage() {
this.prompt = '';
},
@@ -126,7 +185,7 @@ export default {
getConversationMessages(cid) {
if (!cid[0])
return;
- axios.get('/api/chat/get_conversation?conversation_id='+cid[0]).then(response => {
+ axios.get('/api/chat/get_conversation?conversation_id=' + cid[0]).then(response => {
this.currCid = cid[0];
let message = JSON.parse(response.data.data.history);
for (let i = 0; i < message.length; i++) {
@@ -134,6 +193,11 @@ export default {
let img = message[i].message.replace('[IMAGE]', '');
message[i].message = `

`
}
+ if (message[i].image_url && message[i].image_url.length > 0) {
+ for (let j = 0; j < message[i].image_url.length; j++) {
+ message[i].image_url[j] = `/api/chat/get_file?filename=${message[i].image_url[j]}`;
+ }
+ }
}
this.messages = message;
}).catch(err => {
@@ -169,7 +233,7 @@ export default {
},
deleteConversation(cid) {
- axios.get('/api/chat/delete_conversation?conversation_id='+cid).then(response => {
+ axios.get('/api/chat/delete_conversation?conversation_id=' + cid).then(response => {
this.getConversations();
this.currCid = '';
this.messages = [];
@@ -185,7 +249,8 @@ export default {
this.messages.push({
type: 'user',
- message: this.prompt
+ message: this.prompt,
+ image_url: this.stagedImagesUrl
});
// let bot_resp = {
@@ -197,6 +262,14 @@ export default {
this.scrollToBottom();
+ let image_filenames = [];
+ for (let i = 0; i < this.stagedImagesUrl.length; i++) {
+ let img = this.stagedImagesUrl[i].replace('/api/chat/get_file?filename=', '');
+ image_filenames.push(img);
+ }
+
+ this.loadingChat = true;
+
fetch('/api/chat/send', {
method: 'POST',
@@ -204,10 +277,13 @@ export default {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('token')
},
- body: JSON.stringify({ message: this.prompt, conversation_id: this.currCid }) // 发送请求体
+ body: JSON.stringify({ message: this.prompt, conversation_id: this.currCid, image_url: image_filenames }) // 发送请求体
})
.then(response => {
this.prompt = '';
+ this.stagedImagesUrl = [];
+
+ this.loadingChat = false;
const reader = response.body.getReader(); // 获取流的 Reader
const decoder = new TextDecoder();
From fbdb1ae20825b06fdccef5ecb0da9666edfd23e1 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sat, 11 Jan 2025 00:14:08 +0800
Subject: [PATCH 5/5] chore: bump to v3.4.4
---
astrbot/core/config/default.py | 2 +-
changelogs/v3.4.4.md | 6 ++++++
2 files changed, 7 insertions(+), 1 deletion(-)
create mode 100644 changelogs/v3.4.4.md
diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py
index 3eb3519fb..43a010d45 100644
--- a/astrbot/core/config/default.py
+++ b/astrbot/core/config/default.py
@@ -2,7 +2,7 @@
如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。
"""
-VERSION = "3.4.3"
+VERSION = "3.4.4"
DB_PATH = "data/data_v3.db"
# 默认配置
diff --git a/changelogs/v3.4.4.md b/changelogs/v3.4.4.md
new file mode 100644
index 000000000..eaf240edf
--- /dev/null
+++ b/changelogs/v3.4.4.md
@@ -0,0 +1,6 @@
+# What's Changed
+
+1. 支持通过 /set
设置持久化的会话变量, 方便 Dify App 输入变量
+2. 管理面板支持 Web Chat
+3. 管理面板支持手动安装 Pip 库, 在 `控制台` 页中可找到
+