Compare commits

...

2 Commits

Author SHA1 Message Date
c32995edd4 v0.1.2 修复发布问题
v0.1.2 开始发布至 pypi,可以从 pypi 下载本项目然后四处部署了~
另外添加了 README.md 文件。
2026-04-26 00:00:21 +08:00
247d9f089c v0.1.1 项目结构与构建流程更新
涉及的文件很多,但主要是 PyCharm 自动重构 + 手动微调。

1. 使用 src-layout 结构组织项目,旨在更加规范地管理各个子包,并方便组织代码。
2. 可以使用 uv build 构建项目至 wheel。
3. 可以在 /webui 目录下使用 pnpm run build 来构建 WebUI,构建产物会生成在
/src/njupt_suan_api/static 目录下。同时,uv build 时会一同包含此目录下的 WebUI
构建产物,避免在用户侧执行构建。
4. 使用 typer 实现了命令行入口,代码位于 manage.py;命令行实现了现 main.py 的全部功能。
2026-04-25 21:30:11 +08:00
45 changed files with 347 additions and 188 deletions

3
.gitignore vendored
View File

@@ -12,3 +12,6 @@ temp/
.venv
.scripts
# vite 构建产物
src/njupt_suan_api/static/

View File

@@ -2,6 +2,7 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="uv (NJUPT-API-Suan)" jdkType="Python SDK" />

View File

