更新为pg

alan-pg
tanlianwang 2026-02-26 12:35:41 +08:00
parent 01c7e9118b
commit 28481aa27c
29 changed files with 13202 additions and 77 deletions

View File

@ -2,6 +2,15 @@
本项目默认使用 `.env` 读取配置。请复制 `backend/.env.example``backend/.env` 后填写。
## Database Support
- Default: PostgreSQL (DB_TYPE=postgresql)
- Supported: MySQL (set `DB_TYPE=mysql` in `.env`)
## PostgreSQL
- Version 12+
- Default port: 5432
- Use `backend/scripts/convert_sql.py` to convert MySQL dumps if needed.
## MySQL
- 开发环境 MySQL 5.7,生产 MySQL 8.0+
- 表字符集:`utf8mb4`,排序规则:`utf8mb4_unicode_ci`

View File

@ -17,6 +17,11 @@ if config.config_file_name is not None:
def get_url() -> str:
settings = get_settings()
if settings.db_type == "postgresql":
return (
f"postgresql+psycopg2://{settings.db_user}:{settings.db_password}"
f"@{settings.db_host}:{settings.db_port}/{settings.db_name}"
)
return (
f"mysql+pymysql://{settings.db_user}:{settings.db_password}"
f"@{settings.db_host}:{settings.db_port}/{settings.db_name}"

View File

@ -61,17 +61,14 @@ def _get_disk_usage(path: Path) -> float:
@router.get("/summary", response_model=DashboardSummary)
def get_dashboard_summary(db: Session = Depends(get_db), redis: Redis = Depends(get_redis)):
total_users = db.query(func.count(User.user_id)).filter(User.is_deleted.is_(False)).scalar() or 0
total_users = db.query(User).filter(User.is_deleted == 0).count()
now = datetime.now()
start = datetime(now.year, now.month, now.day)
end = start + timedelta(days=1)
new_today = (
db.query(func.count(User.user_id))
.filter(User.is_deleted.is_(False), User.created_at >= start, User.created_at < end)
.scalar()
or 0
)
# 2. 今日新增
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
new_today = db.query(User).filter(
User.created_at >= today_start,
User.is_deleted == 0
).count()
try:
online = len(redis.keys("auth:online:*"))

View File

@ -35,11 +35,11 @@ def list_prompts(
# 系统管理入口:仅展示系统级模板
if not is_admin(current_user):
raise HTTPException(status_code=403, detail="无权访问系统模板库")
filters.append(PromptTemplate.is_system == True)
filters.append(PromptTemplate.is_system == 1)
else:
# 个人管理入口:展示已发布的系统模板 + 自己的个人模板
accessibility_filter = or_(
and_(PromptTemplate.is_system == True, PromptTemplate.status == 1),
and_(PromptTemplate.is_system == 1, PromptTemplate.status == 1),
PromptTemplate.user_id == current_user.user_id
)
filters.append(accessibility_filter)
@ -67,7 +67,7 @@ def list_prompts(
out = []
for template, is_active, user_sort_order in results:
item = PromptTemplateOut.model_validate(template)
item.is_active = is_active if is_active is not None else True
item.is_active = is_active if is_active is not None else 1
item.user_sort_order = user_sort_order if user_sort_order is not None else template.sort_order
out.append(item)
@ -94,7 +94,7 @@ def create_prompt(
item_data["user_id"] = None
else:
item_data["user_id"] = current_user.user_id
item_data["is_system"] = False
item_data["is_system"] = 0
item = PromptTemplate(**item_data)
db.add(item)

View File

@ -53,8 +53,11 @@ def delete_role(role_id: int, db: Session = Depends(get_db)):
if not role:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found")
# Check if users are assigned
user_count = db.query(UserRole).filter(UserRole.role_id == role_id).count()
# 检查是否有用户关联了该角色
user_count = db.query(User).filter(
User.role_ids.contains([role_id]),
User.is_deleted == 0
).count()
if user_count > 0:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete role with assigned users")
@ -91,7 +94,7 @@ def get_role_users(role_id: int, db: Session = Depends(get_db)):
users = (
db.query(User)
.join(UserRole, UserRole.user_id == User.user_id)
.filter(UserRole.role_id == role_id, User.is_deleted.is_(False))
.filter(UserRole.role_id == role_id, User.is_deleted == 0)
.all()
)
return users

View File

@ -80,7 +80,8 @@ def upload_avatar(
@router.get("", response_model=list[UserOut])
def list_users(db: Session = Depends(get_db)):
return db.query(User).filter(User.is_deleted.is_(False)).all()
query = db.query(User).filter(User.is_deleted == 0)
return query.all()
@router.post("", response_model=UserOut)
@ -97,7 +98,7 @@ def create_user(payload: UserCreate, db: Session = Depends(get_db)):
phone=payload.phone,
password_hash=hash_password(password),
status=payload.status,
is_deleted=False,
is_deleted=0,
)
db.add(user)
db.flush()
@ -112,7 +113,7 @@ def create_user(payload: UserCreate, db: Session = Depends(get_db)):
@router.put("/{user_id}", response_model=UserOut)
def update_user(user_id: int, payload: UserUpdate, db: Session = Depends(get_db)):
user = db.query(User).filter(User.user_id == user_id, User.is_deleted.is_(False)).first()
user = db.query(User).filter(User.user_id == user_id, User.is_deleted == 0).first()
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
@ -137,7 +138,7 @@ def update_user(user_id: int, payload: UserUpdate, db: Session = Depends(get_db)
@router.delete("/{user_id}")
def delete_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.user_id == user_id, User.is_deleted.is_(False)).first()
user = db.query(User).filter(User.user_id == user_id, User.is_deleted == 0).first()
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
user.is_deleted = True
@ -167,7 +168,7 @@ def update_user_roles(user_id: int, role_ids: list[int], db: Session = Depends(g
@router.post("/{user_id}/reset-password")
def reset_password(user_id: int, password: str, db: Session = Depends(get_db)):
user = db.query(User).filter(User.user_id == user_id, User.is_deleted.is_(False)).first()
user = db.query(User).filter(User.user_id == user_id, User.is_deleted == 0).first()
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
user.password_hash = hash_password(password)

View File

@ -13,10 +13,11 @@ class Settings(BaseSettings):
api_v1_prefix: str = "/api/v1"
# Database
db_type: str = "postgresql" # mysql or postgresql
db_host: str = "127.0.0.1"
db_port: int = 3306
db_user: str = "root"
db_password: str = ""
db_port: int = 5432
db_user: str = "postgres"
db_password: str = "postgres"
db_name: str = "nex_basse"
db_echo: bool = False

View File

@ -7,6 +7,11 @@ settings = get_settings()
def build_db_url() -> str:
if settings.db_type == "postgresql":
return (
f"postgresql+psycopg2://{settings.db_user}:{settings.db_password}"
f"@{settings.db_host}:{settings.db_port}/{settings.db_name}"
)
return (
f"mysql+pymysql://{settings.db_user}:{settings.db_password}"
f"@{settings.db_host}:{settings.db_port}/{settings.db_name}"

View File

@ -51,7 +51,7 @@ def get_current_user(
redis.setex(online_key, found_ttl, "1")
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Session expired")
user = db.query(User).filter(User.user_id == int(user_id), User.is_deleted.is_(False)).first()
user = db.query(User).filter(User.user_id == int(user_id), User.is_deleted == 0).first()
if not user or user.status != int(StatusEnum.ENABLED):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User disabled")

View File

@ -1,4 +1,4 @@
from sqlalchemy import String, Integer, Boolean, JSON
from sqlalchemy import String, Integer, SmallInteger, JSON
from sqlalchemy.orm import Mapped, mapped_column
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS
@ -15,5 +15,6 @@ class AIModel(Base, TimestampMixin):
base_url: Mapped[str | None] = mapped_column(String(255))
api_path: Mapped[str | None] = mapped_column(String(100))
config: Mapped[dict | None] = mapped_column(JSON)
is_default: Mapped[bool] = mapped_column(Boolean, default=False)
# Changed Boolean to SmallInteger
is_default: Mapped[int] = mapped_column(SmallInteger, default=0)
status: Mapped[int] = mapped_column(Integer, default=1)

View File

@ -1,4 +1,4 @@
from sqlalchemy import String, Integer, Text, Boolean
from sqlalchemy import String, Integer, Text, SmallInteger
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import Enum, UniqueConstraint
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS
@ -21,5 +21,6 @@ class SystemParam(Base, TimestampMixin):
nullable=False,
)
status: Mapped[int] = mapped_column(Integer, default=StatusEnum.ENABLED, nullable=False)
is_system: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# Changed Boolean to SmallInteger
is_system: Mapped[int] = mapped_column(SmallInteger, default=0, nullable=False)
description: Mapped[str | None] = mapped_column(Text)

View File

@ -1,4 +1,4 @@
from sqlalchemy import String, Integer, Boolean, Text
from sqlalchemy import String, Integer, SmallInteger, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy import Enum, JSON
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS
@ -21,7 +21,7 @@ class Permission(Base, TimestampMixin):
path: Mapped[str | None] = mapped_column(String(255))
icon: Mapped[str | None] = mapped_column(String(100))
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
is_visible: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
is_visible: Mapped[int] = mapped_column(SmallInteger, default=1, nullable=False)
status: Mapped[int] = mapped_column(Integer, default=StatusEnum.ENABLED, nullable=False)
description: Mapped[str | None] = mapped_column(Text)
meta: Mapped[dict | None] = mapped_column(JSON)

View File

@ -1,5 +1,5 @@
from datetime import datetime
from sqlalchemy import String, Integer, Boolean, Text, ForeignKey, UniqueConstraint, DateTime
from sqlalchemy import String, Integer, SmallInteger, Text, ForeignKey, UniqueConstraint, DateTime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS
@ -14,7 +14,8 @@ class PromptTemplate(Base, TimestampMixin):
content: Mapped[str] = mapped_column(Text, nullable=False)
description: Mapped[str | None] = mapped_column(String(255))
user_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("sys_user.user_id", ondelete="SET NULL"), index=True)
is_system: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# Changed Boolean to SmallInteger
is_system: Mapped[int] = mapped_column(SmallInteger, default=0, nullable=False)
status: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
@ -31,7 +32,8 @@ class UserPromptConfig(Base, TimestampMixin):
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("sys_user.user_id", ondelete="CASCADE"), nullable=False, index=True)
template_id: Mapped[int] = mapped_column(Integer, ForeignKey("biz_prompt_template.id", ondelete="CASCADE"), nullable=False, index=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
# Changed Boolean to SmallInteger
is_active: Mapped[int] = mapped_column(SmallInteger, default=1, nullable=False)
user_sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
template = relationship("PromptTemplate", back_populates="user_configs")

View File

@ -1,4 +1,4 @@
from sqlalchemy import String, Boolean, Integer
from sqlalchemy import String, Integer, SmallInteger
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS
from .enums import StatusEnum
@ -16,6 +16,7 @@ class User(Base, TimestampMixin):
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
avatar: Mapped[str | None] = mapped_column(String(255))
status: Mapped[int] = mapped_column(Integer, default=StatusEnum.ENABLED, nullable=False)
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# Changed Boolean to SmallInteger for compatibility with existing data (0/1)
is_deleted: Mapped[int] = mapped_column(SmallInteger, default=0, nullable=False)
roles = relationship("UserRole", back_populates="user")

View File

@ -13,11 +13,11 @@ settings = get_settings()
def authenticate_user(db: Session, username: str, password: str) -> User | None:
user = (
db.query(User)
.filter(User.username == username, User.status == int(StatusEnum.ENABLED), User.is_deleted.is_(False))
.first()
)
user = db.query(User).filter(
User.username == username,
User.is_deleted == 0,
User.status == 1
).first()
if not user:
return None
if not verify_password(password, user.password_hash):
@ -61,7 +61,7 @@ def refresh_access_token(db: Session, redis: Redis, refresh_token: str) -> tuple
if not user_id:
raise ValueError("Refresh token expired")
user = db.query(User).filter(User.user_id == int(user_id), User.is_deleted.is_(False)).first()
user = db.query(User).filter(User.user_id == int(user_id), User.is_deleted == 0).first()
if not user or user.status != int(StatusEnum.ENABLED):
raise ValueError("User not found")

View File

@ -11,3 +11,6 @@ python-jose[cryptography]==3.3.0
redis==5.1.1
pymysql==1.1.1
psutil==5.9.8
psycopg2-binary==2.9.9
python-multipart
httpx

View File

@ -0,0 +1,176 @@
import re
import sys
def convert_mysql_to_pg(input_file, output_file):
with open(input_file, 'r', encoding='utf-8') as f:
content = f.read()
# 1. Pre-process: Remove comments and basic cleanup
lines = content.splitlines()
filtered_lines = []
for line in lines:
stripped = line.strip()
if stripped.startswith('/*') or stripped.startswith('--') or stripped == '':
continue
if stripped.startswith('SET ') or stripped.startswith('LOCK TABLES') or stripped.startswith('UNLOCK TABLES'):
continue
filtered_lines.append(line)
content = '\n'.join(filtered_lines)
# 2. Global replacements (safe ones)
# Backticks to double quotes
content = content.replace('`', '"')
# 3. Line-by-line processing for schema definitions
lines = content.splitlines()
final_lines = []
current_table = None
deferred_indexes = []
deferred_fks = []
for line in lines:
stripped = line.strip()
# Track current table
table_match = re.match(r'CREATE TABLE "(\w+)"', stripped)
if table_match:
current_table = table_match.group(1)
# Identify if this line is likely a column definition
# It should start with whitespace and a quoted identifier
# And NOT be an INSERT statement
is_column_def = stripped.startswith('"') and 'INSERT INTO' not in line
if is_column_def:
# Data types
# tinyint(1) -> SMALLINT
line = re.sub(r'tinyint\(1\)', 'SMALLINT', line, flags=re.IGNORECASE)
# tinyint -> SMALLINT (catch-all for other widths or no width)
line = re.sub(r'\btinyint(\(\d+\))?', 'SMALLINT', line, flags=re.IGNORECASE)
line = re.sub(r'int\(\d+\)', 'INTEGER', line, flags=re.IGNORECASE)
# Standalone int -> INTEGER (only in column defs)
line = re.sub(r'\bint\b', 'INTEGER', line, flags=re.IGNORECASE)
# datetime -> TIMESTAMP
line = re.sub(r'\bdatetime\b', 'TIMESTAMP', line, flags=re.IGNORECASE)
# Varchar case
line = re.sub(r'varchar\(\d+\)', lambda m: m.group(0).upper(), line, flags=re.IGNORECASE)
# Remove MySQL specific column attributes
line = re.sub(r'\s+CHARACTER\s+SET\s+[\w]+', '', line, flags=re.IGNORECASE)
line = re.sub(r'\s+COLLATE\s+[\w]+', '', line, flags=re.IGNORECASE)
# AUTO_INCREMENT -> SERIAL
# Pattern: "id" INTEGER NOT NULL AUTO_INCREMENT
# We want: "id" SERIAL
if 'AUTO_INCREMENT' in line:
# Handle INTEGER
line = re.sub(r'("[\w]+")\s+INTEGER\s+NOT\s+NULL\s+AUTO_INCREMENT', r'\1 SERIAL', line, flags=re.IGNORECASE)
# Handle BIGINT
line = re.sub(r'("[\w]+")\s+bigint\s+NOT\s+NULL\s+AUTO_INCREMENT', r'\1 BIGSERIAL', line, flags=re.IGNORECASE)
# Remove AUTO_INCREMENT if still present (e.g. not matched above)
line = re.sub(r'\s+AUTO_INCREMENT', '', line, flags=re.IGNORECASE)
# Remove COMMENT
line = re.sub(r"\s+COMMENT\s+'[^']*'", "", line, flags=re.IGNORECASE)
# Remove ON UPDATE ...
line = re.sub(r'\s+ON\s+UPDATE\s+CURRENT_TIMESTAMP', '', line, flags=re.IGNORECASE)
# Handle Keys
# PRIMARY KEY is usually fine: PRIMARY KEY ("id")
# UNIQUE KEY "name" (...) -> CONSTRAINT "name" UNIQUE (...)
if 'UNIQUE KEY' in line:
line = re.sub(r'UNIQUE KEY\s+"(\w+)"\s+(\(.*\))', r'CONSTRAINT "\1" UNIQUE \2', line, flags=re.IGNORECASE)
# KEY "name" (...) -> Extract to CREATE INDEX (skip PRIMARY, UNIQUE, FOREIGN)
# MySQL: KEY "idx_name" ("col1", "col2")
# Postgres: CREATE INDEX "idx_name" ON "table_name" ("col1", "col2");
if re.search(r'^\s*KEY\s+"', line) and 'PRIMARY' not in line and 'UNIQUE' not in line and 'FOREIGN' not in line:
key_match = re.search(r'^\s*KEY\s+"(\w+)"\s+(\(.*\))', line)
if key_match and current_table:
idx_name = key_match.group(1)
idx_cols = key_match.group(2)
deferred_indexes.append(f'CREATE INDEX "{idx_name}" ON "{current_table}" {idx_cols};')
continue # Skip this line in CREATE TABLE
else:
# Fallback if regex fails, just comment it out to avoid syntax error
line = "-- " + line
# CREATE TABLE line cleanup
if stripped.startswith('CREATE TABLE'):
# usually fine, but check for modifiers?
pass
# Foreign Key Cleanup
if 'FOREIGN KEY' in line:
# Remove db.table references like "nex_docus"."users" -> "users"
line = re.sub(r'"[\w]+"\."([\w]+)"', r'"\1"', line)
# Fix "users" -> "sys_user" if applicable
line = line.replace('"users"', '"sys_user"')
# Fix sys_user PK reference (id -> user_id)
if 'REFERENCES "sys_user"' in line:
line = line.replace('("id")', '("user_id")')
# Extract CONSTRAINT definition to defer it
# Remove trailing comma
constraint_def = line.strip().rstrip(',')
if current_table:
deferred_fks.append(f'ALTER TABLE "{current_table}" ADD {constraint_def};')
continue # Skip adding to CREATE TABLE
# Remove USING BTREE
line = re.sub(r'\s+USING\s+BTREE', '', line, flags=re.IGNORECASE)
# End of table definition cleanup
if stripped.startswith(') ENGINE='):
line = ');'
elif stripped.startswith(') DEFAULT CHARSET='):
line = ');'
elif ') ENGINE=' in line:
line = re.sub(r'\)\s*ENGINE=[^;]+;', ');', line, flags=re.IGNORECASE)
# Global string escaping for INSERTs
if 'INSERT INTO' in line:
line = line.replace(r'\"', '"')
line = line.replace(r"\'", "''")
# Ensure json type is spaced (if json keyword appears)
if 'json' in line.lower() and is_column_def:
line = re.sub(r'\bjson\b', 'JSON', line, flags=re.IGNORECASE)
final_lines.append(line)
# Append deferred indexes
if deferred_indexes:
final_lines.append("\n-- Deferred Indexes")
final_lines.extend(deferred_indexes)
# Append deferred FKs
if deferred_fks:
final_lines.append("\n-- Deferred Foreign Keys")
final_lines.extend(deferred_fks)
content = '\n'.join(final_lines)
# Fix trailing commas before );
# Regex to find comma followed by newline and );
# Or just comma followed by whitespace and );
content = re.sub(r',\s*\);', ');', content)
with open(output_file, 'w', encoding='utf-8') as f:
f.write(content)
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Usage: python convert_sql.py <input_file> <output_file>")
sys.exit(1)
convert_mysql_to_pg(sys.argv[1], sys.argv[2])

View File

@ -1,25 +1,15 @@
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from app.core.config import get_settings
from app.core.security import hash_password
from app.core.db import engine
from app.models import User, Role, UserRole
def build_db_url() -> str:
settings = get_settings()
return (
f"mysql+pymysql://{settings.db_user}:{settings.db_password}"
f"@{settings.db_host}:{settings.db_port}/{settings.db_name}"
"?charset=utf8mb4"
)
def main():
username = os.getenv("INIT_ADMIN_USERNAME", "admin")
password = os.getenv("INIT_ADMIN_PASSWORD", "123456")
engine = create_engine(build_db_url(), pool_pre_ping=True)
with Session(engine) as db:
role = db.query(Role).filter(Role.role_code == "admin").first()
if not role:
@ -36,13 +26,14 @@ def main():
phone=None,
password_hash=hash_password(password),
status=1,
is_deleted=False,
is_deleted=0,
)
db.add(user)
db.flush()
else:
user.password_hash = hash_password(password)
user.status = 1
user.is_deleted = 0
exist_link = db.query(UserRole).filter(UserRole.user_id == user.user_id, UserRole.role_id == role.role_id).first()
if not exist_link:

View File

@ -0,0 +1,45 @@
import sys
import os
# Add backend to path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from app.core.db import SessionLocal
from app.models import Meeting, User, SummarizeTask, TranscriptTask
from sqlalchemy import desc
def test_tasks():
db = SessionLocal()
try:
print("Querying SummarizeTask...")
sum_query = db.query(SummarizeTask, Meeting.title, User.username).join(
Meeting, SummarizeTask.meeting_id == Meeting.meeting_id
).join(
User, Meeting.user_id == User.user_id
)
results = sum_query.order_by(desc(SummarizeTask.created_at)).limit(50).all()
print(f"Found {len(results)} summarize tasks")
for t, m_title, username in results:
print(f"Task: {t.task_id}, Meeting: {m_title}, User: {username}, Created: {t.created_at}")
print("\nQuerying TranscriptTask...")
trans_query = db.query(TranscriptTask, Meeting.title, User.username).join(
Meeting, TranscriptTask.meeting_id == Meeting.meeting_id
).join(
User, Meeting.user_id == User.user_id
)
results = trans_query.order_by(desc(TranscriptTask.created_at)).limit(50).all()
print(f"Found {len(results)} transcript tasks")
for t, m_title, username in results:
print(f"Task: {t.task_id}, Meeting: {m_title}, User: {username}, Created: {t.created_at}")
except Exception as e:
import traceback
traceback.print_exc()
finally:
db.close()
if __name__ == "__main__":
test_tasks()

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,7 @@
"react-router-dom": "^6.27.0"
},
"devDependencies": {
"@rollup/rollup-linux-arm64-gnu": "^4.59.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
@ -667,6 +668,32 @@
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
"integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
"integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.57.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
@ -681,6 +708,291 @@
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
"integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
"integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
"integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
"integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
"integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [
"arm64"
],
"dev": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
"integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
"integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
"integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
"integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
"integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
"integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
"integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
"integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
"integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
"integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
"integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
"integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
"integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
"integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
"integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
"integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -3803,6 +4115,19 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
"integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/rw": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz",

View File

@ -21,6 +21,7 @@
"react-router-dom": "^6.27.0"
},
"devDependencies": {
"@rollup/rollup-linux-arm64-gnu": "^4.59.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",

View File

@ -175,7 +175,7 @@ export default function DashboardPage() {
{/* Task Monitor Section */}
<Card
bordered={false}
variant="borderless"
style={{ marginTop: 24, borderRadius: '16px', boxShadow: '0 4px 20px rgba(0,0,0,0.05)' }}
title={
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>

View File

@ -133,7 +133,7 @@ const GlobalPromptManage: React.FC = () => {
return (
<div className="page-wrapper" style={{ padding: '24px' }}>
<Card
bordered={false}
variant="borderless"
title={
<Space size="large">
<Space><FileTextOutlined style={{ color: '#1890ff' }} /><strong></strong></Space>
@ -161,7 +161,7 @@ const GlobalPromptManage: React.FC = () => {
<Form form={form} layout="vertical">
<Form.Item name="name" label="模板名称" rules={[{ required: true }]}><Input /></Form.Item>
<Form.Item name="category" label="分类" rules={[{ required: true }]}>
<Select>{categories.map(c => <Option key={c.id} value={c.item_value}>{c.item_label}</Option>)}</Select>
<Select>{categories.map(c => <Option key={c.item_value} value={c.item_value}>{c.item_label}</Option>)}</Select>
</Form.Item>
<Form.Item name="content" label="Prompt 内容" rules={[{ required: true }]}><Input.TextArea rows={12} /></Form.Item>
<Form.Item name="description" label="详细描述"><Input.TextArea rows={3} /></Form.Item>

View File

@ -101,7 +101,7 @@ export default function LogManagePage() {
description="查看系统用户的操作记录和审计日志"
/>
<Card bordered={false} className="shadow-card" style={{ marginTop: 24, marginBottom: 24 }}>
<Card variant="borderless" className="shadow-card" style={{ marginTop: 24, marginBottom: 24 }}>
<Form form={form} layout="inline">
<Form.Item name="username" label="用户">
<Input placeholder="请输入用户名" />

View File

@ -270,7 +270,7 @@ const ModelManage: React.FC = () => {
return (
<div className="settings-container" style={{ padding: '24px', background: '#f5f7f9', minHeight: '100vh' }}>
<Card bordered={false} styles={{ body: { padding: 0 } }} style={{ borderRadius: '12px', overflow: 'hidden' }}>
<Card variant="borderless" styles={{ body: { padding: 0 } }} style={{ borderRadius: '12px', overflow: 'hidden' }}>
<Tabs
activeKey={activeTab}
onChange={setActiveTab}

View File

@ -189,7 +189,7 @@ const PromptManage: React.FC = () => {
return (
<div className="page-wrapper" style={{ padding: '24px' }}>
<Card
bordered={false}
variant="borderless"
title={
<Space size="large">
<Space><FileTextOutlined style={{ color: '#1890ff' }} /><strong></strong></Space>

View File

@ -111,7 +111,7 @@ const TaskMonitor: React.FC = () => {
</Space>
</div>
<Card bordered={false} styles={{ body: { padding: 0 } }}>
<Card variant="borderless" styles={{ body: { padding: 0 } }}>
<ListTable
columns={columns}
dataSource={tasks}

View File

@ -238,11 +238,6 @@
resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@esbuild/darwin-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz"
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
"@gera2ld/jsx-dom@^2.2.2":
version "2.2.2"
resolved "https://registry.npmmirror.com/@gera2ld/jsx-dom/-/jsx-dom-2.2.2.tgz"
@ -374,10 +369,20 @@
resolved "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz"
integrity sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==
"@rollup/rollup-darwin-arm64@4.57.1":
"@rollup/rollup-linux-arm64-gnu@^4.59.0":
version "4.59.0"
resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz"
integrity sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==
"@rollup/rollup-linux-arm64-gnu@4.57.1":
version "4.57.1"
resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz"
integrity sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==
resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz"
integrity sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==
"@rollup/rollup-linux-arm64-musl@4.57.1":
version "4.57.1"
resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz"
integrity sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==
"@types/babel__core@^7.20.5":
version "7.20.5"
@ -1092,11 +1097,6 @@ extend@^3.0.0:
resolved "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz"