File size: 4,083 Bytes
65b0b93
6c9a052
65b0b93
 
 
 
92f8acf
65b0b93
 
92f8acf
65b0b93
 
 
 
 
 
 
92f8acf
 
65b0b93
 
92f8acf
 
65b0b93
 
 
 
6c9a052
 
65b0b93
47a0902
 
65b0b93
 
85a3907
47a0902
65b0b93
 
47a0902
6c9a052
65b0b93
 
 
 
 
 
 
6c9a052
 
 
 
 
 
 
 
 
65b0b93
6c9a052
928b8a2
 
65b0b93
928b8a2
 
 
65b0b93
928b8a2
65b0b93
928b8a2
 
 
65b0b93
928b8a2
65b0b93
928b8a2
 
65b0b93
 
6c9a052
 
 
 
928b8a2
 
 
6c9a052
 
 
928b8a2
6c9a052
928b8a2
65b0b93
 
6c9a052
 
 
 
 
 
65b0b93
6c9a052
 
 
 
 
65b0b93
6c9a052
 
 
 
 
 
785ad91
 
 
 
6c9a052
 
 
bb334c7
 
 
 
 
6c9a052
 
 
 
 
bb334c7
6c9a052
 
 
 
 
 
 
 
bb334c7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
"""DanbooruSearch NiceGUI 启动入口与兼容门面。"""

import asyncio
import sys
import time
import traceback

from fastapi.responses import PlainTextResponse
from nicegui import app, ui

from api_fastapi import app as api_app
from core import counter, telemetry
from core.engine import DanbooruTagger
from mcp_server import mcp
from platform_utils import get_host_port, is_cloud
from webui import controller as _controller
from webui.routes import register_main_page


sys.stdout.reconfigure(line_buffering=True)
print('[UI] 脚本开始执行', flush=True)


def _excepthook(exc_type, exc_value, exc_tb):
    print('[UI] FATAL ERROR ON STARTUP:', flush=True)
    traceback.print_exception(exc_type, exc_value, exc_tb)
    sys.__excepthook__(exc_type, exc_value, exc_tb)


sys.excepthook = _excepthook


# 保留既有导入方式:外部代码仍可从 ui_nicegui 获取控制器和旧辅助符号。
DanbooruSearchUI = _controller.DanbooruSearchUI


def __getattr__(name: str):
    return getattr(_controller, name)


register_main_page(
    DanbooruSearchUI,
    _controller._mark_ui_session_active,
    _controller._mark_ui_session_inactive,
    counter.increment_visit,
    telemetry.increment,
)


if __name__ in {'__main__', '__mp_main__'}:
    host, port = get_host_port()

    @app.on_startup
    def _warmup():
        async def background_init_tasks():
            await asyncio.sleep(5)
            print('[UI] 开始预热计数器与引擎', flush=True)
            await counter.init()
            await telemetry.init()
            cold_start_started_at = time.perf_counter()
            await telemetry.increment('engine_cold_start_attempt')
            try:
                await DanbooruTagger.get_instance()
            except Exception:
                await telemetry.increment('engine_cold_start_failure')
                await telemetry.record_timing(
                    'engine_cold_start',
                    (time.perf_counter() - cold_start_started_at) * 1000,
                )
                raise
            await telemetry.increment('engine_cold_start_success')
            await telemetry.record_timing(
                'engine_cold_start',
                (time.perf_counter() - cold_start_started_at) * 1000,
            )
            print('[UI] 后台预热全部完成!', flush=True)

        asyncio.create_task(background_init_tasks())

    @app.on_shutdown
    def _shutdown():
        async def force_sync_all():
            await asyncio.gather(counter.force_sync(), telemetry.force_sync())

        try:
            loop = asyncio.get_event_loop()
            if loop.is_running():
                loop.create_task(force_sync_all())
            else:
                asyncio.run(force_sync_all())
        except Exception as exc:
            print(f'[UI] 关机同步失败: {exc}')

    app.mount('/api', api_app)

    mcp_app = mcp.streamable_http_app()
    app.mount('/mcp', mcp_app)
    _mcp_lifespan_ctx = None

    @app.on_startup
    async def _start_mcp():
        global _mcp_lifespan_ctx
        _mcp_lifespan_ctx = mcp_app.router.lifespan_context(mcp_app)
        await _mcp_lifespan_ctx.__aenter__()

    @app.on_shutdown
    async def _stop_mcp():
        global _mcp_lifespan_ctx
        if _mcp_lifespan_ctx is not None:
            await _mcp_lifespan_ctx.__aexit__(None, None, None)

    @app.get('/googlebd34b54f8562aa06.html')
    def google_verification():
        return PlainTextResponse('google-site-verification: googlebd34b54f8562aa06.html')

    @app.get('/robots.txt')
    def robots_txt():
        content = (
            'User-agent: *\n'
            'Allow: /$\n'
            'Disallow: /api/\n'
            'Disallow: /_nicegui/\n'
            'Disallow: /socket.io/\n'
        )
        return PlainTextResponse(content)

    @app.head('/')
    async def head_root():
        return PlainTextResponse('')

    ui.run(
        host=host,
        port=port,
        title='Danbooru Tags Searcher',
        reload=not is_cloud(),
        show=not is_cloud(),
        reconnect_timeout=120,
    )