Compare commits

...

5 Commits

Author SHA1 Message Date
Soulter dc526273f0 fix 2026-01-03 16:25:55 +08:00
Soulter fc42db40ce chore: bump version to 4.10.6 2026-01-02 12:14:59 +08:00
Soulter e413a002c1 perf: list view mode toggle with localStorage support in ExtensionPage (#4288)
closes: #4253
2026-01-02 11:59:41 +08:00
tjc66666666 6437d759a3 fix: reasoning content inject for openai api (#4284) 2026-01-02 01:09:28 +08:00
Soulter c758b2d888 feat: use shell globbing to match umop config router (#4270)
* feat: use shell globbing to match umop config router

* rf

* fix: use fnmatchcase for case-sensitive matching in UmopConfigRouter
2025-12-31 23:10:12 +08:00
8 changed files with 81 additions and 48 deletions
+1 -1
View File
@@ -1 +1 @@
__version__ = "4.10.5"
__version__ = "4.10.6"
+1 -1
View File
@@ -5,7 +5,7 @@ from typing import Any, TypedDict
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
VERSION = "4.10.5"
VERSION = "4.10.6"
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
WEBHOOK_SUPPORTED_PLATFORMS = [
@@ -378,7 +378,8 @@ class ProviderOpenAIOfficial(Provider):
new_content.append(part)
message["content"] = new_content
# reasoning key is "reasoning_content"
message["reasoning_content"] = reasoning_content
if reasoning_content:
message["reasoning_content"] = reasoning_content
async def _handle_api_error(
self,
+3 -1
View File
@@ -1,3 +1,5 @@
import fnmatch
from astrbot.core.utils.shared_preferences import SharedPreferences
@@ -30,7 +32,7 @@ class UmopConfigRouter:
if len(p1_ls) != 3 or len(p2_ls) != 3:
return False # 非法格式
return all(p == "" or p == "*" or p == t for p, t in zip(p1_ls, p2_ls))
return all(p == "" or fnmatch.fnmatchcase(t, p) for p, t in zip(p1_ls, p2_ls))
def get_conf_id_for_umop(self, umo: str) -> str | None:
"""根据 UMO 获取对应的配置文件 ID
+30 -13
View File
@@ -25,6 +25,8 @@ class SharedPreferences:
t = threading.Thread(target=self._sync_loop.run_forever, daemon=True)
t.start()
self._write_lock = threading.Lock()
async def get_async(
self,
scope: str,
@@ -167,8 +169,11 @@ class SharedPreferences:
raise ValueError(
"scope_id and key cannot be None when getting a specific preference.",
)
scope = scope or "unknown"
scope_id = scope_id or "unknown"
result = asyncio.run_coroutine_threadsafe(
self.get_async(scope or "unknown", scope_id or "unknown", key, default),
self.get_async(scope, scope_id, key, default),
self._sync_loop,
).result()
@@ -190,21 +195,33 @@ class SharedPreferences:
def put(self, key, value, scope: str | None = None, scope_id: str | None = None):
"""设置偏好设置(已弃用)"""
asyncio.run_coroutine_threadsafe(
self.put_async(scope or "unknown", scope_id or "unknown", key, value),
self._sync_loop,
).result()
scope = scope or "unknown"
scope_id = scope_id or "unknown"
with self._write_lock:
asyncio.run_coroutine_threadsafe(
self.put_async(scope, scope_id, key, value),
self._sync_loop,
).result()
def remove(self, key, scope: str | None = None, scope_id: str | None = None):
"""删除偏好设置(已弃用)"""
asyncio.run_coroutine_threadsafe(
self.remove_async(scope or "unknown", scope_id or "unknown", key),
self._sync_loop,
).result()
scope = scope or "unknown"
scope_id = scope_id or "unknown"
with self._write_lock:
asyncio.run_coroutine_threadsafe(
self.remove_async(scope, scope_id, key),
self._sync_loop,
).result()
def clear(self, scope: str | None = None, scope_id: str | None = None):
"""清空偏好设置(已弃用)"""
asyncio.run_coroutine_threadsafe(
self.clear_async(scope or "unknown", scope_id or "unknown"),
self._sync_loop,
).result()
scope = scope or "unknown"
scope_id = scope_id or "unknown"
with self._write_lock:
asyncio.run_coroutine_threadsafe(
self.clear_async(scope, scope_id),
self._sync_loop,
).result()
+11
View File
@@ -0,0 +1,11 @@
## What's Changed
hotfix of v4.10.4
fix:
1. ‼️ 部分情况下使用 OpenAI 接口报错与 reasoning_content 有关的问题;
feat:
1. WebUI 已安装插件页支持记忆视图类型(列表/卡片),列表视图显示插件的人类友好名称和 logo。
+32 -30
View File
@@ -78,13 +78,19 @@ const readmeDialog = reactive({
});
// 新增变量支持列表视图
const isListView = ref(false);
// 从 localStorage 恢复显示模式,默认为 false(卡片视图)
const getInitialListViewMode = () => {
if (typeof window !== 'undefined' && window.localStorage) {
return localStorage.getItem('pluginListViewMode') === 'true';
}
return false;
};
const isListView = ref(getInitialListViewMode());
const pluginSearch = ref("");
const loading_ = ref(false);
// 分页相关
const currentPage = ref(1);
const itemsPerPage = ref(6); // 每页显示6个卡片 (2行 x 3列,避免滚动)
// 危险插件确认对话框
const dangerConfirmDialog = ref(false);
@@ -113,7 +119,6 @@ const uploadTab = ref('file');
const showPluginFullName = ref(false);
const marketSearch = ref("");
const debouncedMarketSearch = ref("");
const filterKeys = ['name', 'desc', 'author'];
const refreshingMarket = ref(false);
const sortBy = ref('default'); // default, stars, author, updated
const sortOrder = ref('desc'); // desc (降序) or asc (升序)
@@ -162,18 +167,6 @@ const pluginHeaders = computed(() => [
]);
// 插件市场表头
const pluginMarketHeaders = computed(() => [
{ title: tm('table.headers.name'), key: 'name', maxWidth: '200px' },
{ title: tm('table.headers.description'), key: 'desc', maxWidth: '250px' },
{ title: tm('table.headers.author'), key: 'author', maxWidth: '90px' },
{ title: tm('table.headers.stars'), key: 'stars', maxWidth: '80px' },
{ title: tm('table.headers.lastUpdate'), key: 'updated_at', maxWidth: '100px' },
{ title: tm('table.headers.tags'), key: 'tags', maxWidth: '100px' },
{ title: tm('table.headers.actions'), key: 'actions', sortable: false }
]);
// 过滤要显示的插件
const filteredExtensions = computed(() => {
const data = Array.isArray(extension_data?.data) ? extension_data.data : [];
@@ -197,9 +190,6 @@ const filteredPlugins = computed(() => {
});
});
const pinnedPlugins = computed(() => {
return pluginMarketData.value.filter(plugin => plugin?.pinned);
});
// 过滤后的插件市场数据(带搜索)
const filteredMarketPlugins = computed(() => {
@@ -552,14 +542,6 @@ const viewReadme = (plugin) => {
readmeDialog.show = true;
};
const open = (link) => {
if (link) {
window.open(link, '_blank');
}
};
// 为表格视图创建一个处理安装插件的函数
const handleInstallPlugin = async (plugin) => {
if (plugin.tags && plugin.tags.includes('danger')) {
@@ -918,6 +900,13 @@ watch(marketSearch, (newVal) => {
}, 300); // 300ms 防抖延迟
});
// 监听显示模式变化并保存到 localStorage
watch(isListView, (newVal) => {
if (typeof window !== 'undefined' && window.localStorage) {
localStorage.setItem('pluginListViewMode', String(newVal));
}
});
</script>
@@ -1037,8 +1026,21 @@ watch(marketSearch, (newVal) => {
<template v-slot:item.name="{ item }">
<div class="d-flex align-center py-2">
<div v-if="item.logo" class="mr-3" style="flex-shrink: 0;">
<img :src="item.logo" :alt="item.name"
style="height: 40px; width: 40px; border-radius: 8px; object-fit: cover;" />
</div>
<div v-else class="mr-3" style="flex-shrink: 0;">
<img :src="defaultPluginIcon" :alt="item.name"
style="height: 40px; width: 40px; border-radius: 8px; object-fit: cover;" />
</div>
<div>
<div class="text-subtitle-1 font-weight-medium">{{ item.name }}</div>
<div class="text-subtitle-1 font-weight-medium">
{{ item.display_name && item.display_name.length ? item.display_name : item.name }}
</div>
<div v-if="item.display_name && item.display_name.length" class="text-caption text-medium-emphasis mt-1">
{{ item.name }}
</div>
<div v-if="item.reserved" class="d-flex align-center mt-1">
<v-chip color="primary" size="x-small" class="font-weight-medium">{{ tm('status.system')
}}</v-chip>
@@ -1048,7 +1050,7 @@ watch(marketSearch, (newVal) => {
</template>
<template v-slot:item.desc="{ item }">
<div class="text-body-2 text-medium-emphasis">{{ item.desc }}</div>
<div class="text-body-2 text-medium-emphasis mt-2 mb-2" style="display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis;">{{ item.desc }}</div>
</template>
<template v-slot:item.version="{ item }">
@@ -1084,7 +1086,7 @@ watch(marketSearch, (newVal) => {
<v-tooltip activator="parent" location="top">{{ tm('tooltips.disable') }}</v-tooltip>
</v-btn>
<v-btn icon size="small" color="info" @click="reloadPlugin(item.name)">
<v-btn icon size="small" @click="reloadPlugin(item.name)">
<v-icon>mdi-refresh</v-icon>
<v-tooltip activator="parent" location="top">{{ tm('tooltips.reload') }}</v-tooltip>
</v-btn>
@@ -1104,7 +1106,7 @@ watch(marketSearch, (newVal) => {
<v-tooltip activator="parent" location="top">{{ tm('tooltips.viewDocs') }}</v-tooltip>
</v-btn>
<v-btn icon size="small" color="warning" @click="updateExtension(item.name)"
<v-btn icon size="small" @click="updateExtension(item.name)"
:v-show="item.has_update">
<v-icon>mdi-update</v-icon>
<v-tooltip activator="parent" location="top">{{ tm('tooltips.update') }}</v-tooltip>
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "AstrBot"
version = "4.10.5"
version = "4.10.6"
description = "Easy-to-use multi-platform LLM chatbot and development framework"
readme = "README.md"
requires-python = ">=3.10"