@@ -0,0 +1,40 @@
# NJUPT Suan API
NJUPT Suan API 是一个 FastAPI 项目,目标在于实现对 NJUPT南京邮电大学的信息获取 API 和 MCP 服务。
## 文档
虽然项目还没个两样,但是文档其实也没个两样 ~~(什么东西)~~
[中文名叫芒果酸](https://suan.mangofanfan.cn) - `suan.mangofanfan.cn`
## 功能
| 计划功能(芒果画饼中) | 支持进度 |
|---------------|---------------|
| 教务系统 - 课程表获取 | ✅ |
| 教务系统 - 课程获取 | ⌛️ |
| 教务系统 - 成绩获取 | ⌛️ |
| 体育部系统 - 早锻炼获取 | ⌛️(等待体育部系统修复) |
## 运行
建议查阅文档了解更多部署方式。
如需从源代码直接运行的话,项目的源码位于 `src/njupt_suan_api` 目录下,`main.py` 是旧的入口文件,可以直接传统方式启动。
`manage.py` 是命令行入口,提供了完整的帮助信息。
`server.py` 是 FastAPI app 所在文件,可以使用 uvicorn 命令启动。
另外如需从源代码启动项目,你需要自行构建 WebUI。
```bash
cd webui/
pnpm install
pnpm run build
```
AI 说 `npm install` 然后 `npm run build` 也可以,但我还没试过,你可以帮我试试(?)
vite 的构建产物会放在 `src/njupt_suan_api/static` 目录下,**构建产物不会被 git 管理,但是会被项目打包进 wheel。**

View File

@@ -1,9 +0,0 @@
from .message import ALREADY_INIT_MESSAGE, NOT_INIT_MESSAGE
from .path import DATA_DIR, TEMP_DIR
__all__ = [
DATA_DIR,
TEMP_DIR,
NOT_INIT_MESSAGE,
ALREADY_INIT_MESSAGE,
]

View File

@@ -1,9 +0,0 @@
NOT_INIT_MESSAGE = """
❓ 当前目录或指定目录下[yellow]似乎还没有执行过初始化命令[/yellow]。
❓ 你也许需要先执行 [green]suanapi init[/green] 。
"""
ALREADY_INIT_MESSAGE = """
❕ 当前目录或指定目录下[yellow]似乎已经执行初始化命令过[/yellow]。
❕ 你也许需要先删除已经存在的 [blue]data[/blue] 和 [blue]temp[/blue] 目录。
"""

View File

@@ -1,4 +0,0 @@
from pathlib import Path
DATA_DIR = Path.cwd() / "data"
TEMP_DIR = Path.cwd() / "temp"

109
manage.py
View File

@@ -1,109 +0,0 @@
from secrets import token_urlsafe
import typer
from rich.console import Console
from rich.panel import Panel
from cli import ALREADY_INIT_MESSAGE, DATA_DIR, NOT_INIT_MESSAGE, TEMP_DIR
from router import __version__
console = Console()
app = typer.Typer(
name="NJUPT-Suan-API",
help="NJUPT Suan API 部署与管理工具",
rich_markup_mode="rich",
no_args_is_help=True,
options_metavar="[选项]",
subcommand_metavar="[命令]",
)
def version_callback(value: bool = False) -> None:
if value:
console.print(f"NJUPT Suan API [green]v.{__version__}[/green]")
if __version__ == "dev":
console.print("""
[bright_black]显示的版本为 dev ?这是因为你正在从源代码运行 cli 入口manage.py。[/bright_black]
[bright_black]从[green]已安装版本[/green]中运行 [green]suanapi --version[/green] 可以正确获取版本号。[/bright_black]""")
raise typer.Exit
@app.callback(invoke_without_command=True)
def main(
version: bool = typer.Option(
False,
"--version",
"-v",
help="显示版本号并退出,没有其他命令会被执行。",
callback=version_callback,
is_eager=True, # 优先处理,避免触发其他逻辑
),
) -> None:
"""
CLI 入口回调,所有子命令执行前都会经过这里。
可以在这里放全局初始化(如日志级别、环境检查)。
"""
pass # 没有 --version 时就正常放行,继续执行子命令
@app.command()
def init() -> None:
"""
初始化 NJUPT Suan API [green]工作目录[/green]。(可能需要较长时间)
会在当前目录或指定目录下创建新文件,并尝试安装 playwright chromium安装过程可能需要较长时间。
"""
if DATA_DIR.exists() or TEMP_DIR.exists():
console.print(Panel(ALREADY_INIT_MESSAGE, title="数据目录已存在"))
console.print("[bright_black]如果你想要强制初始化,使用 [green]suanapi init -f[/green] 命令。[/bright_black]")
return
@app.command()
def token(reset: bool = typer.Option(False, "--reload", "-r", help="强制重新生成令牌,即使令牌已存在。")) -> None:
"""
查看或重新生成[green]管理后端令牌[/green]。
需要先运行过 init 初始化目录。
Args:
reset: bool默认为 False即只查看在不存在时重新生成。
"""
token_ = None
# 首先检查数据目录是否存在
if not DATA_DIR.exists():
console.print(Panel(NOT_INIT_MESSAGE, title="数据目录不存在"))
return
# 确认存在后再判断是否需要重新生成令牌
if not reset:
try:
with open(file=DATA_DIR / "token.txt", mode="r") as f:
token_ = f.read()
except FileNotFoundError:
pass
if not token_:
console.print("[yellow]重新生成令牌...[/yellow]")
token_ = token_urlsafe(32)
msg = f"""
🔐 [green] 令牌 - [/green]{token_}
🔐 [green]有效期 - [/green]无限
✅ WebUI 设计的令牌 cookie 有效期为一天,所以你每天都需要重新登录一次 WebUI这并非令牌本身的有效期。
"""
panel = Panel(msg, title="WebUI 令牌")
console.print(panel)
@app.command()
def run() -> None:
"""
运行 NJUPT Suan API。
"""
pass
if __name__ == "__main__":
app()

View File

@@ -1,6 +1,6 @@
[project]
name = "njupt-suan-api"
version = "0.1.1"
version = "0.1.2"
description = "API and MCP server for NJUPT infomation ~"
readme = "README.md"
requires-python = ">=3.13"
@@ -24,7 +24,18 @@ dependencies = [
]
[project.scripts]
suanapi = "manage:app"
suanapi = "njupt_suan_api.manage:app"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.sdist]
artifacts = ["src/njupt_suan_api/static/**"]
[tool.hatch.build.targets.wheel]
packages = ["src/njupt_suan_api"]
artifacts = ["src/njupt_suan_api/static/**"]
[tool.ruff]
preview = true

View File

@@ -1 +0,0 @@
from .__version__ import __version__ as __version__

View File

@@ -18,7 +18,7 @@ class Config:
async def load_json(self) -> None:
"""
Toml 配置文件中读取配置
异步从配置文件中读取配置
"""
logger.debug("异步读取配置文件。")
async with aiofiles.open(file=CONFIG_PATH, mode="r") as f:
@@ -49,7 +49,7 @@ class Config:
def init_config(self) -> None:
"""
重新初始化 Toml 配置文件这会重置所有配置
重新初始化配置文件这会重置所有配置
"""
logger.warning("初始化配置文件,这会重置所有配置。")
self._doc.clear()
@@ -59,7 +59,7 @@ class Config:
doc_system["host"] = "0.0.0.0"
doc_system["port"] = 8000
doc_system["reload"] = True
doc_system["reload"] = False
doc_system["public_host"] = "http://127.0.0.1:8000"
doc_schedule["playwright_headless"] = True
@@ -78,7 +78,7 @@ class Config:
async def save_json(self) -> None:
"""
异步保存 Toml 配置文件
异步保存配置文件
"""
logger.debug("异步保存配置文件。")
async with aiofiles.open(file=CONFIG_PATH, mode="w") as f:

