Compare commits

..

18 Commits

Author SHA1 Message Date
Soulter
bcebd0fb62 v3.4.33 2025-02-28 22:13:08 +08:00
Soulter
3817d3ca87 fix: 不记忆历史的会话 #630 2025-02-28 22:00:49 +08:00
Soulter
4dd714e814 Merge pull request #648 from Soulter/feat-edge-tts
feat: 添加对于 edge-tts 支持 #471
2025-02-28 21:46:50 +08:00
Soulter
61e8bb49ec chore: Cleanup 2025-02-28 21:33:03 +08:00
Soulter
103dcd3761 Merge pull request #645 from Quirrel-zh/master
修复&优化
2025-02-28 21:24:54 +08:00
Soulter
54ac135fc8 Merge pull request #642 from CAICAIIs/fix_bug
fix bug #621
2025-02-28 21:12:37 +08:00
Soulter
86582809fc Merge pull request #641 from Soulter/perf-plugin-search
perf: 插件市场非列表视图能够正常搜索 #640
2025-02-28 21:11:43 +08:00
Soulter
974d648f19 Merge pull request #638 from Soulter/perf-record
perf: 优化网页录音 #283
2025-02-28 21:10:09 +08:00
崔永亮
a79afc9597 feat: 添加对于 edge-tts 支持 #471 2025-02-28 16:57:44 +08:00
quirrel-zh
e4883241d9 🐛fixed:
1、由于tooltip移入时会消失无法点击其中链接,更改为按钮出发
	2、修复了由于已安装插件与插件市场中name不一致或repo链接大小写不一致导致的检测不到是否安装或有更新的bug
2025-02-28 15:58:57 +08:00
yxw
babf223745 fix bug #621 2025-02-28 14:22:59 +08:00
崔永亮
c7d91730b6 perf: 插件市场非列表视图能够正常搜索 #640 2025-02-28 14:18:10 +08:00
Soulter
71246b65c9 Update README.md 2025-02-28 14:06:31 +08:00
Soulter
50076b647e Merge pull request #639 from CAICAIIs/master
docs: add English README
2025-02-28 14:06:00 +08:00
yxw
a1a788dce8 docs: add English README 2025-02-28 13:39:45 +08:00
崔永亮
a611b4f346 perf: 优化网页录音 #283
1. 为防止输入一大堆 k,改 k 键为 Ctrl 键;
2. 改为长按录音,松手结束;
3. 为防止误触改为只有点击输入框之后才会生效
2025-02-28 13:22:55 +08:00
Soulter
7f6ed674b4 ‼️🐛 fix: 修复钩子函数无法终止事件传播的问题;修复某些情况下终止事件传播后仍然会请求 LLM 的问题 2025-02-28 00:02:17 +08:00
Soulter
aa3cfd887a fix: correct STT model path and improve logging in provider manager and pip installer 2025-02-27 11:33:53 +08:00
14 changed files with 409 additions and 43 deletions

View File

