fix: stabilize packaged runtime pip/ssl behavior and mac font fallback (#5007)
* fix: patch pip distlib finder for frozen electron runtime * fix: use certifi CA bundle for runtime SSL requests * fix: configure certifi CA before core imports * fix: improve mac font fallback for dashboard text * fix: harden frozen pip patch and unify TLS connector * refactor: centralize dashboard CJK font fallback stacks * perf: reuse TLS context and avoid repeated frozen pip patch * refactor: bootstrap TLS setup before core imports * fix: use async confirm dialog for provider deletions * fix: replace native confirm dialogs in dashboard - Add shared confirm helper in dashboard/src/utils/confirmDialog.ts for async dialog usage with safe fallback. - Migrate provider, chat, config, session, platform, persona, MCP, backup, and knowledge-base delete/close confirmations to use the shared helper. - Remove scattered inline confirm handling to keep behavior consistent and avoid native blocking dialog focus/caret issues in Electron. * fix: capture runtime bootstrap logs after logger init - Add bootstrap record buffer in runtime_bootstrap for early TLS patch logs before logger is ready. - Flush buffered bootstrap logs to astrbot logger at process startup in main.py. - Include concrete exception details for TLS bootstrap failures to improve diagnosis. * fix: harden runtime bootstrap and unify confirm handling - Simplify bootstrap log buffering and add a public initialize hook for non-main startup paths. - Guard aiohttp TLS patching with feature/type checks and keep graceful fallback when internals are unavailable. - Standardize dashboard confirmation flow via shared confirm helpers across composition and options API components. * refactor: simplify runtime tls bootstrap and tighten confirm typing * refactor: align ssl helper namespace and confirm usage
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
import logging
|
||||||
|
import ssl
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from astrbot.utils.http_ssl_common import (
|
||||||
|
build_ssl_context_with_certifi as _build_ssl_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger("astrbot")
|
||||||
|
|
||||||
|
_SHARED_TLS_CONTEXT: ssl.SSLContext | None = None
|
||||||
|
_SHARED_TLS_CONTEXT_LOCK = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def build_ssl_context_with_certifi() -> ssl.SSLContext:
|
||||||
|
"""Build an SSL context from system trust store and add certifi CAs."""
|
||||||
|
global _SHARED_TLS_CONTEXT
|
||||||
|
|
||||||
|
if _SHARED_TLS_CONTEXT is not None:
|
||||||
|
return _SHARED_TLS_CONTEXT
|
||||||
|
|
||||||
|
with _SHARED_TLS_CONTEXT_LOCK:
|
||||||
|
if _SHARED_TLS_CONTEXT is not None:
|
||||||
|
return _SHARED_TLS_CONTEXT
|
||||||
|
|
||||||
|
_SHARED_TLS_CONTEXT = _build_ssl_context(log_obj=logger)
|
||||||
|
return _SHARED_TLS_CONTEXT
|
||||||
|
|
||||||
|
|
||||||
|
def build_tls_connector() -> aiohttp.TCPConnector:
|
||||||
|
return aiohttp.TCPConnector(ssl=build_ssl_context_with_certifi())
|
||||||
@@ -3,6 +3,7 @@ from typing import Literal, TypedDict
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from astrbot.core import logger
|
from astrbot.core import logger
|
||||||
|
from astrbot.core.utils.http_ssl import build_tls_connector
|
||||||
|
|
||||||
|
|
||||||
class LLMModalities(TypedDict):
|
class LLMModalities(TypedDict):
|
||||||
@@ -32,7 +33,9 @@ LLM_METADATAS: dict[str, LLMMetadata] = {}
|
|||||||
async def update_llm_metadata() -> None:
|
async def update_llm_metadata() -> None:
|
||||||
url = "https://models.dev/api.json"
|
url = "https://models.dev/api.json"
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession(
|
||||||
|
trust_env=True, connector=build_tls_connector()
|
||||||
|
) as session:
|
||||||
async with session.get(url) as response:
|
async with session.get(url) as response:
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
global LLM_METADATAS
|
global LLM_METADATAS
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ from astrbot.core.utils.runtime_env import is_packaged_electron_runtime
|
|||||||
|
|
||||||
logger = logging.getLogger("astrbot")
|
logger = logging.getLogger("astrbot")
|
||||||
|
|
||||||
|
_DISTLIB_FINDER_PATCH_ATTEMPTED = False
|
||||||
|
|
||||||
|
|
||||||
def _get_pip_main():
|
def _get_pip_main():
|
||||||
try:
|
try:
|
||||||
@@ -47,6 +49,110 @@ def _cleanup_added_root_handlers(original_handlers: list[logging.Handler]) -> No
|
|||||||
handler.close()
|
handler.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_loader_for_package(package: object) -> object | None:
|
||||||
|
loader = getattr(package, "__loader__", None)
|
||||||
|
if loader is not None:
|
||||||
|
return loader
|
||||||
|
|
||||||
|
spec = getattr(package, "__spec__", None)
|
||||||
|
if spec is None:
|
||||||
|
return None
|
||||||
|
return getattr(spec, "loader", None)
|
||||||
|
|
||||||
|
|
||||||
|
def _try_register_distlib_finder(
|
||||||
|
distlib_resources: object,
|
||||||
|
finder_registry: dict[type, object],
|
||||||
|
register_finder,
|
||||||
|
resource_finder: object,
|
||||||
|
loader: object,
|
||||||
|
package_name: str,
|
||||||
|
) -> bool:
|
||||||
|
loader_type = type(loader)
|
||||||
|
if loader_type in finder_registry:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
register_finder(loader_type, resource_finder)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(
|
||||||
|
"Failed to patch pip distlib finder for loader %s (%s): %s",
|
||||||
|
loader_type.__name__,
|
||||||
|
package_name,
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
updated_registry = getattr(distlib_resources, "_finder_registry", finder_registry)
|
||||||
|
if isinstance(updated_registry, dict) and loader_type not in updated_registry:
|
||||||
|
logger.warning(
|
||||||
|
"Distlib finder patch did not take effect for loader %s (%s).",
|
||||||
|
loader_type.__name__,
|
||||||
|
package_name,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Patched pip distlib finder for frozen loader: %s (%s)",
|
||||||
|
loader_type.__name__,
|
||||||
|
package_name,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _patch_distlib_finder_for_frozen_runtime() -> None:
|
||||||
|
global _DISTLIB_FINDER_PATCH_ATTEMPTED
|
||||||
|
|
||||||
|
if not getattr(sys, "frozen", False):
|
||||||
|
return
|
||||||
|
if _DISTLIB_FINDER_PATCH_ATTEMPTED:
|
||||||
|
return
|
||||||
|
|
||||||
|
_DISTLIB_FINDER_PATCH_ATTEMPTED = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pip._vendor.distlib import resources as distlib_resources
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
finder_registry = getattr(distlib_resources, "_finder_registry", None)
|
||||||
|
register_finder = getattr(distlib_resources, "register_finder", None)
|
||||||
|
resource_finder = getattr(distlib_resources, "ResourceFinder", None)
|
||||||
|
|
||||||
|
if not isinstance(finder_registry, dict):
|
||||||
|
logger.warning(
|
||||||
|
"Skip patching distlib finder because _finder_registry is unavailable."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if not callable(register_finder) or resource_finder is None:
|
||||||
|
logger.warning(
|
||||||
|
"Skip patching distlib finder because register API is unavailable."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
for package_name in ("pip._vendor.distlib", "pip._vendor"):
|
||||||
|
try:
|
||||||
|
package = importlib.import_module(package_name)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
loader = _get_loader_for_package(package)
|
||||||
|
if loader is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if _try_register_distlib_finder(
|
||||||
|
distlib_resources,
|
||||||
|
finder_registry,
|
||||||
|
register_finder,
|
||||||
|
resource_finder,
|
||||||
|
loader,
|
||||||
|
package_name,
|
||||||
|
):
|
||||||
|
finder_registry = getattr(
|
||||||
|
distlib_resources, "_finder_registry", finder_registry
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PipInstaller:
|
class PipInstaller:
|
||||||
def __init__(self, pip_install_arg: str, pypi_index_url: str | None = None) -> None:
|
def __init__(self, pip_install_arg: str, pypi_index_url: str | None = None) -> None:
|
||||||
self.pip_install_arg = pip_install_arg
|
self.pip_install_arg = pip_install_arg
|
||||||
@@ -88,6 +194,7 @@ class PipInstaller:
|
|||||||
|
|
||||||
async def _run_pip_in_process(self, args: list[str]) -> int:
|
async def _run_pip_in_process(self, args: list[str]) -> int:
|
||||||
pip_main = _get_pip_main()
|
pip_main = _get_pip_main()
|
||||||
|
_patch_distlib_finder_for_frozen_runtime()
|
||||||
|
|
||||||
original_handlers = list(logging.getLogger().handlers)
|
original_handlers = list(logging.getLogger().handlers)
|
||||||
result_code, output = await asyncio.to_thread(
|
result_code, output = await asyncio.to_thread(
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import ssl
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import certifi
|
|
||||||
|
|
||||||
from astrbot.core.config import VERSION
|
from astrbot.core.config import VERSION
|
||||||
|
from astrbot.core.utils.http_ssl import build_tls_connector
|
||||||
from astrbot.core.utils.io import download_image_by_url
|
from astrbot.core.utils.io import download_image_by_url
|
||||||
from astrbot.core.utils.t2i.template_manager import TemplateManager
|
from astrbot.core.utils.t2i.template_manager import TemplateManager
|
||||||
|
|
||||||
@@ -39,7 +38,10 @@ class NetworkRenderStrategy(RenderStrategy):
|
|||||||
async def get_official_endpoints(self) -> None:
|
async def get_official_endpoints(self) -> None:
|
||||||
"""获取官方的 t2i 端点列表。"""
|
"""获取官方的 t2i 端点列表。"""
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession(
|
||||||
|
trust_env=True,
|
||||||
|
connector=build_tls_connector(),
|
||||||
|
) as session:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
"https://api.soulter.top/astrbot/t2i-endpoints",
|
"https://api.soulter.top/astrbot/t2i-endpoints",
|
||||||
) as resp:
|
) as resp:
|
||||||
@@ -88,12 +90,10 @@ class NetworkRenderStrategy(RenderStrategy):
|
|||||||
for endpoint in endpoints:
|
for endpoint in endpoints:
|
||||||
try:
|
try:
|
||||||
if return_url:
|
if return_url:
|
||||||
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
|
||||||
connector = aiohttp.TCPConnector(ssl=ssl_context)
|
|
||||||
async with (
|
async with (
|
||||||
aiohttp.ClientSession(
|
aiohttp.ClientSession(
|
||||||
trust_env=True,
|
trust_env=True,
|
||||||
connector=connector,
|
connector=build_tls_connector(),
|
||||||
) as session,
|
) as session,
|
||||||
session.post(
|
session.post(
|
||||||
f"{endpoint}/generate",
|
f"{endpoint}/generate",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import logging
|
||||||
|
import ssl
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import certifi
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def build_ssl_context_with_certifi(log_obj: Any | None = None) -> ssl.SSLContext:
|
||||||
|
logger = log_obj or _LOGGER
|
||||||
|
|
||||||
|
ssl_context = ssl.create_default_context()
|
||||||
|
try:
|
||||||
|
ssl_context.load_verify_locations(cafile=certifi.where())
|
||||||
|
except Exception as exc:
|
||||||
|
if logger and hasattr(logger, "warning"):
|
||||||
|
logger.warning(
|
||||||
|
"Failed to load certifi CA bundle into SSL context; "
|
||||||
|
"falling back to system trust store only: %s",
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ssl_context
|
||||||
@@ -144,6 +144,7 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||||
import type { Session } from '@/composables/useSessions';
|
import type { Session } from '@/composables/useSessions';
|
||||||
|
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog';
|
||||||
import LanguageSwitcher from '@/components/shared/LanguageSwitcher.vue';
|
import LanguageSwitcher from '@/components/shared/LanguageSwitcher.vue';
|
||||||
import StyledMenu from '@/components/shared/StyledMenu.vue';
|
import StyledMenu from '@/components/shared/StyledMenu.vue';
|
||||||
import ProviderConfigDialog from '@/components/chat/ProviderConfigDialog.vue';
|
import ProviderConfigDialog from '@/components/chat/ProviderConfigDialog.vue';
|
||||||
@@ -183,6 +184,8 @@ const emit = defineEmits<{
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { tm } = useModuleI18n('features/chat');
|
const { tm } = useModuleI18n('features/chat');
|
||||||
|
|
||||||
|
const confirmDialog = useConfirmDialog();
|
||||||
|
|
||||||
const sidebarCollapsed = ref(true);
|
const sidebarCollapsed = ref(true);
|
||||||
const showProviderConfigDialog = ref(false);
|
const showProviderConfigDialog = ref(false);
|
||||||
|
|
||||||
@@ -199,10 +202,10 @@ function toggleSidebar() {
|
|||||||
localStorage.setItem('sidebarCollapsed', JSON.stringify(sidebarCollapsed.value));
|
localStorage.setItem('sidebarCollapsed', JSON.stringify(sidebarCollapsed.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteConversation(session: Session) {
|
async function handleDeleteConversation(session: Session) {
|
||||||
const sessionTitle = session.display_name || tm('conversation.newConversation');
|
const sessionTitle = session.display_name || tm('conversation.newConversation');
|
||||||
const message = tm('conversation.confirmDelete', { name: sessionTitle });
|
const message = tm('conversation.confirmDelete', { name: sessionTitle });
|
||||||
if (window.confirm(message)) {
|
if (await askForConfirmation(message, confirmDialog)) {
|
||||||
emit('deleteConversation', session.session_id);
|
emit('deleteConversation', session.session_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,4 +362,3 @@ function handleDeleteConversation(session: Session) {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useModuleI18n } from '@/i18n/composables';
|
import { useModuleI18n } from '@/i18n/composables';
|
||||||
|
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog';
|
||||||
|
|
||||||
export interface Project {
|
export interface Project {
|
||||||
project_id: string;
|
project_id: string;
|
||||||
@@ -72,6 +73,8 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const { tm } = useModuleI18n('features/chat');
|
const { tm } = useModuleI18n('features/chat');
|
||||||
|
|
||||||
|
const confirmDialog = useConfirmDialog();
|
||||||
|
|
||||||
const expanded = ref(props.initialExpanded);
|
const expanded = ref(props.initialExpanded);
|
||||||
|
|
||||||
// 从 localStorage 读取项目展开状态
|
// 从 localStorage 读取项目展开状态
|
||||||
@@ -85,9 +88,9 @@ function toggleExpanded() {
|
|||||||
localStorage.setItem('projectsExpanded', JSON.stringify(expanded.value));
|
localStorage.setItem('projectsExpanded', JSON.stringify(expanded.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteProject(project: Project) {
|
async function handleDeleteProject(project: Project) {
|
||||||
const message = tm('project.confirmDelete', { title: project.title });
|
const message = tm('project.confirmDelete', { title: project.title });
|
||||||
if (window.confirm(message)) {
|
if (await askForConfirmation(message, confirmDialog)) {
|
||||||
emit('deleteProject', project.project_id);
|
emit('deleteProject', project.project_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useModuleI18n } from '@/i18n/composables';
|
import { useModuleI18n } from '@/i18n/composables';
|
||||||
import type { Project } from '@/components/chat/ProjectList.vue';
|
import type { Project } from '@/components/chat/ProjectList.vue';
|
||||||
|
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog';
|
||||||
|
|
||||||
interface Session {
|
interface Session {
|
||||||
session_id: string;
|
session_id: string;
|
||||||
@@ -69,14 +70,16 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const { tm } = useModuleI18n('features/chat');
|
const { tm } = useModuleI18n('features/chat');
|
||||||
|
|
||||||
|
const confirmDialog = useConfirmDialog();
|
||||||
|
|
||||||
function formatDate(dateString: string): string {
|
function formatDate(dateString: string): string {
|
||||||
return new Date(dateString).toLocaleString();
|
return new Date(dateString).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteSession(session: Session) {
|
async function handleDeleteSession(session: Session) {
|
||||||
const sessionTitle = session.display_name || tm('conversation.newConversation');
|
const sessionTitle = session.display_name || tm('conversation.newConversation');
|
||||||
const message = tm('conversation.confirmDelete', { name: sessionTitle });
|
const message = tm('conversation.confirmDelete', { name: sessionTitle });
|
||||||
if (window.confirm(message)) {
|
if (await askForConfirmation(message, confirmDialog)) {
|
||||||
emit('deleteSession', session.session_id);
|
emit('deleteSession', session.session_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,6 +218,10 @@ import axios from 'axios';
|
|||||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
|
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
|
||||||
import ItemCard from '@/components/shared/ItemCard.vue';
|
import ItemCard from '@/components/shared/ItemCard.vue';
|
||||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||||
|
import {
|
||||||
|
askForConfirmation as askForConfirmationDialog,
|
||||||
|
useConfirmDialog
|
||||||
|
} from '@/utils/confirmDialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'McpServersSection',
|
name: 'McpServersSection',
|
||||||
@@ -228,7 +232,8 @@ export default {
|
|||||||
setup() {
|
setup() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { tm } = useModuleI18n('features/tooluse');
|
const { tm } = useModuleI18n('features/tooluse');
|
||||||
return { t, tm };
|
const confirmDialog = useConfirmDialog();
|
||||||
|
return { t, tm, confirmDialog };
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -382,18 +387,21 @@ export default {
|
|||||||
this.showError(this.tm('dialogs.addServer.errors.jsonParse', { error: e.message }));
|
this.showError(this.tm('dialogs.addServer.errors.jsonParse', { error: e.message }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteServer(server) {
|
async deleteServer(server) {
|
||||||
const serverName = server.name || server;
|
const serverName = server.name || server;
|
||||||
if (confirm(this.tm('dialogs.confirmDelete', { name: serverName }))) {
|
const message = this.tm('dialogs.confirmDelete', { name: serverName });
|
||||||
axios.post('/api/tools/mcp/delete', { name: serverName })
|
if (!(await askForConfirmationDialog(message, this.confirmDialog))) {
|
||||||
.then(response => {
|
return;
|
||||||
this.getServers();
|
|
||||||
this.showSuccess(response.data.message || this.tm('messages.deleteSuccess'));
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
this.showError(this.tm('messages.deleteError', { error: error.response?.data?.message || error.message }));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
axios.post('/api/tools/mcp/delete', { name: serverName })
|
||||||
|
.then(response => {
|
||||||
|
this.getServers();
|
||||||
|
this.showSuccess(response.data.message || this.tm('messages.deleteSuccess'));
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.showError(this.tm('messages.deleteError', { error: error.response?.data?.message || error.message }));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
editServer(server) {
|
editServer(server) {
|
||||||
const configCopy = { ...server };
|
const configCopy = { ...server };
|
||||||
|
|||||||
@@ -370,10 +370,13 @@
|
|||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { useI18n } from '@/i18n/composables'
|
import { useI18n } from '@/i18n/composables'
|
||||||
|
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog'
|
||||||
import WaitingForRestart from './WaitingForRestart.vue'
|
import WaitingForRestart from './WaitingForRestart.vue'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const confirmDialog = useConfirmDialog()
|
||||||
|
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
const activeTab = ref('export')
|
const activeTab = ref('export')
|
||||||
const wfr = ref(null)
|
const wfr = ref(null)
|
||||||
@@ -844,7 +847,7 @@ const restoreFromList = async (filename) => {
|
|||||||
|
|
||||||
// 删除备份
|
// 删除备份
|
||||||
const deleteBackup = async (filename) => {
|
const deleteBackup = async (filename) => {
|
||||||
if (!confirm(t('features.settings.backup.list.confirmDelete'))) return
|
if (!(await askForConfirmation(t('features.settings.backup.list.confirmDelete'), confirmDialog))) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('/api/backup/delete', { filename })
|
const response = await axios.post('/api/backup/delete', { filename })
|
||||||
@@ -992,4 +995,4 @@ defineExpose({ open })
|
|||||||
.non-interactive-chip:hover {
|
.non-interactive-chip:hover {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -269,8 +269,8 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
span.style = style + 'display: block; font-size: 12px; font-family: Consolas, monospace; white-space: pre-wrap; margin-bottom: 2px;'
|
span.style = style
|
||||||
span.classList.add('fade-in')
|
span.classList.add('console-log-line', 'fade-in')
|
||||||
span.innerText = `${log}`;
|
span.innerText = `${log}`;
|
||||||
ele.appendChild(span)
|
ele.appendChild(span)
|
||||||
if (this.autoScroll) {
|
if (this.autoScroll) {
|
||||||
@@ -290,7 +290,15 @@ export default {
|
|||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in {
|
:deep(.console-log-line) {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, var(--astrbot-font-cjk-mono), monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.fade-in) {
|
||||||
animation: fadeIn 0.3s;
|
animation: fadeIn 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -307,6 +307,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useModuleI18n } from '@/i18n/composables';
|
import { useModuleI18n } from '@/i18n/composables';
|
||||||
|
import {
|
||||||
|
askForConfirmation as askForConfirmationDialog,
|
||||||
|
useConfirmDialog
|
||||||
|
} from '@/utils/confirmDialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PersonaForm',
|
name: 'PersonaForm',
|
||||||
@@ -331,7 +335,8 @@ export default {
|
|||||||
emits: ['update:modelValue', 'saved', 'error', 'deleted'],
|
emits: ['update:modelValue', 'saved', 'error', 'deleted'],
|
||||||
setup() {
|
setup() {
|
||||||
const { tm } = useModuleI18n('features/persona');
|
const { tm } = useModuleI18n('features/persona');
|
||||||
return { tm };
|
const confirmDialog = useConfirmDialog();
|
||||||
|
return { tm, confirmDialog };
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -596,8 +601,13 @@ export default {
|
|||||||
|
|
||||||
async deletePersona() {
|
async deletePersona() {
|
||||||
if (!this.editingPersona) return;
|
if (!this.editingPersona) return;
|
||||||
|
|
||||||
if (!confirm(this.tm('messages.deleteConfirm', { id: this.editingPersona.persona_id }))) {
|
if (
|
||||||
|
!(await askForConfirmationDialog(
|
||||||
|
this.tm('messages.deleteConfirm', { id: this.editingPersona.persona_id }),
|
||||||
|
this.confirmDialog,
|
||||||
|
))
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { getProviderIcon } from '@/utils/providerUtils'
|
import { getProviderIcon } from '@/utils/providerUtils'
|
||||||
|
import { askForConfirmation as askForConfirmationDialog, useConfirmDialog } from '@/utils/confirmDialog'
|
||||||
|
|
||||||
export interface UseProviderSourcesOptions {
|
export interface UseProviderSourcesOptions {
|
||||||
defaultTab?: string
|
defaultTab?: string
|
||||||
@@ -37,6 +38,12 @@ export function resolveDefaultTab(value?: string) {
|
|||||||
export function useProviderSources(options: UseProviderSourcesOptions) {
|
export function useProviderSources(options: UseProviderSourcesOptions) {
|
||||||
const { tm, showMessage } = options
|
const { tm, showMessage } = options
|
||||||
|
|
||||||
|
const confirmDialog = useConfirmDialog()
|
||||||
|
|
||||||
|
async function askForConfirmation(message: string) {
|
||||||
|
return askForConfirmationDialog(message, confirmDialog)
|
||||||
|
}
|
||||||
|
|
||||||
// ===== State =====
|
// ===== State =====
|
||||||
const config = ref<Record<string, any>>({})
|
const config = ref<Record<string, any>>({})
|
||||||
const metadata = ref<Record<string, any>>({})
|
const metadata = ref<Record<string, any>>({})
|
||||||
@@ -396,7 +403,10 @@ export function useProviderSources(options: UseProviderSourcesOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteProviderSource(source: any) {
|
async function deleteProviderSource(source: any) {
|
||||||
if (!confirm(tm('providerSources.deleteConfirm', { id: source.id }))) return
|
const confirmed = await askForConfirmation(
|
||||||
|
tm('providerSources.deleteConfirm', { id: source.id })
|
||||||
|
)
|
||||||
|
if (!confirmed) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post('/api/config/provider_sources/delete', { id: source.id })
|
await axios.post('/api/config/provider_sources/delete', { id: source.id })
|
||||||
@@ -558,7 +568,8 @@ export function useProviderSources(options: UseProviderSourcesOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteProvider(provider: any) {
|
async function deleteProvider(provider: any) {
|
||||||
if (!confirm(tm('models.deleteConfirm', { id: provider.id }))) return
|
const confirmed = await askForConfirmation(tm('models.deleteConfirm', { id: provider.id }))
|
||||||
|
if (!confirmed) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post('/api/config/provider/delete', { id: provider.id })
|
await axios.post('/api/config/provider/delete', { id: provider.id })
|
||||||
|
|||||||
@@ -9,7 +9,15 @@ $color-pack: false;
|
|||||||
// Global font size and border radius
|
// Global font size and border radius
|
||||||
$font-size-root: 1rem;
|
$font-size-root: 1rem;
|
||||||
$border-radius-root: 8px;
|
$border-radius-root: 8px;
|
||||||
$body-font-family: 'Roboto', sans-serif !default;
|
$cjk-sans-fallback: 'PingFang SC', 'Hiragino Sans GB', 'Noto Sans CJK SC', 'Microsoft YaHei' !default;
|
||||||
|
$cjk-mono-fallback: 'PingFang SC', 'PingFang TC', 'Hiragino Sans GB', 'Noto Sans CJK SC', 'Microsoft YaHei' !default;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--astrbot-font-cjk-sans: #{$cjk-sans-fallback};
|
||||||
|
--astrbot-font-cjk-mono: #{$cjk-mono-fallback};
|
||||||
|
}
|
||||||
|
|
||||||
|
$body-font-family: 'Roboto', $cjk-sans-fallback, sans-serif !default;
|
||||||
$heading-font-family: $body-font-family !default;
|
$heading-font-family: $body-font-family !default;
|
||||||
$btn-font-weight: 400 !default;
|
$btn-font-weight: 400 !default;
|
||||||
$btn-letter-spacing: 0 !default;
|
$btn-letter-spacing: 0 !default;
|
||||||
|
|||||||
@@ -85,15 +85,15 @@ $sizes: (
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
.Poppins {
|
.Poppins {
|
||||||
font-family: 'Poppins', sans-serif !important;
|
font-family: 'Poppins', $cjk-sans-fallback, sans-serif !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Inter {
|
.Inter {
|
||||||
font-family: 'Inter', sans-serif !important;
|
font-family: 'Inter', $cjk-sans-fallback, sans-serif !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Outfit {
|
.Outfit {
|
||||||
font-family: 'Outfit', sans-serif !important;
|
font-family: 'Outfit', $cjk-sans-fallback, sans-serif !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
import 'vue'
|
||||||
|
|
||||||
|
import type { ConfirmDialogHandler } from '@/utils/confirmDialog'
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
$confirm?: ConfirmDialogHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { inject } from 'vue'
|
||||||
|
|
||||||
|
export type ConfirmDialogOptions = {
|
||||||
|
title?: string
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConfirmDialogHandler = (options: ConfirmDialogOptions) => Promise<boolean>
|
||||||
|
|
||||||
|
export type ConfirmDialogCandidate = ConfirmDialogHandler | null | undefined
|
||||||
|
|
||||||
|
export function useConfirmDialog(): ConfirmDialogHandler | undefined {
|
||||||
|
return inject<ConfirmDialogHandler | undefined>('$confirm', undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function askForConfirmation(
|
||||||
|
message: string,
|
||||||
|
candidate?: ConfirmDialogCandidate
|
||||||
|
): Promise<boolean> {
|
||||||
|
const confirmDialog = candidate ?? undefined
|
||||||
|
|
||||||
|
if (confirmDialog) {
|
||||||
|
try {
|
||||||
|
return await confirmDialog({ message })
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.confirm(message)
|
||||||
|
}
|
||||||
@@ -190,6 +190,10 @@ import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
|
|||||||
import StandaloneChat from '@/components/chat/StandaloneChat.vue';
|
import StandaloneChat from '@/components/chat/StandaloneChat.vue';
|
||||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
||||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||||
|
import {
|
||||||
|
askForConfirmation as askForConfirmationDialog,
|
||||||
|
useConfirmDialog
|
||||||
|
} from '@/utils/confirmDialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ConfigPage',
|
name: 'ConfigPage',
|
||||||
@@ -208,10 +212,12 @@ export default {
|
|||||||
setup() {
|
setup() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { tm } = useModuleI18n('features/config');
|
const { tm } = useModuleI18n('features/config');
|
||||||
|
const confirmDialog = useConfirmDialog();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
t,
|
t,
|
||||||
tm
|
tm,
|
||||||
|
confirmDialog
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -473,8 +479,9 @@ export default {
|
|||||||
this.createNewConfig();
|
this.createNewConfig();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmDeleteConfig(config) {
|
async confirmDeleteConfig(config) {
|
||||||
if (confirm(this.tm('configManagement.confirmDelete').replace('{name}', config.name))) {
|
const message = this.tm('configManagement.confirmDelete').replace('{name}', config.name);
|
||||||
|
if (await askForConfirmationDialog(message, this.confirmDialog)) {
|
||||||
this.deleteConfig(config.id);
|
this.deleteConfig(config.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -658,4 +665,4 @@ export default {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 0 0 16px 16px;
|
border-radius: 0 0 16px 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -333,6 +333,10 @@ import { useCommonStore } from '@/stores/common';
|
|||||||
import { useCustomizerStore } from '@/stores/customizer';
|
import { useCustomizerStore } from '@/stores/customizer';
|
||||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||||
import MessageList from '@/components/chat/MessageList.vue';
|
import MessageList from '@/components/chat/MessageList.vue';
|
||||||
|
import {
|
||||||
|
askForConfirmation as askForConfirmationDialog,
|
||||||
|
useConfirmDialog
|
||||||
|
} from '@/utils/confirmDialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ConversationPage',
|
name: 'ConversationPage',
|
||||||
@@ -345,12 +349,14 @@ export default {
|
|||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const { tm } = useModuleI18n('features/conversation');
|
const { tm } = useModuleI18n('features/conversation');
|
||||||
const customizerStore = useCustomizerStore();
|
const customizerStore = useCustomizerStore();
|
||||||
|
const confirmDialog = useConfirmDialog();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
t,
|
t,
|
||||||
tm,
|
tm,
|
||||||
locale,
|
locale,
|
||||||
customizerStore
|
customizerStore,
|
||||||
|
confirmDialog
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -744,9 +750,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 关闭对话历史对话框
|
// 关闭对话历史对话框
|
||||||
closeHistoryDialog() {
|
async closeHistoryDialog() {
|
||||||
if (this.isEditingHistory) {
|
if (this.isEditingHistory) {
|
||||||
if (confirm(this.tm('dialogs.view.confirmClose'))) {
|
if (await askForConfirmationDialog(this.tm('dialogs.view.confirmClose'), this.confirmDialog)) {
|
||||||
this.dialogView = false;
|
this.dialogView = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1133,4 +1139,4 @@ export default {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -197,6 +197,10 @@ import AddNewPlatform from '@/components/platform/AddNewPlatform.vue';
|
|||||||
import { useCommonStore } from '@/stores/common';
|
import { useCommonStore } from '@/stores/common';
|
||||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||||
import { getPlatformIcon, getTutorialLink } from '@/utils/platformUtils';
|
import { getPlatformIcon, getTutorialLink } from '@/utils/platformUtils';
|
||||||
|
import {
|
||||||
|
askForConfirmation as askForConfirmationDialog,
|
||||||
|
useConfirmDialog
|
||||||
|
} from '@/utils/confirmDialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PlatformPage',
|
name: 'PlatformPage',
|
||||||
@@ -210,10 +214,12 @@ export default {
|
|||||||
setup() {
|
setup() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { tm } = useModuleI18n('features/platform');
|
const { tm } = useModuleI18n('features/platform');
|
||||||
|
const confirmDialog = useConfirmDialog();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
t,
|
t,
|
||||||
tm
|
tm,
|
||||||
|
confirmDialog
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -451,15 +457,18 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deletePlatform(platform) {
|
async deletePlatform(platform) {
|
||||||
if (confirm(`${this.messages.deleteConfirm} ${platform.id}?`)) {
|
const message = `${this.messages.deleteConfirm} ${platform.id}?`;
|
||||||
axios.post('/api/config/platform/delete', { id: platform.id }).then((res) => {
|
if (!(await askForConfirmationDialog(message, this.confirmDialog))) {
|
||||||
this.getConfig();
|
return;
|
||||||
this.showSuccess(res.data.message || this.messages.deleteSuccess);
|
|
||||||
}).catch((err) => {
|
|
||||||
this.showError(err.response?.data?.message || err.message);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
axios.post('/api/config/platform/delete', { id: platform.id }).then((res) => {
|
||||||
|
this.getConfig();
|
||||||
|
this.showSuccess(res.data.message || this.messages.deleteSuccess);
|
||||||
|
}).catch((err) => {
|
||||||
|
this.showError(err.response?.data?.message || err.message);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
platformStatusChange(platform) {
|
platformStatusChange(platform) {
|
||||||
|
|||||||
@@ -522,16 +522,22 @@
|
|||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { useI18n, useModuleI18n } from '@/i18n/composables'
|
import { useI18n, useModuleI18n } from '@/i18n/composables'
|
||||||
|
import {
|
||||||
|
askForConfirmation as askForConfirmationDialog,
|
||||||
|
useConfirmDialog
|
||||||
|
} from '@/utils/confirmDialog'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SessionManagementPage',
|
name: 'SessionManagementPage',
|
||||||
setup() {
|
setup() {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { tm } = useModuleI18n('features/session-management')
|
const { tm } = useModuleI18n('features/session-management')
|
||||||
|
const confirmDialog = useConfirmDialog()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
t,
|
t,
|
||||||
tm
|
tm,
|
||||||
|
confirmDialog
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -1503,7 +1509,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async deleteGroup(group) {
|
async deleteGroup(group) {
|
||||||
if (!confirm(`确定要删除分组 "${group.name}" 吗?`)) return
|
const message = `确定要删除分组 "${group.name}" 吗?`
|
||||||
|
if (!(await askForConfirmationDialog(message, this.confirmDialog))) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('/api/session/group/delete', { id: group.id })
|
const response = await axios.post('/api/session/group/delete', { id: group.id })
|
||||||
|
|||||||
@@ -244,10 +244,13 @@ import { ref, computed, onMounted } from 'vue'
|
|||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { useModuleI18n } from '@/i18n/composables'
|
import { useModuleI18n } from '@/i18n/composables'
|
||||||
|
import { askForConfirmation, useConfirmDialog } from '@/utils/confirmDialog'
|
||||||
|
|
||||||
const { tm: t } = useModuleI18n('features/knowledge-base/document')
|
const { tm: t } = useModuleI18n('features/knowledge-base/document')
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
const confirmDialog = useConfirmDialog()
|
||||||
|
|
||||||
const kbId = ref(route.params.kbId as string)
|
const kbId = ref(route.params.kbId as string)
|
||||||
const docId = ref(route.params.docId as string)
|
const docId = ref(route.params.docId as string)
|
||||||
|
|
||||||
@@ -356,7 +359,7 @@ const viewChunk = (chunk: any) => {
|
|||||||
|
|
||||||
// 删除分块
|
// 删除分块
|
||||||
const deleteChunk = async (chunk: any) => {
|
const deleteChunk = async (chunk: any) => {
|
||||||
if (!confirm(t('chunks.deleteConfirm'))) return
|
if (!(await askForConfirmation(t('chunks.deleteConfirm'), confirmDialog))) return
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('/api/kb/chunk/delete', {
|
const response = await axios.post('/api/kb/chunk/delete', {
|
||||||
chunk_id: chunk.chunk_id,
|
chunk_id: chunk.chunk_id,
|
||||||
|
|||||||
@@ -260,6 +260,10 @@ import PersonaCard from './PersonaCard.vue';
|
|||||||
import PersonaForm from '@/components/shared/PersonaForm.vue';
|
import PersonaForm from '@/components/shared/PersonaForm.vue';
|
||||||
import CreateFolderDialog from './CreateFolderDialog.vue';
|
import CreateFolderDialog from './CreateFolderDialog.vue';
|
||||||
import MoveToFolderDialog from './MoveToFolderDialog.vue';
|
import MoveToFolderDialog from './MoveToFolderDialog.vue';
|
||||||
|
import {
|
||||||
|
askForConfirmation as askForConfirmationDialog,
|
||||||
|
useConfirmDialog
|
||||||
|
} from '@/utils/confirmDialog';
|
||||||
|
|
||||||
import type { Folder, FolderTreeNode } from '@/components/folder/types';
|
import type { Folder, FolderTreeNode } from '@/components/folder/types';
|
||||||
|
|
||||||
@@ -294,7 +298,8 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { tm } = useModuleI18n('features/persona');
|
const { tm } = useModuleI18n('features/persona');
|
||||||
return { t, tm };
|
const confirmDialog = useConfirmDialog();
|
||||||
|
return { t, tm, confirmDialog };
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -420,7 +425,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async confirmDeletePersona(persona: Persona) {
|
async confirmDeletePersona(persona: Persona) {
|
||||||
if (!confirm(this.tm('messages.deleteConfirm', { id: persona.persona_id }))) {
|
if (
|
||||||
|
!(await askForConfirmationDialog(
|
||||||
|
this.tm('messages.deleteConfirm', { id: persona.persona_id }),
|
||||||
|
this.confirmDialog,
|
||||||
|
))
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,16 @@ function createWindow() {
|
|||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
sandbox: true,
|
sandbox: true,
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
...(isMac
|
||||||
|
? {
|
||||||
|
defaultFontFamily: {
|
||||||
|
standard: 'PingFang SC',
|
||||||
|
sansSerif: 'PingFang SC',
|
||||||
|
serif: 'Songti SC',
|
||||||
|
monospace: 'SF Mono',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from astrbot.core import LogBroker, LogManager, db_helper, logger
|
import runtime_bootstrap
|
||||||
from astrbot.core.config.default import VERSION
|
|
||||||
from astrbot.core.initial_loader import InitialLoader
|
runtime_bootstrap.initialize_runtime_bootstrap()
|
||||||
from astrbot.core.utils.astrbot_path import (
|
|
||||||
|
from astrbot.core import LogBroker, LogManager, db_helper, logger # noqa: E402
|
||||||
|
from astrbot.core.config.default import VERSION # noqa: E402
|
||||||
|
from astrbot.core.initial_loader import InitialLoader # noqa: E402
|
||||||
|
from astrbot.core.utils.astrbot_path import ( # noqa: E402
|
||||||
get_astrbot_config_path,
|
get_astrbot_config_path,
|
||||||
get_astrbot_data_path,
|
get_astrbot_data_path,
|
||||||
get_astrbot_plugin_path,
|
get_astrbot_plugin_path,
|
||||||
@@ -16,7 +20,10 @@ from astrbot.core.utils.astrbot_path import (
|
|||||||
get_astrbot_site_packages_path,
|
get_astrbot_site_packages_path,
|
||||||
get_astrbot_temp_path,
|
get_astrbot_temp_path,
|
||||||
)
|
)
|
||||||
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
|
from astrbot.core.utils.io import ( # noqa: E402
|
||||||
|
download_dashboard,
|
||||||
|
get_dashboard_version,
|
||||||
|
)
|
||||||
|
|
||||||
# 将父目录添加到 sys.path
|
# 将父目录添加到 sys.path
|
||||||
sys.path.append(Path(__file__).parent.as_posix())
|
sys.path.append(Path(__file__).parent.as_posix())
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import logging
|
||||||
|
import ssl
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import aiohttp.connector as aiohttp_connector
|
||||||
|
|
||||||
|
from astrbot.utils.http_ssl_common import build_ssl_context_with_certifi
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _try_patch_aiohttp_ssl_context(
|
||||||
|
ssl_context: ssl.SSLContext,
|
||||||
|
log_obj: Any | None = None,
|
||||||
|
) -> bool:
|
||||||
|
log = log_obj or logger
|
||||||
|
attr_name = "_SSL_CONTEXT_VERIFIED"
|
||||||
|
|
||||||
|
if not hasattr(aiohttp_connector, attr_name):
|
||||||
|
log.warning(
|
||||||
|
"aiohttp connector does not expose _SSL_CONTEXT_VERIFIED; skipped patch.",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
current_value = getattr(aiohttp_connector, attr_name, None)
|
||||||
|
if current_value is not None and not isinstance(current_value, ssl.SSLContext):
|
||||||
|
log.warning(
|
||||||
|
"aiohttp connector exposes _SSL_CONTEXT_VERIFIED with unexpected type; skipped patch.",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
setattr(aiohttp_connector, attr_name, ssl_context)
|
||||||
|
log.info("Configured aiohttp verified SSL context with system+certifi trust chain.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def configure_runtime_ca_bundle(log_obj: Any | None = None) -> bool:
|
||||||
|
log = log_obj or logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
log.info("Bootstrapping runtime CA bundle.")
|
||||||
|
ssl_context = build_ssl_context_with_certifi(log_obj=log)
|
||||||
|
return _try_patch_aiohttp_ssl_context(ssl_context, log_obj=log)
|
||||||
|
except Exception as exc:
|
||||||
|
log.error("Failed to configure runtime CA bundle for aiohttp: %r", exc)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_runtime_bootstrap(log_obj: Any | None = None) -> bool:
|
||||||
|
return configure_runtime_ca_bundle(log_obj=log_obj)
|
||||||
Reference in New Issue
Block a user