View File

@@ -7,7 +7,7 @@ from playwright.async_api import (
async_playwright,
)
from . import config
from .config import config
class PlayContextManager:

View File

@@ -1,5 +1,4 @@
from njupt_api.baselib import PlayContextManager, logger
from ..baselib import PlayContextManager, logger
from .exc import LoginError

View File

@@ -1,8 +1,7 @@
from ddddocr import DdddOcr
from playwright.async_api import Browser, BrowserContext, Page, Playwright
from njupt_api.baselib import PlayContextManager, logger
from ..baselib import PlayContextManager, logger
from .createcourse import create_course_schedule
from .exc import LoginError
from .sso import SSO

View File

@@ -0,0 +1,13 @@
from .message import ALREADY_INIT_MESSAGE, INIT_STAGE_MESSAGE, NOT_INIT_MESSAGE, RUN_CHECK_MESSAGE, TOKEN_CHECK_MESSAGE
from .path import DATA_DIR, TEMP_DIR, WORKSPACE_DIR
__all__ = [
WORKSPACE_DIR,
DATA_DIR,
TEMP_DIR,
NOT_INIT_MESSAGE,
ALREADY_INIT_MESSAGE,
RUN_CHECK_MESSAGE,
TOKEN_CHECK_MESSAGE,
INIT_STAGE_MESSAGE,
]

View File

@@ -0,0 +1,25 @@
NOT_INIT_MESSAGE = """
❓ 当前目录或指定目录下[yellow]似乎还没有执行过初始化命令[/yellow]。
❓ 你也许需要先执行 [green]suanapi init[/green] 。
"""
ALREADY_INIT_MESSAGE = """
❕ 当前目录或指定目录下[yellow]似乎已经执行初始化命令过[/yellow]。
❕ 你也许需要先删除已经存在的 [blue]data[/blue] 和 [blue]temp[/blue] 目录。
"""
RUN_CHECK_MESSAGE = """
[bright_black]运行时的配置可能来自命令行参数、配置文件以及默认值,列在这里供你检查。[/bright_black]
[cyan]主机名[/cyan] - [cyan]host[/cyan] - {host}
[cyan]端口[/cyan] - [cyan]port[/cyan] - {port}
[cyan]自动重启[/cyan] - [cyan]reload[/cyan] - {reload}
[bright_black]NJUPT Suan API 会很快启动。使用 [green]Ctrl + C[/green] 以退出。[/bright_black]
"""
TOKEN_CHECK_MESSAGE = """
🔐 [green] 令牌 - [/green]{token}
🔐 [green]有效期 - [/green]无限
✅ WebUI 设计的令牌 cookie 有效期为一天,所以你每天都需要重新登录一次 WebUI这并非令牌本身的有效期。
"""
INIT_STAGE_MESSAGE = "[cyan]- {stage} / 3 -[/cyan] [bright_black]{message}[/bright_black]"

View File

@@ -0,0 +1,5 @@
from pathlib import Path
WORKSPACE_DIR = Path.cwd()
DATA_DIR = WORKSPACE_DIR / "data"
TEMP_DIR = WORKSPACE_DIR / "temp"

View File

@@ -1,8 +1,12 @@
"""
main.py 未来不再作为项目入口日后可能会被删除请参照 README 使用命令行作为入口或直接使用 uvicorn 命令
"""
from pathlib import Path
from secrets import token_urlsafe
from njupt_api.baselib import config, logger
from router import __version__
from njupt_suan_api.api.baselib import config, logger
from njupt_suan_api.router import __version__
DATA_DIR = Path.cwd() / "data"
TEMP_DIR = Path.cwd() / "temp"
@@ -10,7 +14,7 @@ TEMP_DIR = Path.cwd() / "temp"
if __name__ == "__main__":
try:
with open(file=Path.cwd() / "njupt_api" / "art.txt", mode="r", encoding="utf-8") as f:
with open(file=Path.cwd() / "api" / "art.txt", mode="r", encoding="utf-8") as f:
print(f.read().format(__version__)) # noqa:T201
except FileNotFoundError:
pass
@@ -77,7 +81,7 @@ if __name__ == "__main__":
host=host,
port=port,
reload=reload,
reload_dirs=["njupt_api", "router"],
reload_dirs=["api", "router"],
access_log=False,
log_level="critical",
timeout_graceful_shutdown=2,

