更新为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` 后填写。 本项目默认使用 `.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
- 开发环境 MySQL 5.7,生产 MySQL 8.0+ - 开发环境 MySQL 5.7,生产 MySQL 8.0+
- 表字符集:`utf8mb4`,排序规则:`utf8mb4_unicode_ci` - 表字符集:`utf8mb4`,排序规则:`utf8mb4_unicode_ci`

View File

@ -17,6 +17,11 @@ if config.config_file_name is not None:
def get_url() -> str: def get_url() -> str:
settings = get_settings() 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 ( return (
f"mysql+pymysql://{settings.db_user}:{settings.db_password}" f"mysql+pymysql://{settings.db_user}:{settings.db_password}"
f"@{settings.db_host}:{settings.db_port}/{settings.db_name}" 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) @router.get("/summary", response_model=DashboardSummary)
def get_dashboard_summary(db: Session = Depends(get_db), redis: Redis = Depends(get_redis)): 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() # 2. 今日新增
start = datetime(now.year, now.month, now.day) today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
end = start + timedelta(days=1) new_today = db.query(User).filter(
new_today = ( User.created_at >= today_start,
db.query(func.count(User.user_id)) User.is_deleted == 0
.filter(User.is_deleted.is_(False), User.created_at >= start, User.created_at < end) ).count()
.scalar()
or 0
)
try: try:
online = len(redis.keys("auth:online:*")) online = len(redis.keys("auth:online:*"))

View File

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

View File

@ -53,8 +53,11 @@ def delete_role(role_id: int, db: Session = Depends(get_db)):
if not role: if not role:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found") 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: if user_count > 0:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete role with assigned users") 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 = ( users = (
db.query(User) db.query(User)
.join(UserRole, UserRole.user_id == User.user_id) .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() .all()
) )
return users return users

View File

@ -80,7 +80,8 @@ def upload_avatar(
@router.get("", response_model=list[UserOut]) @router.get("", response_model=list[UserOut])
def list_users(db: Session = Depends(get_db)): 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) @router.post("", response_model=UserOut)
@ -97,7 +98,7 @@ def create_user(payload: UserCreate, db: Session = Depends(get_db)):
phone=payload.phone, phone=payload.phone,
password_hash=hash_password(password), password_hash=hash_password(password),
status=payload.status, status=payload.status,
is_deleted=False, is_deleted=0,
) )
db.add(user) db.add(user)
db.flush() db.flush()
@ -112,7 +113,7 @@ def create_user(payload: UserCreate, db: Session = Depends(get_db)):
@router.put("/{user_id}", response_model=UserOut) @router.put("/{user_id}", response_model=UserOut)
def update_user(user_id: int, payload: UserUpdate, db: Session = Depends(get_db)): 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: if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") 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}") @router.delete("/{user_id}")
def delete_user(user_id: int, db: Session = Depends(get_db)): 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: if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
user.is_deleted = True 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") @router.post("/{user_id}/reset-password")
def reset_password(user_id: int, password: str, db: Session = Depends(get_db)): 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: if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
user.password_hash = hash_password(password) user.password_hash = hash_password(password)

View File

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

View File

@ -7,6 +7,11 @@ settings = get_settings()
def build_db_url() -> str: 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 ( return (
f"mysql+pymysql://{settings.db_user}:{settings.db_password}" f"mysql+pymysql://{settings.db_user}:{settings.db_password}"
f"@{settings.db_host}:{settings.db_port}/{settings.db_name}" 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") redis.setex(online_key, found_ttl, "1")
else: else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Session expired") 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): if not user or user.status != int(StatusEnum.ENABLED):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User disabled") 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 sqlalchemy.orm import Mapped, mapped_column
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS 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)) base_url: Mapped[str | None] = mapped_column(String(255))
api_path: Mapped[str | None] = mapped_column(String(100)) api_path: Mapped[str | None] = mapped_column(String(100))
config: Mapped[dict | None] = mapped_column(JSON) 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) 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.orm import Mapped, mapped_column
from sqlalchemy import Enum, UniqueConstraint from sqlalchemy import Enum, UniqueConstraint
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS
@ -21,5 +21,6 @@ class SystemParam(Base, TimestampMixin):
nullable=False, nullable=False,
) )
status: Mapped[int] = mapped_column(Integer, default=StatusEnum.ENABLED, 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) 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.orm import Mapped, mapped_column, relationship
from sqlalchemy import Enum, JSON from sqlalchemy import Enum, JSON
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS
@ -21,7 +21,7 @@ class Permission(Base, TimestampMixin):
path: Mapped[str | None] = mapped_column(String(255)) path: Mapped[str | None] = mapped_column(String(255))
icon: Mapped[str | None] = mapped_column(String(100)) icon: Mapped[str | None] = mapped_column(String(100))
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False) 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) status: Mapped[int] = mapped_column(Integer, default=StatusEnum.ENABLED, nullable=False)
description: Mapped[str | None] = mapped_column(Text) description: Mapped[str | None] = mapped_column(Text)
meta: Mapped[dict | None] = mapped_column(JSON) meta: Mapped[dict | None] = mapped_column(JSON)

View File