@@ -18,6 +18,7 @@ _✨ 易上手的多平台 LLM 聊天机器人及开发框架 ✨_
![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.soulter.top%2Fastrbot%2Fstats&query=v&label=7%E6%97%A5%E6%B6%88%E6%81%AF%E4%B8%8A%E8%A1%8C%E9%87%8F&cacheSeconds=3600)
[![codecov](https://codecov.io/gh/Soulter/AstrBot/graph/badge.svg?token=FF3P5967B8)](https://codecov.io/gh/Soulter/AstrBot)
<a href="https://github.com/Soulter/AstrBot/blob/master/README_en.md">English</a>
<a href="https://github.com/Soulter/AstrBot/blob/master/README_ja.md">日本語</a>
<a href="https://astrbot.app/">查看文档</a>
<a href="https://github.com/Soulter/AstrBot/issues">问题提交</a>

164
README_en.md Normal file
View File

@@ -0,0 +1,164 @@
<p align="center">
![6e1279651f16d7fdf4727558b72bbaf1](https://github.com/user-attachments/assets/ead4c551-fc3c-48f7-a6f7-afbfdb820512)
</p>
<div align="center">
_✨ Easy-to-use Multi-platform LLM Chatbot & Development Framework ✨_
<a href="https://trendshift.io/repositories/12875" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12875" alt="Soulter%2FAstrBot | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Soulter/AstrBot)](https://github.com/Soulter/AstrBot/releases/latest)
<img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="python">
<a href="https://hub.docker.com/r/soulter/astrbot"><img alt="Docker pull" src="https://img.shields.io/docker/pulls/soulter/astrbot.svg"/></a>
<img alt="Static Badge" src="https://img.shields.io/badge/QQ群-630166526-purple">
[![wakatime](https://wakatime.com/badge/user/915e5316-99c6-4563-a483-ef186cf000c9/project/018e705a-a1a7-409a-a849-3013485e6c8e.svg)](https://wakatime.com/badge/user/915e5316-99c6-4563-a483-ef186cf000c9/project/018e705a-a1a7-409a-a849-3013485e6c8e)
![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.soulter.top%2Fastrbot%2Fstats&query=v&label=7%E6%97%A5%E6%B6%88%E6%81%AF%E4%B8%8A%E8%A1%8C%E9%87%8F&cacheSeconds=3600)
[![codecov](https://codecov.io/gh/Soulter/AstrBot/graph/badge.svg?token=FF3P5967B8)](https://codecov.io/gh/Soulter/AstrBot)
<a href="https://astrbot.app/">Documentation</a>
<a href="https://github.com/Soulter/AstrBot/issues">Issue Tracking</a>
</div>
AstrBot is a loosely coupled, asynchronous chatbot and development framework that supports multi-platform deployment, featuring an easy-to-use plugin system and comprehensive Large Language Model (LLM) integration capabilities.
## ✨ Key Features
1. **LLM Conversations** - Supports various LLMs including OpenAI API, Google Gemini, Llama, Deepseek, ChatGLM, etc. Enables local model deployment via Ollama/LLMTuner. Features multi-turn dialogues, personality contexts, multimodal capabilities (image understanding), and speech-to-text (Whisper).
2. **Multi-platform Integration** - Supports QQ (OneBot), QQ Channels, WeChat (Gewechat), Feishu, and Telegram. Planned support for DingTalk, Discord, WhatsApp, and Xiaomi Smart Speakers. Includes rate limiting, whitelisting, keyword filtering, and Baidu content moderation.
3. **Agent Capabilities** - Native support for code execution, natural language TODO lists, web search. Integrates with [Dify Platform](https://astrbot.app/others/dify.html) for easy access to Dify assistants/knowledge bases/workflows.
4. **Plugin System** - Optimized plugin mechanism with minimal development effort. Supports multiple installed plugins.
5. **Web Dashboard** - Visual configuration management, plugin controls, logging, and WebChat interface for direct LLM interaction.
6. **High Stability & Modularity** - Event bus and pipeline architecture ensures high modularization and loose coupling.
> [!TIP]
> Dashboard Demo: [https://demo.astrbot.app/](https://demo.astrbot.app/)
> Username: `astrbot`, Password: `astrbot` (LLM not configured for chat page)
## ✨ Deployment
#### Docker Deployment
See docs: [Deploy with Docker](https://astrbot.app/deploy/astrbot/docker.html#docker-deployment)
#### Windows Installer
Requires Python (>3.10). See docs: [Windows Installer Guide](https://astrbot.app/deploy/astrbot/windows.html)
#### Replit Deployment
[![Run on Repl.it](https://repl.it/badge/github/Soulter/AstrBot)](https://repl.it/github/Soulter/AstrBot)
#### CasaOS Deployment
Community-contributed method.
See docs: [CasaOS Deployment](https://astrbot.app/deploy/astrbot/casaos.html)
#### Manual Deployment
See docs: [Source Code Deployment](https://astrbot.app/deploy/astrbot/cli.html)
## ⚡ Platform Support
| Platform | Status | Details | Message Types |
| -------------------------------------------------------------- | ------ | ------------------- | ------------------- |
| QQ (Official Bot) | ✔ | Private/Group chats | Text, Images |
| QQ (OneBot) | ✔ | Private/Group chats | Text, Images, Voice |
| WeChat (Personal) | ✔ | Private/Group chats | Text, Images, Voice |
| [Telegram](https://github.com/Soulter/astrbot_plugin_telegram) | ✔ | Private/Group chats | Text, Images |
| [WeChat Work](https://github.com/Soulter/astrbot_plugin_wecom) | ✔ | Private chats | Text, Images, Voice |
| Feishu | ✔ | Group chats | Text, Images |
| WeChat Open Platform | 🚧 | Planned | - |
| Discord | 🚧 | Planned | - |
| WhatsApp | 🚧 | Planned | - |
| Xiaomi Speakers | 🚧 | Planned | - |
# 🦌 Roadmap
> [!TIP]
> Suggestions welcome via Issues <3
- [ ] Ensure feature parity across all platform adapters
- [ ] Optimize plugin APIs
- [ ] Add default TTS services (e.g., GPT-Sovits)
- [ ] Enhance chat features with persistent memory
- [ ] i18n Planning
## ❤️ Contributions
All Issues/PRs welcome! Simply submit your changes to this project :)
For major features, please discuss via Issues first.
## 🌟 Support
- Star this project!
- Support via [Afdian](https://afdian.com/a/soulter)
- WeChat support: [QR Code](https://drive.soulter.top/f/pYfA/d903f4fa49a496fda3f16d2be9e023b5.png)
## ✨ Demos
> [!NOTE]
> Code executor file I/O currently tested with Napcat(QQ)/Lagrange(QQ)
<div align='center'>
<img src="https://github.com/user-attachments/assets/4ee688d9-467d-45c8-99d6-368f9a8a92d8" width="600">
_✨ Docker-based Sandboxed Code Executor (Beta) ✨_
<img src="https://github.com/user-attachments/assets/0378f407-6079-4f64-ae4c-e97ab20611d2" height=500>
_✨ Multimodal Input, Web Search, Text-to-Image ✨_
<img src="https://github.com/user-attachments/assets/8ec12797-e70f-460a-959e-48eca39ca2bb" height=100>
_✨ Natural Language TODO Lists ✨_
<img src="https://github.com/user-attachments/assets/e137a9e1-340a-4bf2-bb2b-771132780735" height=150>
<img src="https://github.com/user-attachments/assets/480f5e82-cf6a-4955-a869-0d73137aa6e1" height=150>
_✨ Plugin System Showcase ✨_
<img src="https://github.com/user-attachments/assets/592a8630-14c7-4e06-b496-9c0386e4f36c" width=600>
_✨ Web Dashboard ✨_
![webchat](https://drive.soulter.top/f/vlsA/ezgif-5-fb044b2542.gif)
_✨ Built-in Web Chat Interface ✨_
</div>
## ⭐ Star History
> [!TIP]
> If this project helps you, please give it a star <3
<div align="center">
[![Star History Chart](https://api.star-history.com/svg?repos=soulter/astrbot&type=Date)](https://star-history.com/#soulter/astrbot&Date)
</div>
## Disclaimer
1. Licensed under `AGPL-v3`.
2. WeChat integration uses [Gewechat](https://github.com/Devo919/Gewechat). Use at your own risk with non-critical accounts.
3. Users must comply with local laws and regulations.
<!-- ## ✨ ATRI [Beta]
Available as plugin: [astrbot_plugin_atri](https://github.com/Soulter/astrbot_plugin_atri)
1. Qwen1.5-7B-Chat Lora model fine-tuned with ATRI character data
2. Long-term memory
3. Meme understanding & responses
4. TTS integration
-->
_私は、高性能ですから!_

View File

@@ -2,7 +2,7 @@
如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。
"""
VERSION = "3.4.32"
VERSION = "3.4.33"
DB_PATH = "data/data_v3.db"
# 默认配置
@@ -588,7 +588,7 @@ CONFIG_METADATA_2 = {
"enable": False,
"id": "sensevoice",
"type": "sensevoice_stt_selfhost",
"stt_model": "icc/SenseVoiceSmall",
"stt_model": "iic/SenseVoiceSmall",
"is_emotion": False,
},
"OpenAI_TTS(API)": {
@@ -601,6 +601,13 @@ CONFIG_METADATA_2 = {
"openai-tts-voice": "alloy",
"timeout": "20",
},
"Edge_TTS": {
"id": "edge_tts",
"type": "edge_tts",
"enable": False,
"edge-tts-voice": "zh-CN-XiaoxiaoNeural",
"timeout": 20,
},
"FishAudio_TTS(API)": {
"id": "fishaudio_tts",
"type": "fishaudio_tts_api",

View File

@@ -75,6 +75,10 @@ class LLMRequestSubStage(Stage):
except BaseException:
logger.error(traceback.format_exc())
if event.is_stopped():
logger.info(f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。")
return
if isinstance(req.contexts, str):
req.contexts = json.loads(req.contexts)
@@ -92,6 +96,11 @@ class LLMRequestSubStage(Stage):
await handler.handler(event, llm_response)
except BaseException:
logger.error(traceback.format_exc())
if event.is_stopped():
logger.info(f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。")
return
# 保存到历史记录
await self._save_to_history(event, req, llm_response)
@@ -142,7 +151,7 @@ class LLMRequestSubStage(Stage):
return
async def _save_to_history(self, event: AstrMessageEvent, req: ProviderRequest, llm_response: LLMResponse):
if not req or not req.conversation or not llm_response or not req.contexts:
if not req or not req.conversation or not llm_response:
return
if llm_response.role == "assistant":

View File

@@ -95,5 +95,9 @@ class RespondStage(Stage):
await handler.handler(event)
except BaseException:
logger.error(traceback.format_exc())
if event.is_stopped():
logger.info(f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。")
return
event.clear_result()

View File

@@ -70,6 +70,10 @@ class ResultDecorateStage(Stage):
logger.debug(f"hook(on_decorating_result) -> {star_map[handler.handler_module_path].name} - {handler.handler_name} 将消息结果清空。")
except BaseException:
logger.error(traceback.format_exc())
if event.is_stopped():
logger.info(f"{star_map[handler.handler_module_path].name} - {handler.handler_name} 终止了事件传播。")
return
# 需要再获取一次。插件可能直接对 chain 进行了替换。
result = event.get_result()

View File

@@ -27,6 +27,9 @@ class PipelineScheduler():
logger.debug(f"阶段 {stage.__class__ .__name__} 已终止事件传播。")
break
await self._process_stages(event, i + 1)
if event.is_stopped():
logger.debug(f"阶段 {stage.__class__ .__name__} 已终止事件传播。")
break
else:
await coro

View File

@@ -75,7 +75,7 @@ class AiocqhttpAdapter(Platform):
else:
abm.type = MessageType.FRIEND_MESSAGE
if self.unique_session and abm.type == MessageType.GROUP_MESSAGE:
abm.session_id = abm.sender.user_id + "_" + str(event.group_id)
abm.session_id = str(abm.sender.user_id) + "_" + str(event.group_id)
abm.message_str = ''
abm.message = []
abm.timestamp = int(time.time())

View File

@@ -152,6 +152,8 @@ class ProviderManager():
from .sources.whisper_selfhosted_source import ProviderOpenAIWhisperSelfHost as ProviderOpenAIWhisperSelfHost
case "openai_tts_api":
from .sources.openai_tts_api_source import ProviderOpenAITTSAPI as ProviderOpenAITTSAPI
case "edge_tts":
from .sources.edge_tts_source import ProviderEdgeTTS as ProviderEdgeTTS
case "fishaudio_tts_api":
from .sources.fishaudio_tts_api_source import ProviderFishAudioTTSAPI as ProviderFishAudioTTSAPI
except (ImportError, ModuleNotFoundError) as e:
@@ -219,7 +221,7 @@ class ProviderManager():
self.inst_map[provider_config['id']] = inst
except Exception as e:
traceback.print_exc()
logger.error(traceback.format_exc())
logger.error(f"实例化 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}")
async def reload(self, provider_config: dict):

View File

@@ -0,0 +1,90 @@
import uuid
import os
import edge_tts
import subprocess
from ..provider import TTSProvider
from ..entites import ProviderType
from ..register import register_provider_adapter
from astrbot.core import logger
"""
edge_tts 方式能够免费、快速生成语音使用需要先安装edge-tts库
```
pip install edge_tts
```
Windows 如果提示找不到指定文件,以管理员身份运行命令行窗口,然后再次运行 AstrBot
"""
@register_provider_adapter("edge_tts", "Microsoft Edge TTS", provider_type=ProviderType.TEXT_TO_SPEECH)
class ProviderEdgeTTS(TTSProvider):
def __init__(
self,
provider_config: dict,
provider_settings: dict,
) -> None:
super().__init__(provider_config, provider_settings)
# 设置默认语音,如果没有指定则使用中文小萱
self.voice = provider_config.get("edge-tts-voice", "zh-CN-XiaoxiaoNeural")
self.rate = provider_config.get("rate", None)
self.volume = provider_config.get("volume", None)
self.pitch = provider_config.get("pitch", None)
self.timeout = provider_config.get("timeout", 30)
self.set_model("edge_tts")
async def get_audio(self, text: str) -> str:
os.makedirs("data/temp", exist_ok=True)
mp3_path = f'data/temp/edge_tts_temp_{uuid.uuid4()}.mp3'
wav_path = f'data/temp/edge_tts_{uuid.uuid4()}.wav'
# 构建Edge TTS参数
kwargs = {"text": text, "voice": self.voice}
if self.rate:
kwargs["rate"] = self.rate
if self.volume:
kwargs["volume"] = self.volume
if self.pitch:
kwargs["pitch"] = self.pitch
try:
communicate = edge_tts.Communicate(**kwargs)
await communicate.save(mp3_path)
# 使用ffmpeg将MP3转换为标准WAV格式
_ = subprocess.run([
"ffmpeg",
"-y", # 覆盖输出文件
"-i", mp3_path, # 输入文件
"-acodec", "pcm_s16le", # 16位PCM编码
"-ar", "24000", # 采样率24kHz (适合微信语音)
"-ac", "1", # 单声道
wav_path # 输出文件
], capture_output=True, check=True)
os.remove(mp3_path)
if os.path.exists(wav_path) and os.path.getsize(wav_path) > 0:
return wav_path
else:
logger.error("生成的WAV文件不存在或为空")
raise RuntimeError("生成的WAV文件不存在或为空")
except subprocess.CalledProcessError as e:
logger.error(f"FFmpeg转换失败: {e.stderr.decode() if e.stderr else str(e)}")
try:
if os.path.exists(mp3_path):
os.remove(mp3_path)
except:
pass
raise RuntimeError(f"FFmpeg转换失败: {str(e)}")
except Exception as e:
logger.error(f"音频生成失败: {str(e)}")
try:
if os.path.exists(mp3_path):
os.remove(mp3_path)
except:
pass
raise RuntimeError(f"音频生成失败: {str(e)}")

View File

@@ -1,6 +1,7 @@
import logging
from pip import main as pip_main
logger = logging.getLogger("astrbot")
class PipInstaller():
def __init__(self, pip_install_arg: str):
self.pip_install_arg = pip_install_arg
@@ -20,7 +21,7 @@ class PipInstaller():
if self.pip_install_arg:
args.extend(self.pip_install_arg.split())
print(f"Pip 包管理器: {' '.join(args)}")
logger.info(f"Pip 包管理器: pip {' '.join(args)}")
result_code = pip_main(args)

13
changelogs/v3.4.33.md Normal file
View File

@@ -0,0 +1,13 @@
# What's Changed
1. ✨ 新增: add English README by @CAICAIIs
2. ✨ 新增: perf: 优化网页录音 [#283](https://github.com/Soulter/AstrBot/issues/283) by @Fridemn
3. ✨ 新增: 添加对于 Edge-TTS 的支持 [#471](https://github.com/Soulter/AstrBot/issues/471) by @Fridemn
4. ⚡ 优化: 为防止输入一大堆 k改 k 键为 Ctrl 键;改为长按录音,松手结束;为防止误触改为只有点击输入框之后才会生效 by @Fridemn
5. ⚡ 优化: 插件市场非列表视图能够正常搜索 [#640](https://github.com/Soulter/AstrBot/issues/640) by @Fridemn
6. ⚡ 优化: 插件市场帮助按钮 tooltip 移入时会消失无法点击其中链接,更改为按钮触发 by @Quirrel-zh
7. ‼️‼️ 🐛 修复: v3.4.32 无法记忆历史的会话 [#630](https://github.com/Soulter/AstrBot/issues/630)
8. ‼️🐛 修复: 钩子函数无法终止事件传播的问题;修复某些情况下终止事件传播后仍然会请求 LLM 的问题
9. ‼️🐛 修复: OneBot V11 通知类事件某些情况无法回复问题 by @CAICAIIs
10. 🐛 修复: Correct STT model path and improve logging in provider manager and pip installer
11. 🐛 修复: 由于已安装插件与插件市场中 name 不一致或 repo 链接大小写不一致导致的检测不到是否安装或有更新 by @Quirrel-zh

View File

@@ -60,10 +60,10 @@ marked.setOptions({
<span>获取帮助 😊</span>
</div>
<div style="margin-top: 8px; color: #aaa;">
<span>按</span>
<span>按</span>
<span
style="background-color: #eee; padding-left: 4px; padding-right: 4px; margin: 2px; border-radius: 4px;">K</span>
<span>开始语音 🎤</span>
style="background-color: #eee; padding-left: 4px; padding-right: 4px; margin: 2px; border-radius: 4px;">Ctrl</span>
<span>录制语音 🎤</span>
</div>
<div style="margin-top: 8px; color: #aaa;">
<span>按</span>
@@ -112,7 +112,8 @@ marked.setOptions({
<v-text-field id="input-field" variant="outlined" v-model="prompt" :label="inputFieldLabel"
placeholder="Start typing..." loading clear-icon="mdi-close-circle" clearable
@click:clear="clearMessage" style="width: 100%; max-width: 850px;">
@click:clear="clearMessage" style="width: 100%; max-width: 850px;"
@keydown="handleInputKeyDown">
<template v-slot:loader>
<v-progress-linear :active="loadingChat" height="6"
indeterminate></v-progress-linear>
@@ -189,7 +190,12 @@ export default {
status: {},
statusText: '',
eventSource: null
eventSource: null,
// 添加Ctrl键长按相关变量
ctrlKeyDown: false,
ctrlKeyTimer: null,
ctrlKeyLongPressThreshold: 300 // 长按阈值,单位毫秒
}
},
@@ -205,11 +211,9 @@ export default {
this.sendMessage();
}
}.bind(this));
document.addEventListener('keydown', function (e) {
if (e.keyCode == 75) {
this.isRecording ? this.stopRecording() : this.startRecording();
}
}.bind(this));
// 添加keyup事件监听
document.addEventListener('keyup', this.handleInputKeyUp);
},
beforeUnmount() {
@@ -218,6 +222,9 @@ export default {
this.eventSource.cancel();
console.log('SSE连接已断开');
}
// 移除keyup事件监听
document.removeEventListener('keyup', this.handleInputKeyUp);
},
methods: {
@@ -531,7 +538,40 @@ export default {
const container = this.$refs.messageContainer;
container.scrollTop = container.scrollHeight;
});
}
},
handleInputKeyDown(e) {
if (e.keyCode === 17) { // Ctrl键
// 防止重复触发
if (this.ctrlKeyDown) return;
this.ctrlKeyDown = true;
// 设置定时器识别长按
this.ctrlKeyTimer = setTimeout(() => {
if (this.ctrlKeyDown && !this.isRecording) {
this.startRecording();
}
}, this.ctrlKeyLongPressThreshold);
}
},
handleInputKeyUp(e) {
if (e.keyCode === 17) { // Ctrl键
this.ctrlKeyDown = false;
// 清除定时器
if (this.ctrlKeyTimer) {
clearTimeout(this.ctrlKeyTimer);
this.ctrlKeyTimer = null;
}
// 如果正在录音,停止录音
if (this.isRecording) {
this.stopRecording();
}
}
},
},
}

View File

@@ -4,7 +4,6 @@ import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
import AstrBotConfig from '@/components/shared/AstrBotConfig.vue';
import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue';
import axios from 'axios';
import { max } from 'date-fns';
</script>
@@ -94,12 +93,15 @@ import { max } from 'date-fns';
🧩 插件市场
<v-btn icon size="small" style="margin-left: 8px" variant="plain">
<v-btn icon size="small" style="margin-left: 8px" variant="plain" @click="jumpToPluginMarket()">
<v-icon size="small">mdi-help</v-icon>
<v-tooltip activator="parent" location="start">
如无法显示请打开 <a href="https://soulter.github.io/AstrBot_Plugins_Collection/plugins.json">链接</a> 复制想安装插件对应的
<span>
如无法显示请单击此按钮跳转至插件市场复制想安装插件对应的
`repo`
链接然后点击右下角 + 号安装或打开链接下载压缩包安装
</span>
</v-tooltip>
</v-btn>
@@ -141,7 +143,7 @@ import { max } from 'date-fns';
</template>
<template v-else>
<v-row style="margin: 8px;">
<v-col cols="12" md="6" lg="3" v-for="plugin in pluginMarketData">
<v-col cols="12" md="6" lg="3" v-for="plugin in filteredPluginMarketData">
<ExtensionCard :key="plugin.name" :title="plugin.name" :link="plugin.repo" style="margin-bottom: 4px;">
<div style="min-height: 130px; max-height: 130px; overflow: hidden;">
<p style="font-weight: bold;">By @{{ plugin.author }}</p>
@@ -380,6 +382,17 @@ export default {
alreadyCheckUpdate: false
}
},
computed: {
filteredPluginMarketData() {
if (!this.marketSearch) {
return this.pluginMarketData;
}
const search = this.marketSearch.toLowerCase();
return this.pluginMarketData.filter(plugin =>
plugin.name.toLowerCase().includes(search)
);
}
},
mounted() {
this.getExtensions();
this.fetchPluginCollection();
@@ -390,6 +403,9 @@ export default {
});
},
methods: {
jumpToPluginMarket() {
window.open('https://soulter.github.io/AstrBot_Plugins_Collection/plugins.json', '_blank');
},
toast(message, success) {
this.snack_message = message;
this.snack_show = true;
@@ -422,21 +438,34 @@ export default {
},
checkUpdate() {
// 遍历 extension_data 和 pluginMarketData检查是否有更新\
for (let i = 0; i < this.extension_data.data.length; i++) {
for (let j = 0; j < this.pluginMarketData.length; j++) {
console.log(this.extension_data.data[i].repo, this.pluginMarketData[j].repo);
if (this.extension_data.data[i].repo === this.pluginMarketData[j].repo ||
this.extension_data.data[i].name === this.pluginMarketData[j].name) {
this.extension_data.data[i].online_version = this.pluginMarketData[j].version;
if (this.extension_data.data[i].version !== this.pluginMarketData[j].version && this.pluginMarketData[j].version !== "未知") {
this.extension_data.data[i].has_update = true;
} else {
this.extension_data.data[i].has_update = false;
}
}
// 创建在线插件的map
const onlinePluginsMap = new Map();
const onlinePluginsNameMap = new Map();
// 将在线插件信息存储到map中
this.pluginMarketData.forEach(plugin => {
if (plugin.repo) {
onlinePluginsMap.set(plugin.repo.toLowerCase(), plugin);
}
}
onlinePluginsNameMap.set(plugin.name, plugin);
});
// 遍历本地插件列表
this.extension_data.data.forEach(extension => {
// 通过repo或name查找在线版本
const repoKey = extension.repo?.toLowerCase();
const onlinePlugin = repoKey ? onlinePluginsMap.get(repoKey) : null;
const onlinePluginByName = onlinePluginsNameMap.get(extension.name);
const matchedPlugin = onlinePlugin || onlinePluginByName;
if (matchedPlugin) {
extension.online_version = matchedPlugin.version;
extension.has_update = extension.version !== matchedPlugin.version &&
matchedPlugin.version !== "未知";
} else {
extension.has_update = false;
}
});
},
newExtension() {
@@ -613,15 +642,14 @@ export default {
});
},
checkAlreadyInstalled() {
// 创建已安装插件的仓库和名称集合 统一格式
const installedRepos = new Set(this.extension_data.data.map(ext => ext.repo?.toLowerCase()));
const installedNames = new Set(this.extension_data.data.map(ext => ext.name));
// 遍历检查安装状态
for (let i = 0; i < this.pluginMarketData.length; i++) {
this.pluginMarketData[i].installed = false;
}
for (let i = 0; i < this.pluginMarketData.length; i++) {
for (let j = 0; j < this.extension_data.data.length; j++) {
if (this.pluginMarketData[i].repo === this.extension_data.data[j].repo || this.pluginMarketData[i].name === this.extension_data.data[j].name) {
this.pluginMarketData[i].installed = true;
}
}
const plugin = this.pluginMarketData[i];
plugin.installed = installedRepos.has(plugin.repo?.toLowerCase()) || installedNames.has(plugin.name);
}
// 将已安装的插件移动到最后面