View File

@@ -0,0 +1,181 @@
import subprocess
from secrets import token_urlsafe
import typer
from rich.console import Console
from rich.panel import Panel
from njupt_suan_api.api.baselib import config
from njupt_suan_api.cli import (
ALREADY_INIT_MESSAGE,
DATA_DIR,
INIT_STAGE_MESSAGE,
NOT_INIT_MESSAGE,
RUN_CHECK_MESSAGE,
TEMP_DIR,
TOKEN_CHECK_MESSAGE,
WORKSPACE_DIR,
)
from njupt_suan_api.router import __version__
console = Console()
app = typer.Typer(
name="NJUPT-Suan-API",
help="NJUPT Suan API 部署与管理工具",
rich_markup_mode="rich",
no_args_is_help=True,
)
def version_callback(value: bool = False) -> None:
if value:
console.print(f"NJUPT Suan API [green]v.{__version__}[/green]")
if __version__ == "dev":
console.print("""
[bright_black]显示的版本为 dev ?这是因为你正在从源代码运行 cli 入口manage.py。[/bright_black]
[bright_black]从[green]已安装版本[/green]中运行 [green]suanapi --version[/green] 可以正确获取版本号。[/bright_black]""")
raise typer.Exit
@app.callback(invoke_without_command=True)
def main(
version: bool = typer.Option(
False,
"--version",
"-v",
help="显示版本号并退出,没有其他命令会被执行。",
callback=version_callback,
is_eager=True, # 优先处理,避免触发其他逻辑
),
) -> None:
"""
CLI 入口回调,所有子命令执行前都会经过这里。
可以在这里放全局初始化(如日志级别、环境检查)。
"""
pass # 没有 --version 时就正常放行,继续执行子命令
@app.command()
def init(force: bool = typer.Option(False, "--force", "-f", help="强制初始化,可能导致问题,不建议使用。")) -> None:
"""
初始化 NJUPT Suan API [green]工作目录[/green]。(可能需要较长时间)
会在当前目录或指定目录下创建新文件,并尝试安装 playwright chromium。
视网络情况,安装过程可能(几乎必然)需要较长时间。
如果已存在 data 和/或 temp 目录,初始化会失败。你也可以强制初始化,但可能导致先前的数据丢失。
Raises:
typer.Exit: 如果初始化失败,返回 1。
"""
if (DATA_DIR.exists() or TEMP_DIR.exists()) and not force:
console.print(Panel(ALREADY_INIT_MESSAGE, title="工作目录已存在"))
console.print("[bright_black]如果你想要强制初始化,使用 [green]suanapi init -f[/green] 命令。[/bright_black]")
raise typer.Exit(code=1)
# 1 创建 data 和 temp 目录
DATA_DIR.mkdir(parents=True, exist_ok=True)
TEMP_DIR.mkdir(parents=True, exist_ok=True)
console.print(INIT_STAGE_MESSAGE.format(stage=1, message="工作目录 data 和 temp 已创建。"))
# 2 初始化配置文件
try:
config.sync_load_json()
except FileNotFoundError:
config.init_config()
config.sync_create_json()
console.print(INIT_STAGE_MESSAGE.format(stage=2, message="已初始化配置并创建配置文件。"))
else:
console.print(INIT_STAGE_MESSAGE.format(stage=2, message="配置文件已存在,跳过配置初始化。"))
# 3 执行 uv run playwright install chromium
console.print("[bright_black]即将安装 playwright 的 chromium这可能是耗时最长的部分。[/bright_black]")
cp3 = subprocess.run(["playwright", "install", "chromium"], cwd=WORKSPACE_DIR)
if cp3.returncode != 0:
console.print("[yellow]运行 playwright install chromuim 失败,双是什么原因呢?[/yellow]")
raise typer.Exit(code=cp3.returncode)
console.print(INIT_STAGE_MESSAGE.format(stage=3, message="已安装 playwright chromium。"))
console.print("[green]初始化完成。接下来可以执行 suanapi run 来启动 NJUPT Suan API。[/green]")
console.print(
"[bright_black]初始化只需要执行一次即可,以后即使更新 NJUPT Suan API也无再需重复执行。[/bright_black]"
)
@app.command()
def token(reset: bool = typer.Option(False, "--reload", "-r", help="强制重新生成令牌,即使令牌已存在。")) -> None:
"""
查看或重新生成[green]管理后端令牌[/green]。
需要先运行过 init 初始化目录。
Raises:
typer.Exit: 如果未初始化,返回 1。
"""
token_ = None
# 首先检查数据目录是否存在
if not DATA_DIR.exists():
console.print(Panel(NOT_INIT_MESSAGE, title="数据目录不存在"))
raise typer.Exit(code=1)
# 确认存在后再判断是否需要重新生成令牌
if not reset:
try:
with open(file=DATA_DIR / "token.txt", mode="r") as f:
token_ = f.read()
except FileNotFoundError:
pass
if not token_:
console.print("[yellow]重新生成令牌...[/yellow]")
token_ = token_urlsafe(32)
with open(file=DATA_DIR / "token.txt", mode="w") as f:
f.write(token_)
panel = Panel(TOKEN_CHECK_MESSAGE.format(token=token_), title="WebUI 令牌")
console.print(panel)
@app.command()
def run(
host: str | None = typer.Option(None, "--host", help="监听主机名,默认 0.0.0.0。"),
port: int | None = typer.Option(None, "--port", help="监听端口,默认 8000。"),
reload: bool | None = typer.Option(None, "--reload", "-r", help="在检测到代码变化时自动重启,默认 False。"),
) -> None:
"""
运行 NJUPT Suan API。
可接收运行参数。若不提供,则尝试使用配置文件中的值,最后回退到默认值。
Raises:
typer.Exit: 如果未初始化,返回 1。
"""
if host is None:
host = config.get("system", "host", "0.0.0.0")
if port is None:
port = config.get("system", "port", 8000)
if reload is None:
reload = config.get("system", "reload", False)
if not DATA_DIR.exists() or not TEMP_DIR.exists():
console.print(Panel(NOT_INIT_MESSAGE, title="工作目录不存在"))
raise typer.Exit(code=1)
token(False)
console.print(Panel(RUN_CHECK_MESSAGE.format(host=host, port=port, reload=reload), title="运行前检查"))
import uvicorn
uvicorn.run(
"njupt_suan_api.server:app",
host=host,
port=port,
reload=reload,
access_log=False,
log_level="critical",
timeout_graceful_shutdown=2,
)
if __name__ == "__main__":
app()

View File

@@ -0,0 +1,14 @@
from .__version__ import __version__ as __version__
from .admin_router import admin_router
from .api_router import api_router
from .mcp_router import mcp_app
from .webui_router import ASSETS_DIR, webui_router
__all__ = [
admin_router,
api_router,
mcp_app,
webui_router,
__version__,
ASSETS_DIR,
]

View File

@@ -6,11 +6,11 @@ from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlmodel import Session, delete, select
from njupt_api.baselib import config, logger
from njupt_api.zhengfang import LoginError, course_list_serializer, jwxt
from router.enhance.auth import verify_token
from router.enhance.lib import AliasDto, ReturnDto, TestDto, get_session
from router.enhance.model import Alias, Course
from njupt_suan_api.api.baselib import config, logger
from njupt_suan_api.api.zhengfang import LoginError, course_list_serializer, jwxt
from njupt_suan_api.router.enhance.auth import verify_token
from njupt_suan_api.router.enhance.lib import AliasDto, ReturnDto, TestDto, get_session
from njupt_suan_api.router.enhance.model import Alias, Course
class ValidateTokenDto(BaseModel):

View File

@@ -5,15 +5,10 @@ from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import FileResponse
from sqlmodel import Session, select
from njupt_api.baselib import logger
from njupt_api.zhengfang import (
course_dict_serializer,
course_list_serializer,
jwxt,
)
from njupt_api.zhengfang.exc import LoginError
from router.enhance.lib import ReturnDto, ScheduleQueryDto, apply_enhance, get_session
from router.enhance.model import Course
from njupt_suan_api.api.baselib import logger
from njupt_suan_api.api.zhengfang import LoginError, course_dict_serializer, course_list_serializer, jwxt
from njupt_suan_api.router.enhance.lib import ReturnDto, ScheduleQueryDto, apply_enhance, get_session
from njupt_suan_api.router.enhance.model import Course
TEMP_DIR = Path.cwd() / "temp"

View File

@@ -0,0 +1,8 @@
from .lib import ReturnDto, apply_enhance
from .model import create_db_and_tables
__all__ = [
ReturnDto,
apply_enhance,
create_db_and_tables,
]

View File

@@ -7,8 +7,8 @@ from typing import Sequence
from sqlmodel import Session, select
from njupt_api.baselib import logger
from router.enhance.model import Alias, engine
from njupt_suan_api.api.baselib import logger
from njupt_suan_api.router.enhance.model import Alias, engine
def apply_alias(courses: list[dict]) -> list[dict]:

View File

@@ -4,7 +4,7 @@ from typing import Any, Generator, Literal
from pydantic import BaseModel
from sqlmodel import Session
from njupt_api.baselib import config
from njupt_suan_api.api.baselib import config
from .alias import apply_alias
from .model import engine

View File

@@ -3,7 +3,7 @@ from typing import Optional
from sqlalchemy import JSON, Column
from sqlmodel import Field, SQLModel, create_engine
sqlite_file_name = "data/njupt_api.db"
sqlite_file_name = "data/njupt-api.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, connect_args={"check_same_thread": False})

View File

@@ -7,7 +7,7 @@ from uuid import uuid4
from playwright.async_api import ViewportSize
from njupt_api.baselib import PlayContextManager, logger
from njupt_suan_api.api.baselib import PlayContextManager, logger
TEMP_DIR = Path.cwd() / "temp"

View File

@@ -7,15 +7,10 @@ from mcp.types import ToolAnnotations
from pydantic import Field
from sqlmodel import Session, select
from njupt_api.baselib import LoggingMiddleware, logger
from njupt_api.zhengfang import (
course_dict_serializer,
course_list_serializer,
jwxt,
)
from njupt_api.zhengfang.exc import LoginError
from router.enhance.lib import ReturnDto, apply_enhance
from router.enhance.model import Course, engine
from njupt_suan_api.api.baselib import LoggingMiddleware, logger
from njupt_suan_api.api.zhengfang import LoginError, course_dict_serializer, course_list_serializer, jwxt
from njupt_suan_api.router.enhance.lib import ReturnDto, apply_enhance
from njupt_suan_api.router.enhance.model import Course, engine
mcp = FastMCP("NJUPT API Suan")

View File

@@ -4,8 +4,10 @@ import aiofiles
from fastapi import APIRouter
from fastapi.responses import HTMLResponse
WEBUI_INDEX = Path.cwd() / "webui" / "dist" / "index.html"
SCHEDULE_INDEX = Path.cwd() / "webui" / "dist" / "index-schedule.html"
STATIC_DIR = Path(__file__).parent.parent / "static"
WEBUI_INDEX = STATIC_DIR / "index.html"
SCHEDULE_INDEX = STATIC_DIR / "index-schedule.html"
ASSETS_DIR = STATIC_DIR / "assets"
webui_router = APIRouter(prefix="/webui")

View File

@@ -12,20 +12,15 @@ from fastapi.staticfiles import StaticFiles
from fastmcp.utilities.lifespan import combine_lifespans
from watchfiles import awatch
from njupt_api.baselib import (
from njupt_suan_api.api.baselib import (
LogRecord,
config,
log_buffer,
log_record_serialize,
logger,
)
from router import __version__
from router.admin_router import admin_router
from router.api_router import api_router
from router.enhance.lib import ReturnDto
from router.enhance.model import create_db_and_tables
from router.mcp_router import mcp_app
from router.webui_router import webui_router
from njupt_suan_api.router import ASSETS_DIR, __version__, admin_router, api_router, mcp_app, webui_router
from njupt_suan_api.router.enhance import ReturnDto, create_db_and_tables
DATA_DIR = Path.cwd() / "data"
@@ -166,7 +161,7 @@ app.include_router(webui_router)
app.mount("/mcp", mcp_app)
app.mount(
"/assets",
StaticFiles(directory=Path.cwd() / "webui" / "dist" / "assets"),
StaticFiles(directory=ASSETS_DIR),
name="assets",
)

2
uv.lock generated
View File

@@ -702,7 +702,7 @@ wheels = [
[[package]]
name = "njupt-suan-api"
version = "0.1.1"
source = { virtual = "." }
source = { editable = "." }
dependencies = [
{ name = "aiofiles" },
{ name = "beautifulsoup4" },

View File

@@ -35,8 +35,9 @@ export default defineConfig({
input: {
index: path.resolve(__dirname, 'index.html'),
'index-schedule': path.resolve(__dirname, 'index-schedule.html'),
}
},
},
outDir: "../src/njupt_suan_api/static"
},
resolve: {
alias: {