@ -1,5 +1,5 @@
from datetime import datetime 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 sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS
@ -14,10 +14,11 @@ class PromptTemplate(Base, TimestampMixin):
content: Mapped[str] = mapped_column(Text, nullable=False) content: Mapped[str] = mapped_column(Text, nullable=False)
description: Mapped[str | None] = mapped_column(String(255)) 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) 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) status: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False) sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
user_configs = relationship("UserPromptConfig", back_populates="template", cascade="all, delete-orphan") user_configs = relationship("UserPromptConfig", back_populates="template", cascade="all, delete-orphan")
@ -31,7 +32,8 @@ class UserPromptConfig(Base, TimestampMixin):
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) 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) 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) 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) user_sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
template = relationship("PromptTemplate", back_populates="user_configs") 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 sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS from .base import Base, TimestampMixin, MYSQL_TABLE_ARGS
from .enums import StatusEnum from .enums import StatusEnum
@ -16,6 +16,7 @@ class User(Base, TimestampMixin):
password_hash: Mapped[str] = mapped_column(String(255), nullable=False) password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
avatar: Mapped[str | None] = mapped_column(String(255)) avatar: Mapped[str | None] = mapped_column(String(255))
status: Mapped[int] = mapped_column(Integer, default=StatusEnum.ENABLED, nullable=False) 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") 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: def authenticate_user(db: Session, username: str, password: str) -> User | None:
user = ( user = db.query(User).filter(
db.query(User) User.username == username,
.filter(User.username == username, User.status == int(StatusEnum.ENABLED), User.is_deleted.is_(False)) User.is_deleted == 0,
.first() User.status == 1
) ).first()
if not user: if not user:
return None return None
if not verify_password(password, user.password_hash): 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: if not user_id:
raise ValueError("Refresh token expired") 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): if not user or user.status != int(StatusEnum.ENABLED):
raise ValueError("User not found") raise ValueError("User not found")

View File

@ -11,3 +11,6 @@ python-jose[cryptography]==3.3.0
redis==5.1.1 redis==5.1.1
pymysql==1.1.1 pymysql==1.1.1
psutil==5.9.8 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 import os
from sqlalchemy import create_engine
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.config import get_settings from app.core.config import get_settings
from app.core.security import hash_password from app.core.security import hash_password
from app.core.db import engine
from app.models import User, Role, UserRole 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(): def main():
username = os.getenv("INIT_ADMIN_USERNAME", "admin") username = os.getenv("INIT_ADMIN_USERNAME", "admin")
password = os.getenv("INIT_ADMIN_PASSWORD", "123456") password = os.getenv("INIT_ADMIN_PASSWORD", "123456")
engine = create_engine(build_db_url(), pool_pre_ping=True)
with Session(engine) as db: with Session(engine) as db:
role = db.query(Role).filter(Role.role_code == "admin").first() role = db.query(Role).filter(Role.role_code == "admin").first()
if not role: if not role:
@ -36,13 +26,14 @@ def main():
phone=None, phone=None,
password_hash=hash_password(password), password_hash=hash_password(password),
status=1, status=1,
is_deleted=False, is_deleted=0,
) )
db.add(user) db.add(user)
db.flush() db.flush()
else: else:
user.password_hash = hash_password(password) user.password_hash = hash_password(password)
user.status = 1 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() exist_link = db.query(UserRole).filter(UserRole.user_id == user.user_id, UserRole.role_id == role.role_id).first()
if not exist_link: 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" "react-router-dom": "^6.27.0"
}, },
"devDependencies": { "devDependencies": {
"@rollup/rollup-linux-arm64-gnu": "^4.59.0",
"@types/react": "^18.3.12", "@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3", "@vitejs/plugin-react": "^4.3.3",
@ -667,6 +668,32 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.57.1", "version": "4.57.1",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
@ -681,6 +708,291 @@
"darwin" "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": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -3803,6 +4115,19 @@
"fsevents": "~2.3.2" "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": { "node_modules/rw": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz",

View File

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

View File

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

View File

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

View File

@ -101,7 +101,7 @@ export default function LogManagePage() {
description="查看系统用户的操作记录和审计日志" 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 form={form} layout="inline">
<Form.Item name="username" label="用户"> <Form.Item name="username" label="用户">
<Input placeholder="请输入用户名" /> <Input placeholder="请输入用户名" />

View File

@ -270,7 +270,7 @@ const ModelManage: React.FC = () => {
return ( return (
<div className="settings-container" style={{ padding: '24px', background: '#f5f7f9', minHeight: '100vh' }}> <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 <Tabs
activeKey={activeTab} activeKey={activeTab}
onChange={setActiveTab} onChange={setActiveTab}

View File

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

View File

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

View File

@ -238,11 +238,6 @@
resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz" resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== 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": "@gera2ld/jsx-dom@^2.2.2":
version "2.2.2" version "2.2.2"
resolved "https://registry.npmmirror.com/@gera2ld/jsx-dom/-/jsx-dom-2.2.2.tgz" 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" resolved "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz"
integrity sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA== 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" version "4.57.1"
resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz" resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz"
integrity sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg== 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": "@types/babel__core@^7.20.5":
version "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" resolved "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 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: gensync@^1.0.0-beta.2:
version "1.0.0-beta.2" version "1.0.0-beta.2"
resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz" resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz"