This commit is contained in:
2026-08-06 13:55:51 +08:00
parent 1e54e3f7f4
commit 4766bab453
13 changed files with 1306 additions and 394 deletions
File diff suppressed because one or more lines are too long
+36 -36
View File
@@ -1,37 +1,37 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>后台管理</title>
<script>
(function() {
var cached = localStorage.getItem('siteInfo');
if (cached) {
try {
var info = JSON.parse(cached);
if (info.siteName) {
document.title = info.siteName + ' - 管理后台';
}
if (info.siteLogo) {
var link = document.querySelector('link[rel="icon"]');
if (link) {
link.href = info.siteLogo;
link.type = 'image/png';
}
}
} catch (e) {}
}
})();
</script>
<script type="module" crossorigin src="/assets/index-BZDhy9nW.js"></script>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>后台管理</title>
<script>
(function() {
var cached = localStorage.getItem('siteInfo');
if (cached) {
try {
var info = JSON.parse(cached);
if (info.siteName) {
document.title = info.siteName + ' - 管理后台';
}
if (info.siteLogo) {
var link = document.querySelector('link[rel="icon"]');
if (link) {
link.href = info.siteLogo;
link.type = 'image/png';
}
}
} catch (e) {}
}
})();
</script>
<script type="module" crossorigin src="/assets/index-C5-PCv54.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D3fwIbOp.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
</head>
<body>
<div id="root"></div>
</body>
</html>
+2 -1
View File
@@ -3,6 +3,7 @@ import dayjs from 'dayjs';
import {
Button, Card, DatePicker, Divider, Form, Input, InputNumber, message, Modal, Popconfirm, Progress, Select, Space, Switch, Table, Tag, Typography,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import {
PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, EyeOutlined, KeyOutlined, CopyOutlined,
} from '@ant-design/icons';
@@ -299,7 +300,7 @@ const AdminApiKeys: React.FC = () => {
return cycle ? map[cycle] || cycle : '无限';
};
const columns = [
const columns: ColumnsType<ApiKey> = [
{ title: '公司', dataIndex: 'companyName', width: 120, ellipsis: true },
{
title: 'Key 前缀',
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import {
Button, Card, Form, InputNumber, message, Modal, Popconfirm, Select, Space, Table, Tag, Typography,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import {
PlusOutlined, EditOutlined, DeleteOutlined, DollarOutlined,
} from '@ant-design/icons';
@@ -69,9 +70,9 @@ const AdminApiModelPricings: React.FC = () => {
const filteredEngines = engines.filter(e => e.genType === genType);
const selectedEngine = engines.find(e => e.id === selectedEngineId);
const resolutions = genType === 'video'
const resolutions: string[] = genType === 'video'
? (selectedEngine?.supportedResolutions?.length ? selectedEngine.supportedResolutions : DEFAULT_VIDEO_RESOLUTIONS)
: (selectedEngine?.supportedSizes?.length ? selectedEngine.supportedSizes : DEFAULT_IMAGE_SIZES);
: (selectedEngine?.supportedSizes?.length ? Object.keys(selectedEngine.supportedSizes) : DEFAULT_IMAGE_SIZES);
const openEdit = (pricing: ApiModelPricing | null = null) => {
if (pricing) {
@@ -146,7 +147,7 @@ const AdminApiModelPricings: React.FC = () => {
}
};
const columns = [
const columns: ColumnsType<ApiModelPricing> = [
{
title: '类型',
dataIndex: 'genType',
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import {
Button, Card, DatePicker, Input, Select, Space, Table, Tag, Typography,
Button, Card, DatePicker, Input, message, Select, Space, Table, Tag, Typography,
} from 'antd';
import {
TableOutlined, ReloadOutlined,
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/app.tsx","./src/env.d.ts","./src/main.tsx","./src/api/client.ts","./src/api/crypto.ts","./src/api/index.ts","./src/components/preresultdisplay.tsx","./src/components/generation/generationtaskresourcegrid.tsx","./src/pages/adminauthoriz.tsx","./src/pages/adminconsume.tsx","./src/pages/admincontactrequests.tsx","./src/pages/admincreditratios.tsx","./src/pages/admincreditrecords.tsx","./src/pages/admindashboard.tsx","./src/pages/admingenerationairecords.tsx","./src/pages/admingenerationrecords.tsx","./src/pages/adminhomematerials.tsx","./src/pages/adminhotopeningreplicationdetail.tsx","./src/pages/adminhotopeningreplications.tsx","./src/pages/adminimageengines.tsx","./src/pages/adminindustries.tsx","./src/pages/adminlayout.tsx","./src/pages/adminloginpage.tsx","./src/pages/adminmateriallist.tsx","./src/pages/adminmenuconfig.tsx","./src/pages/adminmodels.tsx","./src/pages/adminnotificationmanager.tsx","./src/pages/adminoauthlist.tsx","./src/pages/adminoauthapplist.tsx","./src/pages/adminoperationlogs.tsx","./src/pages/adminpaymentconfig.tsx","./src/pages/adminpaymentstats.tsx","./src/pages/adminplatform.tsx","./src/pages/adminpretesttemplates.tsx","./src/pages/adminprivateportraitprojects.tsx","./src/pages/adminrechargepackages.tsx","./src/pages/adminreplicationprojectdetail.tsx","./src/pages/adminsettings.tsx","./src/pages/adminshotreplications.tsx","./src/pages/adminshottasksetdetail.tsx","./src/pages/adminteams.tsx","./src/pages/adminusers.tsx","./src/pages/adminvideoengines.tsx","./src/pages/adminvideopromptschemaconfig.tsx","./src/pages/adminvideoupscale.tsx","./src/pages/adminreplication/components/jsoncollapse.tsx","./src/pages/adminreplication/components/mediapreview.tsx","./src/pages/adminreplication/components/statustag.tsx","./src/pages/adminreplication/components/videopromptschemaviewer.tsx","./src/pages/homematerials/homematerialassettable.tsx","./src/pages/homematerials/homematerialcategorypanel.tsx","./src/pages/homematerials/homematerialuploadmodal.tsx","./src/pages/homematerials/mediareferenceseditor.tsx","./src/pages/homematerials/watermarkeditor.tsx","./src/pages/homematerials/watermarklibrarymodal.tsx","./src/pages/homematerials/watermarkpreview.tsx","./src/store/index.ts","./src/types/index.ts","./src/types/xlsx-js-style.d.ts","./src/utils/clipboard.ts","./src/utils/excelexport.ts","./src/utils/formatdate.ts","./src/utils/generationtaskstatus.ts","./src/utils/resourceurl.ts","./src/utils/shotreplicatestatus.ts","./src/utils/videopromptschema.ts"],"version":"6.0.3"}
{"root":["./src/app.tsx","./src/env.d.ts","./src/main.tsx","./src/api/client.ts","./src/api/crypto.ts","./src/api/index.ts","./src/components/preresultdisplay.tsx","./src/components/generation/generationtaskresourcegrid.tsx","./src/pages/adminapikeys.tsx","./src/pages/adminapimodelpricings.tsx","./src/pages/adminapiusage.tsx","./src/pages/adminauthoriz.tsx","./src/pages/adminconsume.tsx","./src/pages/admincontactrequests.tsx","./src/pages/admincreditratios.tsx","./src/pages/admincreditrecords.tsx","./src/pages/admindashboard.tsx","./src/pages/admingenerationairecords.tsx","./src/pages/admingenerationrecords.tsx","./src/pages/adminhomematerials.tsx","./src/pages/adminhotopeningreplicationdetail.tsx","./src/pages/adminhotopeningreplications.tsx","./src/pages/adminimageengines.tsx","./src/pages/adminindustries.tsx","./src/pages/adminlayout.tsx","./src/pages/adminloginpage.tsx","./src/pages/adminmateriallist.tsx","./src/pages/adminmenuconfig.tsx","./src/pages/adminmodels.tsx","./src/pages/adminnotificationmanager.tsx","./src/pages/adminoauthlist.tsx","./src/pages/adminoauthapplist.tsx","./src/pages/adminoperationlogs.tsx","./src/pages/adminpaymentconfig.tsx","./src/pages/adminpaymentstats.tsx","./src/pages/adminplatform.tsx","./src/pages/adminpretesttemplates.tsx","./src/pages/adminprivateportraitprojects.tsx","./src/pages/adminrechargepackages.tsx","./src/pages/adminreplicationprojectdetail.tsx","./src/pages/adminsettings.tsx","./src/pages/adminshotreplications.tsx","./src/pages/adminshottasksetdetail.tsx","./src/pages/adminteams.tsx","./src/pages/adminusers.tsx","./src/pages/adminvideoengines.tsx","./src/pages/adminvideopromptschemaconfig.tsx","./src/pages/adminvideoupscale.tsx","./src/pages/adminreplication/components/jsoncollapse.tsx","./src/pages/adminreplication/components/mediapreview.tsx","./src/pages/adminreplication/components/statustag.tsx","./src/pages/adminreplication/components/videopromptschemaviewer.tsx","./src/pages/homematerials/homematerialassettable.tsx","./src/pages/homematerials/homematerialcategorypanel.tsx","./src/pages/homematerials/homematerialuploadmodal.tsx","./src/pages/homematerials/mediareferenceseditor.tsx","./src/pages/homematerials/watermarkeditor.tsx","./src/pages/homematerials/watermarklibrarymodal.tsx","./src/pages/homematerials/watermarkpreview.tsx","./src/store/index.ts","./src/types/index.ts","./src/types/xlsx-js-style.d.ts","./src/utils/clipboard.ts","./src/utils/excelexport.ts","./src/utils/formatdate.ts","./src/utils/generationtaskstatus.ts","./src/utils/resourceurl.ts","./src/utils/shotreplicatestatus.ts","./src/utils/videopromptschema.ts"],"version":"6.0.3"}
@@ -0,0 +1,910 @@
"""上线整合迁移文件
将 20da1d353914 之后的所有版本迁移整合为一个文件,包含:
1. 创建 api_keys, api_generation_tasks, api_usage_logs, api_key_upscale_configs, api_upscale_links 表
2. 创建 api_model_pricings 表
3. 添加 api_key_encrypted 字段到 api_keys
4. 添加 model_name 字段到 api_generation_tasks
5. 添加 api_generation_task_id 字段到 video_upscale_tasks
6. 扩展 api_usage_logs 字段(price_action, resolution, duration 等)
7. 添加 local_media_json 字段到 api_generation_tasks
8. 添加所有表的表注释和字段注释(COMMENT ON TABLE/COLUMN
9. 创建 vp_v3_api_key_quotas, vp_v3_projects, vp_v3_assets 表
Revision ID: 2026080602
Revises: 20da1d353914
Create Date: 2026-08-06 00:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = '2026080602'
down_revision: Union[str, None] = '20da1d353914'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def _comment_table(table_name: str, comment: str) -> None:
op.execute(f"COMMENT ON TABLE {table_name} IS '{comment}'")
def _comment_column(table_name: str, column_name: str, comment: str) -> None:
escaped = comment.replace("'", "''")
op.execute(f"COMMENT ON COLUMN {table_name}.{column_name} IS '{escaped}'")
def upgrade() -> None:
# ============================================================
# 1. 创建 api_keys 表
# ============================================================
op.create_table(
'api_keys',
sa.Column('id', sa.String(32), primary_key=True),
sa.Column('company_name', sa.String(128), nullable=False),
sa.Column('api_key_hash', sa.String(64), nullable=False, unique=True),
sa.Column('api_key_prefix', sa.String(16), nullable=False),
sa.Column('description', sa.Text, nullable=True),
sa.Column('callable_models', sa.Text, nullable=False, server_default='[]'),
sa.Column('quota_limit', sa.Float, nullable=True),
sa.Column('quota_cycle', sa.String(16), nullable=True),
sa.Column('quota_used', sa.Float, nullable=False, server_default='0.0'),
sa.Column('valid_from', sa.DateTime(timezone=True), nullable=True),
sa.Column('valid_until', sa.DateTime(timezone=True), nullable=True),
sa.Column('max_concurrent_video_tasks', sa.Integer, nullable=True),
sa.Column('is_active', sa.Boolean, nullable=False, server_default='true'),
sa.Column('last_used_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
)
op.create_index('idx_api_key_hash', 'api_keys', ['api_key_hash'], unique=True)
op.create_index('idx_api_keys_active', 'api_keys', ['is_active'])
op.create_index('idx_api_keys_company', 'api_keys', ['company_name'])
# ============================================================
# 2. 创建 api_generation_tasks 表
# ============================================================
op.create_table(
'api_generation_tasks',
sa.Column('id', sa.String(32), primary_key=True),
sa.Column('api_key_id', sa.String(32), sa.ForeignKey('api_keys.id', ondelete='CASCADE'), nullable=False),
sa.Column('external_idempotency_key', sa.String(64), nullable=True),
sa.Column('original_prompt', sa.Text, nullable=False),
sa.Column('optimized_prompt', sa.Text, nullable=True),
sa.Column('gen_type', sa.String(16), nullable=False, default='video'),
sa.Column('duration', sa.Integer, nullable=True),
sa.Column('aspect_ratio', sa.String(8), nullable=True),
sa.Column('resolution', sa.String(8), nullable=True),
sa.Column('provider_generation_resolution', sa.String(16), nullable=True),
sa.Column('image_size', sa.String(16), nullable=True),
sa.Column('image_proportion', sa.String(8), nullable=True),
sa.Column('image_px', sa.String(16), nullable=True),
sa.Column('generation_count', sa.Integer, nullable=False, default=1, server_default='1'),
sa.Column('engine_id', sa.String(32), nullable=True),
sa.Column('media_references', sa.Text, nullable=True),
sa.Column('engine_snapshot_json', sa.Text, nullable=True),
sa.Column('request_params_json', sa.Text, nullable=True),
sa.Column('status', sa.String(32), nullable=False, default='pending'),
sa.Column('pipeline_stage', sa.String(32), nullable=True),
sa.Column('generation_attempt_no', sa.Integer, nullable=False, default=1, server_default='1'),
sa.Column('resource_generation_started_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deadline_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('provider_task_id', sa.String(128), nullable=True),
sa.Column('remote_result_url', sa.Text, nullable=True),
sa.Column('provider_response_json', sa.Text, nullable=True),
sa.Column('image_url', sa.String(512), nullable=True),
sa.Column('video_url', sa.String(512), nullable=True),
sa.Column('video_cover_url', sa.String(512), nullable=True),
sa.Column('video_upscale_enabled_snapshot', sa.Boolean, nullable=False, default=False, server_default='false'),
sa.Column('video_upscale_snapshot_json', sa.Text, nullable=True),
sa.Column('credits_cost', sa.Float, nullable=False, default=0.0, server_default='0.0'),
sa.Column('video_tokens_used', sa.Integer, nullable=False, default=0, server_default='0'),
sa.Column('image_tokens_used', sa.Integer, nullable=False, default=0, server_default='0'),
sa.Column('error_message', sa.Text, nullable=True),
sa.Column('generated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('next_poll_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('poll_interval_seconds', sa.Integer, nullable=False, default=30, server_default='30'),
sa.Column('poll_count', sa.Integer, nullable=False, default=0, server_default='0'),
sa.Column('last_poll_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('provider_create_claim_token', sa.String(64), nullable=True),
sa.Column('provider_create_lease_until', sa.DateTime(timezone=True), nullable=True),
sa.Column('provider_create_started_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('poll_started_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('poll_claim_token', sa.String(64), nullable=True),
sa.Column('poll_lease_until', sa.DateTime(timezone=True), nullable=True),
sa.Column('poll_error_count', sa.Integer, nullable=False, default=0, server_default='0'),
sa.Column('download_celery_task_id', sa.String(160), nullable=True),
sa.Column('download_enqueued_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('download_started_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('download_claim_token', sa.String(64), nullable=True),
sa.Column('download_lease_until', sa.DateTime(timezone=True), nullable=True),
sa.Column('download_next_retry_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('download_attempt_count', sa.Integer, nullable=False, default=0, server_default='0'),
sa.Column('download_last_error', sa.Text, nullable=True),
sa.Column('download_storage_date_dir', sa.String(16), nullable=True),
sa.Column('local_path', sa.Text, nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
)
op.create_index('idx_api_generation_tasks_api_key', 'api_generation_tasks', ['api_key_id'])
op.create_index('idx_api_generation_tasks_status', 'api_generation_tasks', ['status'])
op.create_index('idx_api_generation_tasks_provider_task_id', 'api_generation_tasks', ['provider_task_id'])
op.create_index('idx_api_generation_tasks_next_poll_at', 'api_generation_tasks', ['next_poll_at'])
op.create_index('idx_api_generation_tasks_api_key_created', 'api_generation_tasks', ['api_key_id', 'created_at'])
op.create_index(
'uq_api_generation_tasks_key_idempotency',
'api_generation_tasks',
['api_key_id', 'external_idempotency_key'],
unique=True,
postgresql_where=sa.text("deleted_at IS NULL AND external_idempotency_key IS NOT NULL"),
)
# ============================================================
# 3. 创建 api_usage_logs 表
# ============================================================
op.create_table(
'api_usage_logs',
sa.Column('id', sa.String(32), primary_key=True),
sa.Column('api_key_id', sa.String(32), sa.ForeignKey('api_keys.id', ondelete='CASCADE'), nullable=False),
sa.Column('api_generation_task_id', sa.String(32), sa.ForeignKey('api_generation_tasks.id', ondelete='SET NULL'), nullable=True),
sa.Column('request_type', sa.String(32), nullable=False),
sa.Column('model_name', sa.String(128), nullable=False),
sa.Column('gen_type', sa.String(16), nullable=False),
sa.Column('credits_cost', sa.Float, nullable=False, default=0.0, server_default='0.0'),
sa.Column('tokens_used', sa.Integer, nullable=False, default=0, server_default='0'),
sa.Column('request_duration_ms', sa.Integer, nullable=False, default=0, server_default='0'),
sa.Column('status', sa.String(32), nullable=False),
sa.Column('error_message', sa.Text, nullable=True),
sa.Column('error_code', sa.String(64), nullable=True),
sa.Column('request_payload_json', sa.Text, nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
)
op.create_index('idx_api_usage_logs_api_key', 'api_usage_logs', ['api_key_id'])
op.create_index('idx_api_usage_logs_task_id', 'api_usage_logs', ['api_generation_task_id'])
op.create_index('idx_api_usage_logs_api_key_created', 'api_usage_logs', ['api_key_id', 'created_at'])
# ============================================================
# 4. 创建 api_key_upscale_configs 表
# ============================================================
op.create_table(
'api_key_upscale_configs',
sa.Column('id', sa.String(32), primary_key=True),
sa.Column('api_key_id', sa.String(32), sa.ForeignKey('api_keys.id', ondelete='CASCADE'), nullable=False, unique=True),
sa.Column('enabled', sa.Boolean, nullable=False, default=False, server_default='false'),
sa.Column('delete_source_after_success', sa.Boolean, nullable=False, default=True, server_default='true'),
sa.Column('rules_json', sa.Text, nullable=False, server_default='[]'),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
)
# ============================================================
# 5. 创建 api_upscale_links 表
# ============================================================
op.create_table(
'api_upscale_links',
sa.Column('id', sa.String(32), primary_key=True),
sa.Column('api_generation_task_id', sa.String(32), sa.ForeignKey('api_generation_tasks.id', ondelete='CASCADE'), nullable=False),
sa.Column('video_upscale_task_id', sa.String(32), sa.ForeignKey('video_upscale_tasks.id', ondelete='CASCADE'), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
)
op.create_index('idx_api_upscale_links_api_task', 'api_upscale_links', ['api_generation_task_id'])
op.create_index('idx_api_upscale_links_video_task', 'api_upscale_links', ['video_upscale_task_id'])
# ============================================================
# 6. 创建 api_model_pricings 表
# ============================================================
op.create_table(
'api_model_pricings',
sa.Column('id', sa.String(32), primary_key=True),
sa.Column('model_config_id', sa.String(32), nullable=False, index=True),
sa.Column('gen_type', sa.String(16), nullable=False, default='video', index=True),
sa.Column('resolution', sa.String(16), nullable=False, index=True),
sa.Column('price_ratio', sa.Float, nullable=False, default=1.0),
sa.Column('base_price', sa.Float, nullable=False, default=0.0),
sa.Column('per_second_price', sa.Float, nullable=False, default=0.0),
sa.Column('input_video_ratio', sa.Float, nullable=False, default=1.0),
sa.Column('input_video_base_price', sa.Float, nullable=False, default=0.0),
sa.Column('input_video_per_second_price', sa.Float, nullable=False, default=0.0),
sa.Column('input_image_ratio', sa.Float, nullable=False, default=1.0),
sa.Column('input_image_base_price', sa.Float, nullable=False, default=0.0),
sa.Column('input_image_per_image_price', sa.Float, nullable=False, default=0.0),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now(), onupdate=sa.func.now()),
)
op.create_index(
'ix_api_model_pricings_gen_type_engine_resolution',
'api_model_pricings',
['gen_type', 'model_config_id', 'resolution'],
)
op.create_index(
'ix_api_model_pricings_gen_type_resolution',
'api_model_pricings',
['gen_type', 'resolution'],
)
# ============================================================
# 7. 给 api_keys 添加 api_key_encrypted 字段
# ============================================================
op.add_column(
'api_keys',
sa.Column('api_key_encrypted', sa.Text, nullable=True, comment='AES-256-GCM 加密的完整 API Key'),
)
op.execute("UPDATE api_keys SET api_key_encrypted = '' WHERE api_key_encrypted IS NULL")
op.alter_column('api_keys', 'api_key_encrypted', nullable=False)
# ============================================================
# 8. 给 api_generation_tasks 添加 model_name 字段
# ============================================================
op.add_column(
'api_generation_tasks',
sa.Column('model_name', sa.String(128), nullable=False, server_default='', comment="模型名称,如 doubao-seedance-2-0-260128"),
)
# ============================================================
# 9. 给 video_upscale_tasks 添加 api_generation_task_id 字段
# ============================================================
op.add_column(
'video_upscale_tasks',
sa.Column('api_generation_task_id', sa.String(32), nullable=True, comment="API v3 任务ID,关联 api_generation_tasks.id"),
)
op.create_index(
'idx_video_upscale_tasks_api_generation_task_id',
'video_upscale_tasks',
['api_generation_task_id'],
)
op.create_foreign_key(
'fk_video_upscale_tasks_api_generation_task_id',
'video_upscale_tasks',
'api_generation_tasks',
['api_generation_task_id'],
['id'],
ondelete='CASCADE',
)
op.execute("ALTER TABLE video_upscale_tasks DROP CONSTRAINT IF EXISTS ck_video_upscale_tasks_exactly_one_owner")
op.execute("""
ALTER TABLE video_upscale_tasks
ADD CONSTRAINT ck_video_upscale_tasks_exactly_one_owner
CHECK (
(chat_generation_task_id IS NOT NULL)::int +
(generation_record_id IS NOT NULL)::int +
(api_generation_task_id IS NOT NULL)::int = 1
)
""")
# ============================================================
# 10. 扩展 api_usage_logs 字段
# ============================================================
op.add_column('api_usage_logs', sa.Column('price_action', sa.String(16), nullable=False, server_default='deduct', comment='deduct=扣除, refund=退回'))
op.add_column('api_usage_logs', sa.Column('resolution', sa.String(16), nullable=True, comment="分辨率: 480p/720p/1080p/2K/4K"))
op.add_column('api_usage_logs', sa.Column('duration', sa.Integer(), nullable=True, comment="视频时长(秒)"))
op.add_column('api_usage_logs', sa.Column('refund_amount', sa.Float(), nullable=False, server_default='0.0', comment='退回金额'))
op.add_column('api_usage_logs', sa.Column('quota_before', sa.Float(), nullable=True, comment='操作前配额余额'))
op.add_column('api_usage_logs', sa.Column('quota_after', sa.Float(), nullable=True, comment='操作后配额余额'))
op.add_column('api_usage_logs', sa.Column('price_detail_json', sa.Text(), nullable=True, comment='价格计算明细JSON'))
op.create_index('idx_api_usage_logs_action', 'api_usage_logs', ['price_action'])
# ============================================================
# 11. 给 api_generation_tasks 添加 local_media_json 字段
# ============================================================
op.add_column(
'api_generation_tasks',
sa.Column('local_media_json', sa.Text, nullable=True, comment='下载到本地的媒体文件路径JSON'),
)
# ============================================================
# 12. 创建 vp_v3_api_key_quotas 表
# ============================================================
op.create_table(
'vp_v3_api_key_quotas',
sa.Column('id', sa.String(length=32), nullable=False, comment='主键ID'),
sa.Column('api_key_id', sa.String(length=32), nullable=False,
comment='所属 API Key,唯一:一个 API Key 只有一份虚拟素材配额'),
sa.Column('project_limit', sa.Integer(), nullable=False, server_default=sa.text('0'),
comment='虚拟项目上限,默认 0 不可创建'),
sa.Column('asset_limit', sa.Integer(), nullable=False, server_default=sa.text('0'),
comment='虚拟素材总数上限(图片+视频),默认 0 不可上传'),
sa.Column('storage_mb_limit', sa.Integer(), nullable=False, server_default=sa.text('0'),
comment='上传存储上限 MB,默认 0 不可上传文件'),
sa.Column('project_used', sa.Integer(), nullable=False, server_default=sa.text('0'),
comment='已创建项目数(未删除)'),
sa.Column('asset_used', sa.Integer(), nullable=False, server_default=sa.text('0'),
comment='已上传素材数(未删除,图片+视频)'),
sa.Column('storage_mb_used', sa.Integer(), nullable=False, server_default=sa.text('0'),
comment='已占用存储 MB(未删除文件大小合计,1MB=1024*1024'),
sa.Column('remark', sa.Text(), nullable=True, comment='后台备注'),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(),
comment='创建时间'),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False,
server_default=sa.func.now(),
onupdate=sa.func.now(),
comment='最后更新时间'),
sa.ForeignKeyConstraint(['api_key_id'], ['api_keys.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
)
op.create_unique_constraint('uq_vp_v3_api_key_quotas_key_id', 'vp_v3_api_key_quotas', ['api_key_id'])
op.create_index('idx_vp_v3_api_key_quotas_api_key_id', 'vp_v3_api_key_quotas', ['api_key_id'])
# ============================================================
# 13. 创建 vp_v3_projects 表
# ============================================================
op.create_table(
'vp_v3_projects',
sa.Column('id', sa.String(length=32), nullable=False, comment='项目ID'),
sa.Column('api_key_id', sa.String(length=32), nullable=False,
comment='所属 API KeyV3 调用方)'),
sa.Column('name', sa.String(length=128), nullable=False, comment='项目展示名称'),
sa.Column('name_slug', sa.String(length=128), nullable=False, comment='名称安全 slug(构建远端 GroupName 用)'),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('remote_project_name', sa.String(length=256), nullable=False,
comment='火山 ProjectName(快照)'),
sa.Column('remote_group_id', sa.String(length=128), nullable=False,
comment='火山 AssetGroup Id'),
sa.Column('remote_group_name', sa.String(length=256), nullable=True,
comment='火山 AssetGroup Name 快照'),
sa.Column('status', sa.String(length=32), nullable=False, server_default=sa.text("'active'"),
index=True,
comment='项目状态:active/creating_remote_group/create_group_failed/deleting'),
sa.Column('asset_count', sa.Integer(), nullable=False, server_default=sa.text('0')),
sa.Column('active_asset_count', sa.Integer(), nullable=False, server_default=sa.text('0')),
sa.Column('image_asset_count', sa.Integer(), nullable=False, server_default=sa.text('0')),
sa.Column('video_asset_count', sa.Integer(), nullable=False, server_default=sa.text('0')),
sa.Column('active_image_asset_count', sa.Integer(), nullable=False, server_default=sa.text('0')),
sa.Column('active_video_asset_count', sa.Integer(), nullable=False, server_default=sa.text('0')),
sa.Column('storage_mb_used', sa.Integer(), nullable=False, server_default=sa.text('0'),
comment='项目占用存储 MB(未删除素材文件大小合计)'),
sa.Column('last_used_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('remote_delete_status', sa.String(length=32), nullable=False, server_default=sa.text("'none'"),
index=True, comment='远端删除状态:none/pending/processing/deleted/failed'),
sa.Column('remote_deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('remote_delete_error', sa.Text(), nullable=True),
sa.Column('error_message', sa.Text(), nullable=True, comment='创建失败等错误信息'),
sa.Column('raw_response_json', sa.Text(), nullable=True, comment='火山原始响应'),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True, comment='删除时间(NULL=未删除)'),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(),
comment='创建时间'),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False,
server_default=sa.func.now(),
onupdate=sa.func.now(),
comment='最后更新时间'),
sa.ForeignKeyConstraint(['api_key_id'], ['api_keys.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
)
op.create_index('idx_vp_v3_projects_key_status_created', 'vp_v3_projects',
['api_key_id', 'status', 'created_at'])
op.create_index('idx_vp_v3_projects_remote_project_name', 'vp_v3_projects', ['remote_project_name'])
op.create_index('idx_vp_v3_projects_remote_group_id', 'vp_v3_projects', ['remote_group_id'])
op.execute(
"CREATE INDEX idx_vp_v3_projects_key_deleted ON vp_v3_projects (api_key_id, deleted_at)"
" WHERE deleted_at IS NULL;"
)
# ============================================================
# 14. 创建 vp_v3_assets 表
# ============================================================
op.create_table(
'vp_v3_assets',
sa.Column('id', sa.String(length=32), nullable=False, comment='素材ID'),
sa.Column('api_key_id', sa.String(length=32), nullable=False,
comment='所属 API KeyV3 调用方)'),
sa.Column('project_id', sa.String(length=32), nullable=False, comment='所属项目ID'),
sa.Column('remote_project_name', sa.String(length=256), nullable=False,
comment='火山 ProjectName'),
sa.Column('remote_group_id', sa.String(length=128), nullable=False,
comment='火山 AssetGroup Id'),
sa.Column('remote_asset_id', sa.String(length=128), nullable=True, comment='火山素材 Id'),
sa.Column('asset_type', sa.String(length=16), nullable=False, server_default=sa.text("'Image'"),
comment='素材类型:Image=图片 / Video=视频', index=True),
sa.Column('name', sa.String(length=128), nullable=True, comment='素材展示名称', index=True),
sa.Column('source_url', sa.Text(), nullable=False, comment='本地上传后的访问 URL'),
sa.Column('preview_url', sa.Text(), nullable=True, comment='给前端预览/显示用的 URL'),
sa.Column('remote_url', sa.Text(), nullable=True, comment='火山返回的资源访问 URL(可能带签名)'),
sa.Column('remote_url_expired_at', sa.DateTime(timezone=True), nullable=True,
comment='remote_url 过期时间'),
sa.Column('upload_resource_id', sa.String(length=32), nullable=True, index=True,
comment='本地上传 resource_id,供容量释放用'),
sa.Column('video_duration', sa.Float(), nullable=True, comment='视频时长,秒'),
sa.Column('video_cover_url', sa.Text(), nullable=True, comment='视频封面预览'),
sa.Column('file_size_bytes', sa.Integer(), nullable=True, comment='素材文件大小,字节'),
sa.Column('mime_type', sa.String(length=128), nullable=True),
sa.Column('status', sa.String(length=32), nullable=False, server_default=sa.text("'creating'"),
index=True,
comment='素材状态:creating/审核中 active/可用 failed/失败 deleting/删除中'),
sa.Column('moderation_json', sa.Text(), nullable=True, comment='火山审核结果 JSON'),
sa.Column('error_message', sa.Text(), nullable=True, comment='失败原因'),
sa.Column('raw_response_json', sa.Text(), nullable=True, comment='火山原始响应 JSON'),
sa.Column('last_poll_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('next_poll_at', sa.DateTime(timezone=True), nullable=True, index=True,
comment='下次轮询时间(创建中状态自动轮询)'),
sa.Column('poll_count', sa.Integer(), nullable=False, server_default=sa.text('0')),
sa.Column('remote_delete_status', sa.String(length=32), nullable=False, server_default=sa.text("'none'"),
index=True, comment='远端删除状态:none/pending/processing/deleted/failed'),
sa.Column('remote_deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('remote_delete_error', sa.Text(), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True, comment='删除时间(NULL=未删除)'),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(),
comment='创建时间'),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False,
server_default=sa.func.now(),
onupdate=sa.func.now(),
comment='最后更新时间'),
sa.ForeignKeyConstraint(['api_key_id'], ['api_keys.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['project_id'], ['vp_v3_projects.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
)
op.create_unique_constraint('uq_vp_v3_assets_remote_asset_id', 'vp_v3_assets', ['remote_asset_id'])
op.create_index('idx_vp_v3_assets_key_status_created', 'vp_v3_assets',
['api_key_id', 'status', 'created_at'])
op.create_index('idx_vp_v3_assets_project_status_created', 'vp_v3_assets',
['project_id', 'status', 'created_at'])
op.create_index('idx_vp_v3_assets_asset_type', 'vp_v3_assets', ['asset_type'])
op.create_index('idx_vp_v3_assets_remote_delete_status', 'vp_v3_assets', ['remote_delete_status'])
op.execute(
"CREATE INDEX idx_vp_v3_assets_next_poll_status ON vp_v3_assets (next_poll_at, status)"
" WHERE deleted_at IS NULL AND next_poll_at IS NOT NULL;"
)
# ============================================================
# 15. 添加所有表的表注释和字段注释
# ============================================================
# --- users 表 ---
_comment_table("users", "用户表")
_comment_column("users", "id", "主键ID")
_comment_column("users", "username", "用户名,唯一")
_comment_column("users", "email", "邮箱,唯一")
_comment_column("users", "phone", "手机号,唯一")
_comment_column("users", "hashed_password", "加密后的密码")
_comment_column("users", "avatar", "头像URL")
_comment_column("users", "credits", "账户积分余额")
_comment_column("users", "is_active", "是否启用,True启用")
_comment_column("users", "is_admin", "是否管理员,True管理员")
_comment_column("users", "user_type", "用户类型:frontend前台用户,admin后台管理员")
_comment_column("users", "frontend_user_kind", "前台用户类型:internal内部用户,external外部用户")
_comment_column("users", "team_id", "当前归属团队ID,仅前台用户有意义")
_comment_column("users", "last_login_at", "最后登录时间")
_comment_column("users", "password_set_at", "密码设置时间,NULL表示未设置密码")
_comment_column("users", "allowed_menus", "允许访问的菜单列表(JSON),NULL表示继承默认")
_comment_column("users", "private_portrait_asset_limit", "私域人像素材总量上限,0表示关闭模块")
_comment_column("users", "created_at", "创建时间")
_comment_column("users", "updated_at", "更新时间")
# --- projects 表 ---
_comment_table("projects", "项目表")
_comment_column("projects", "id", "主键ID")
_comment_column("projects", "user_id", "所属用户ID")
_comment_column("projects", "name", "项目名称")
_comment_column("projects", "industry", "所属行业")
_comment_column("projects", "created_at", "创建时间")
_comment_column("projects", "updated_at", "更新时间")
_comment_column("projects", "deleted_at", "软删除时间,NULL表示未删除")
# --- credit_ratios 表 ---
_comment_table("credit_ratios", "积分计费规则表")
_comment_column("credit_ratios", "id", "主键ID")
_comment_column("credit_ratios", "model_config_id", "引擎ID:图片对应image_engines.id,视频对应video_engines.id")
_comment_column("credit_ratios", "gen_type", "生成类型:image图片,video视频")
_comment_column("credit_ratios", "resolution", "分辨率档位:图片(2K/4K) / 视频(480p/720p/1080p)")
_comment_column("credit_ratios", "ratio", "生成倍率,最终积分 = (基础积分+单位积分×时长/张数) × 倍率")
_comment_column("credit_ratios", "base_credits", "生成基础积分")
_comment_column("credit_ratios", "per_second_credits", "视频每秒积分 / 图片每张积分")
_comment_column("credit_ratios", "input_video_ratio", "传入视频积分倍率")
_comment_column("credit_ratios", "input_video_base_credits", "传入视频基础积分")
_comment_column("credit_ratios", "input_video_per_second_credits", "传入视频每秒积分")
_comment_column("credit_ratios", "input_image_ratio", "传入图片积分倍率")
_comment_column("credit_ratios", "input_image_base_credits", "传入图片基础积分")
_comment_column("credit_ratios", "input_image_per_image_credits", "传入图片每张积分")
_comment_column("credit_ratios", "created_at", "创建时间")
_comment_column("credit_ratios", "updated_at", "更新时间")
# --- credit_records 表 ---
_comment_table("credit_records", "积分流水表")
_comment_column("credit_records", "id", "主键ID")
_comment_column("credit_records", "user_id", "所属用户ID")
_comment_column("credit_records", "type", "流水类型:charge扣费,recharge充值,refund退款,gift赠送")
_comment_column("credit_records", "amount", "流水金额,扣费为负数,充值/退款/赠送为正数")
_comment_column("credit_records", "balance_after", "流水后账户余额")
_comment_column("credit_records", "description", "流水描述")
_comment_column("credit_records", "related_id", "关联业务ID,如生成任务ID/订单ID")
_comment_column("credit_records", "biz_key", "业务幂等键,格式如 owner_type:owner_id:attempt_no:charge_kind:action")
_comment_column("credit_records", "refund_for_biz_key", "退款时,对应的扣费biz_key")
_comment_column("credit_records", "owner_type", "归属类型:chat_generation_task/ generation_record等")
_comment_column("credit_records", "owner_id", "归属业务记录ID")
_comment_column("credit_records", "attempt_no", "计费尝试次数,重试时递增")
_comment_column("credit_records", "charge_kind", "扣费大类:media媒体生成,prompt提示词等")
_comment_column("credit_records", "charge_action", "扣费动作:charge扣费,refund退款")
_comment_column("credit_records", "credit_subject", "计费科目:image/video/text")
_comment_column("credit_records", "media_type", "媒体类型:与credit_subject配合细分")
_comment_column("credit_records", "billing_scene", "计费场景:如chat_creation、project等")
_comment_column("credit_records", "source_module", "来源模块:generation_record/module_generation等")
_comment_column("credit_records", "source_project_id", "来源项目ID")
_comment_column("credit_records", "source_step_id", "来源步骤ID")
_comment_column("credit_records", "source_step_code", "来源步骤编码")
_comment_column("credit_records", "token_usage_id", "关联Token消耗记录ID")
_comment_column("credit_records", "input_tokens", "输入Token数量快照")
_comment_column("credit_records", "output_tokens", "输出Token数量快照")
_comment_column("credit_records", "total_tokens", "总Token数量快照")
_comment_column("credit_records", "engine_type", "引擎类型:image/video/text")
_comment_column("credit_records", "engine_id", "使用的引擎ID")
_comment_column("credit_records", "engine_name", "引擎名称快照")
_comment_column("credit_records", "engine_provider", "引擎供应商快照:ark/其他")
_comment_column("credit_records", "engine_model_name", "引擎模型名快照")
_comment_column("credit_records", "user_type_snapshot", "用户类型快照:frontend/admin")
_comment_column("credit_records", "frontend_user_kind_snapshot", "前台用户类型快照:internal/external")
_comment_column("credit_records", "team_id_snapshot", "团队ID快照,流水发生时的归属团队")
_comment_column("credit_records", "team_name_snapshot", "团队名称快照")
_comment_column("credit_records", "created_at", "创建时间")
_comment_column("credit_records", "updated_at", "更新时间")
# --- chat_generation_tasks 表 ---
_comment_table("chat_generation_tasks", "AI创作任务表(不绑定项目的聊天式生成)")
_comment_column("chat_generation_tasks", "id", "主键ID,顶层/子任务ID")
_comment_column("chat_generation_tasks", "user_id", "所属用户ID")
_comment_column("chat_generation_tasks", "original_prompt", "原始用户提示词")
_comment_column("chat_generation_tasks", "optimized_prompt", "优化后的提示词")
_comment_column("chat_generation_tasks", "gen_type", "生成类型:image图片,video视频")
_comment_column("chat_generation_tasks", "duration", "视频时长(秒)")
_comment_column("chat_generation_tasks", "aspect_ratio", "视频比例:16:9/9:16等")
_comment_column("chat_generation_tasks", "resolution", "用户选择的分辨率")
_comment_column("chat_generation_tasks", "provider_generation_resolution", "供应商实际生成分辨率")
_comment_column("chat_generation_tasks", "video_upscale_enabled_snapshot", "是否开启视频超分")
_comment_column("chat_generation_tasks", "video_upscale_snapshot_json", "视频超分参数快照JSON")
_comment_column("chat_generation_tasks", "image_size", "图片分辨率档位:2K/4K")
_comment_column("chat_generation_tasks", "image_proportion", "图片比例:1:1/16:9等")
_comment_column("chat_generation_tasks", "image_px", "图片像素,如2048×2048")
_comment_column("chat_generation_tasks", "status", "任务状态:generating/success/failed等")
_comment_column("chat_generation_tasks", "pipeline_stage", "流水线阶段:prompt_optimized/resource_generated等")
_comment_column("chat_generation_tasks", "generation_mode", "生成模式:chatapi_async单份异步/chatapi_main多份主任务")
_comment_column("chat_generation_tasks", "parent_task_id", "父任务ID,多份生成时子任务关联主任务")
_comment_column("chat_generation_tasks", "generation_count", "生成份数,主任务表示总共多少份")
_comment_column("chat_generation_tasks", "generation_index", "第N份子任务,主任务为NULL")
_comment_column("chat_generation_tasks", "generation_attempt_no", "生成尝试次数,重试时递增")
_comment_column("chat_generation_tasks", "resource_generation_started_at", "资源生成开始时间")
_comment_column("chat_generation_tasks", "provider_create_claim_token", "供应商创建任务分布式租约token")
_comment_column("chat_generation_tasks", "provider_create_lease_until", "供应商创建租约过期时间")
_comment_column("chat_generation_tasks", "provider_create_started_at", "供应商创建任务开始时间")
_comment_column("chat_generation_tasks", "media_references", "参考素材JSON数组")
_comment_column("chat_generation_tasks", "provider_task_id", "供应商任务ID")
_comment_column("chat_generation_tasks", "seedance_task_id", "Seedance任务ID(兼容字段)")
_comment_column("chat_generation_tasks", "remote_result_url", "供应商返回的远程资源URL")
_comment_column("chat_generation_tasks", "image_url", "图片结果URL")
_comment_column("chat_generation_tasks", "video_url", "视频结果URL")
_comment_column("chat_generation_tasks", "video_cover_url", "视频封面URL")
_comment_column("chat_generation_tasks", "engine_id", "使用的引擎ID")
_comment_column("chat_generation_tasks", "engine_snapshot_json", "引擎参数快照JSON")
_comment_column("chat_generation_tasks", "provider_response_json", "供应商完整响应JSON")
_comment_column("chat_generation_tasks", "credits_cost", "媒体生成消耗的总积分")
_comment_column("chat_generation_tasks", "text_credits_cost", "提示词优化消耗积分")
_comment_column("chat_generation_tasks", "text_tokens_used", "提示词优化Token消耗")
_comment_column("chat_generation_tasks", "video_tokens_used", "视频生成Token消耗")
_comment_column("chat_generation_tasks", "image_tokens_used", "图片生成Token消耗")
_comment_column("chat_generation_tasks", "retry_count", "重试次数(兼容旧字段)")
_comment_column("chat_generation_tasks", "manual_retry_count", "用户手动重试次数")
_comment_column("chat_generation_tasks", "poll_error_count", "轮询错误次数")
_comment_column("chat_generation_tasks", "poll_count", "轮询总次数")
_comment_column("chat_generation_tasks", "last_poll_at", "最后一次轮询时间")
_comment_column("chat_generation_tasks", "poll_started_at", "本次轮询开始时间")
_comment_column("chat_generation_tasks", "next_poll_at", "下一次轮询触发时间")
_comment_column("chat_generation_tasks", "poll_interval_seconds", "轮询间隔秒数")
_comment_column("chat_generation_tasks", "poll_claim_token", "轮询分布式租约token")
_comment_column("chat_generation_tasks", "poll_lease_until", "轮询租约过期时间")
_comment_column("chat_generation_tasks", "deadline_at", "任务截止时间,超时自动失败")
_comment_column("chat_generation_tasks", "generated_at", "资源生成完成时间")
_comment_column("chat_generation_tasks", "error_message", "错误信息")
_comment_column("chat_generation_tasks", "idempotency_key", "幂等键,防重复创建")
_comment_column("chat_generation_tasks", "download_celery_task_id", "下载步骤Celery任务ID")
_comment_column("chat_generation_tasks", "download_enqueued_at", "下载入队时间")
_comment_column("chat_generation_tasks", "download_started_at", "下载开始时间")
_comment_column("chat_generation_tasks", "download_claim_token", "下载租约token")
_comment_column("chat_generation_tasks", "download_lease_until", "下载租约过期时间")
_comment_column("chat_generation_tasks", "download_next_retry_at", "下载下次重试时间")
_comment_column("chat_generation_tasks", "download_attempt_count", "下载重试次数")
_comment_column("chat_generation_tasks", "download_last_error", "下载最后一次错误信息")
_comment_column("chat_generation_tasks", "download_storage_date_dir", "下载存储日期目录")
_comment_column("chat_generation_tasks", "created_at", "创建时间")
_comment_column("chat_generation_tasks", "updated_at", "更新时间")
_comment_column("chat_generation_tasks", "deleted_at", "软删除时间,NULL表示未删除")
# --- generation_records 表 ---
_comment_table("generation_records", "项目生成记录表(绑定项目的旧版生成)")
_comment_column("generation_records", "id", "主键ID")
_comment_column("generation_records", "user_id", "所属用户ID")
_comment_column("generation_records", "project_id", "所属项目ID")
_comment_column("generation_records", "original_prompt", "原始提示词")
_comment_column("generation_records", "optimized_prompt", "优化后的提示词")
_comment_column("generation_records", "prompt_usage_snapshot_json", "提示词消耗快照JSON")
_comment_column("generation_records", "gen_type", "生成类型:image/video")
_comment_column("generation_records", "duration", "视频时长秒数")
_comment_column("generation_records", "aspect_ratio", "视频比例")
_comment_column("generation_records", "resolution", "分辨率档位")
_comment_column("generation_records", "provider_generation_resolution", "供应商实际分辨率")
_comment_column("generation_records", "video_upscale_enabled_snapshot", "是否开启视频超分")
_comment_column("generation_records", "video_upscale_snapshot_json", "视频超分快照JSON")
_comment_column("generation_records", "image_size", "图片分辨率档位")
_comment_column("generation_records", "image_proportion", "图片比例")
_comment_column("generation_records", "image_px", "图片像素尺寸")
_comment_column("generation_records", "status", "任务状态")
_comment_column("generation_records", "pipeline_stage", "流水线阶段")
_comment_column("generation_records", "video_url", "视频结果URL")
_comment_column("generation_records", "video_cover_url", "视频封面URL")
_comment_column("generation_records", "image_url", "图片结果URL")
_comment_column("generation_records", "media_references", "参考素材JSON数组")
_comment_column("generation_records", "include_media_references", "是否包含参考素材")
_comment_column("generation_records", "video_url_expires_at", "视频URL过期时间")
_comment_column("generation_records", "seedance_task_id", "Seedance任务ID")
_comment_column("generation_records", "credits_cost", "媒体生成消耗积分")
_comment_column("generation_records", "text_credits_cost", "提示词消耗积分")
_comment_column("generation_records", "text_tokens_used", "提示词Token数")
_comment_column("generation_records", "video_tokens_used", "视频Token数")
_comment_column("generation_records", "image_tokens_used", "图片Token数")
_comment_column("generation_records", "generated_at", "生成完成时间")
_comment_column("generation_records", "error_message", "错误信息")
_comment_column("generation_records", "idempotency_key", "幂等键")
_comment_column("generation_records", "generation_attempt_no", "生成尝试次数")
_comment_column("generation_records", "resource_generation_started_at", "资源生成开始时间")
_comment_column("generation_records", "deadline_at", "任务截止时间")
_comment_column("generation_records", "engine_id", "使用引擎ID")
_comment_column("generation_records", "engine_snapshot_json", "引擎参数快照JSON")
_comment_column("generation_records", "provider_response_json", "供应商响应JSON")
_comment_column("generation_records", "remote_result_url", "远程资源URL")
_comment_column("generation_records", "provider_create_claim_token", "供应商创建租约token")
_comment_column("generation_records", "provider_create_lease_until", "供应商创建租约过期")
_comment_column("generation_records", "provider_create_started_at", "供应商创建开始时间")
_comment_column("generation_records", "retry_count", "重试次数(兼容)")
_comment_column("generation_records", "manual_retry_count", "手动重试次数")
_comment_column("generation_records", "poll_error_count", "轮询错误次数")
_comment_column("generation_records", "poll_count", "轮询次数")
_comment_column("generation_records", "last_poll_at", "最后轮询时间")
_comment_column("generation_records", "poll_started_at", "轮询开始时间")
_comment_column("generation_records", "next_poll_at", "下次轮询时间")
_comment_column("generation_records", "poll_interval_seconds", "轮询间隔秒")
_comment_column("generation_records", "poll_claim_token", "轮询租约token")
_comment_column("generation_records", "poll_lease_until", "轮询租约过期")
_comment_column("generation_records", "download_celery_task_id", "下载Celery任务ID")
_comment_column("generation_records", "download_enqueued_at", "下载开始入队时间")
_comment_column("generation_records", "download_started_at", "下载开始时间")
_comment_column("generation_records", "download_claim_token", "下载租约token")
_comment_column("generation_records", "download_lease_until", "下载租约过期")
_comment_column("generation_records", "download_next_retry_at", "下载下次重试")
_comment_column("generation_records", "download_attempt_count", "下载重试次数")
_comment_column("generation_records", "download_last_error", "下载最后错误")
_comment_column("generation_records", "download_storage_date_dir", "下载存储日期目录")
_comment_column("generation_records", "created_at", "创建时间")
_comment_column("generation_records", "updated_at", "更新时间")
_comment_column("generation_records", "deleted_at", "软删除时间")
# --- generated_resources 表 ---
_comment_table("generated_resources", "生成资源账本表(统一记录所有生成的图片/视频)")
_comment_column("generated_resources", "id", "主键ID")
_comment_column("generated_resources", "user_id", "所属用户ID")
_comment_column("generated_resources", "resource_type", "资源类型:image/video")
_comment_column("generated_resources", "resource_url", "资源访问URL")
_comment_column("generated_resources", "remote_url", "供应商原始远程URL")
_comment_column("generated_resources", "storage_type", "存储类型:local本地/oss对象存储")
_comment_column("generated_resources", "storage_path", "存储路径")
_comment_column("generated_resources", "file_name", "文件名,平台素材名称")
_comment_column("generated_resources", "file_size_bytes", "文件大小(字节)")
_comment_column("generated_resources", "source_model", "来源模型:chat_generation_task/generation_record")
_comment_column("generated_resources", "source_model_module", "来源模块描述")
_comment_column("generated_resources", "source_id", "来源记录ID")
_comment_column("generated_resources", "engine_id", "使用引擎ID")
_comment_column("generated_resources", "engine_type", "引擎类型:image/video")
_comment_column("generated_resources", "provider", "供应商:ark/其他")
_comment_column("generated_resources", "model_name", "模型名称")
_comment_column("generated_resources", "generated_at", "资源生成完成时间")
_comment_column("generated_resources", "resource_month", "资源归属月份,按月统计")
_comment_column("generated_resources", "extra_json", "扩展字段JSON")
_comment_column("generated_resources", "created_at", "创建时间")
_comment_column("generated_resources", "updated_at", "更新时间")
_comment_column("generated_resources", "deleted_at", "软删除时间")
# --- upload_resources 表 ---
_comment_table("upload_resources", "用户上传资源账本表(用户上传/模块上传/切片文件)")
_comment_column("upload_resources", "id", "主键ID")
_comment_column("upload_resources", "user_id", "所属用户ID")
_comment_column("upload_resources", "module", "所属模块:conversation/generation_record等")
_comment_column("upload_resources", "resource_type", "资源类型:image/video/audio/file")
_comment_column("upload_resources", "resource_url", "资源访问URL")
_comment_column("upload_resources", "storage_path", "存储路径,唯一")
_comment_column("upload_resources", "file_name", "原始文件名")
_comment_column("upload_resources", "file_ext", "文件扩展名")
_comment_column("upload_resources", "mime_type", "MIME类型")
_comment_column("upload_resources", "file_size_bytes", "文件大小(字节)")
_comment_column("upload_resources", "duration_seconds", "音视频时长(秒)")
_comment_column("upload_resources", "duration_source", "时长来源:probe探测/用户设置")
_comment_column("upload_resources", "width", "图片/视频宽度(像素)")
_comment_column("upload_resources", "height", "图片/视频高度(像素)")
_comment_column("upload_resources", "source_model", "关联业务模型")
_comment_column("upload_resources", "source_id", "关联业务记录ID")
_comment_column("upload_resources", "source_module", "关联业务模块")
_comment_column("upload_resources", "bind_status", "绑定状态:pending待绑定/bound已绑定/unbound已解绑")
_comment_column("upload_resources", "delete_policy", "删除策略:user_deletable用户可删/keep_forever永久保留")
_comment_column("upload_resources", "created_by", "创建来源:api用户上传/worker系统生成")
_comment_column("upload_resources", "metadata_json", "媒体元数据JSON")
_comment_column("upload_resources", "capacity_released_at", "容量统计中已释放时间")
_comment_column("upload_resources", "physical_deleted_at", "物理文件删除时间")
_comment_column("upload_resources", "file_delete_status", "文件删除状态:active待删/deleting删除中/deleted已删除/error失败")
_comment_column("upload_resources", "file_delete_error", "文件删除失败信息")
_comment_column("upload_resources", "created_at", "创建时间")
_comment_column("upload_resources", "updated_at", "更新时间")
_comment_column("upload_resources", "deleted_at", "软删除时间")
# --- image_engines 表 ---
_comment_table("image_engines", "图片生成引擎配置表")
_comment_column("image_engines", "id", "主键ID")
_comment_column("image_engines", "name", "引擎显示名称")
_comment_column("image_engines", "provider", "供应商:ark/其他")
_comment_column("image_engines", "api_base", "API基础地址")
_comment_column("image_engines", "api_key", "API密钥")
_comment_column("image_engines", "model_name", "模型名")
_comment_column("image_engines", "supported_models", "支持的模型列表JSON")
_comment_column("image_engines", "supported_sizes", "支持尺寸JSON{分辨率:{比例:像素}}")
_comment_column("image_engines", "default_size", "默认分辨率档位")
_comment_column("image_engines", "max_image_count", "允许生成图片数量上限")
_comment_column("image_engines", "multi_generation_enabled", "是否允许多份生成")
_comment_column("image_engines", "max_generation_count", "多份生成最大份数")
_comment_column("image_engines", "multi_image_max_images", "组图接口参考图+生成图数量上限")
_comment_column("image_engines", "max_reference_image_count", "最多参考图片张数")
_comment_column("image_engines", "output_format", "输出格式,空表示使用默认")
_comment_column("image_engines", "generate_url", "生成接口URL,留空使用SDK默认")
_comment_column("image_engines", "is_active", "是否启用")
_comment_column("image_engines", "priority", "排序优先级,越大越优先")
_comment_column("image_engines", "created_at", "创建时间")
_comment_column("image_engines", "updated_at", "更新时间")
_comment_column("image_engines", "deleted_at", "软删除时间")
# --- video_engines 表 ---
_comment_table("video_engines", "视频生成引擎配置表")
_comment_column("video_engines", "id", "主键ID")
_comment_column("video_engines", "name", "引擎显示名称")
_comment_column("video_engines", "provider", "供应商:ark/其他")
_comment_column("video_engines", "api_base", "API基础地址")
_comment_column("video_engines", "api_key", "API密钥")
_comment_column("video_engines", "model_name", "模型名")
_comment_column("video_engines", "supported_ratios", "支持比例JSON数组")
_comment_column("video_engines", "supported_resolutions", "支持分辨率JSON数组")
_comment_column("video_engines", "supported_durations", "支持时长JSON数组")
_comment_column("video_engines", "max_duration", "最大时长秒数")
_comment_column("video_engines", "max_image_count", "最多参考图片张数,0表示不支持")
_comment_column("video_engines", "max_video_count", "最多参考视频段数,0表示不支持")
_comment_column("video_engines", "max_audio_count", "最多参考音频段数,0表示不支持")
_comment_column("video_engines", "multi_generation_enabled", "是否允许多份生成")
_comment_column("video_engines", "max_generation_count", "多份生成最大份数")
_comment_column("video_engines", "supports_first_last_frame", "是否支持首尾帧参考")
_comment_column("video_engines", "supports_universal_reference", "是否支持通用参考素材")
_comment_column("video_engines", "generate_url", "生成接口URL")
_comment_column("video_engines", "query_url", "查询接口URL")
_comment_column("video_engines", "is_active", "是否启用")
_comment_column("video_engines", "priority", "排序优先级")
_comment_column("video_engines", "created_at", "创建时间")
_comment_column("video_engines", "updated_at", "更新时间")
_comment_column("video_engines", "deleted_at", "软删除时间")
# --- model_configs 表 ---
_comment_table("model_configs", "文本模型配置表(提示词优化等文本模型)")
_comment_column("model_configs", "id", "主键ID")
_comment_column("model_configs", "name", "模型显示名称")
_comment_column("model_configs", "provider", "供应商")
_comment_column("model_configs", "api_base", "API基础地址")
_comment_column("model_configs", "api_key", "API密钥")
_comment_column("model_configs", "model_name", "模型名")
_comment_column("model_configs", "weight", "权重,权重选择时使用")
_comment_column("model_configs", "max_tokens", "最大输出Token数")
_comment_column("model_configs", "temperature", "采样温度")
_comment_column("model_configs", "is_active", "是否启用")
_comment_column("model_configs", "priority", "排序优先级")
_comment_column("model_configs", "created_at", "创建时间")
_comment_column("model_configs", "updated_at", "更新时间")
_comment_column("model_configs", "deleted_at", "软删除时间")
# --- system_configs 表 ---
_comment_table("system_configs", "系统配置表")
_comment_column("system_configs", "id", "主键ID")
_comment_column("system_configs", "key", "配置键名,唯一")
_comment_column("system_configs", "value", "配置值")
_comment_column("system_configs", "description", "配置说明")
_comment_column("system_configs", "created_at", "创建时间")
_comment_column("system_configs", "updated_at", "更新时间")
# --- operation_logs 表 ---
_comment_table("operation_logs", "操作日志表")
_comment_column("operation_logs", "id", "主键ID")
_comment_column("operation_logs", "user_id", "操作用户ID")
_comment_column("operation_logs", "username", "操作用户名")
_comment_column("operation_logs", "action", "操作动作:CREATE/UPDATE/DELETE等")
_comment_column("operation_logs", "method", "HTTP方法:GET/POST/PUT/DELETE")
_comment_column("operation_logs", "path", "请求路径")
_comment_column("operation_logs", "detail", "操作详情JSON")
_comment_column("operation_logs", "ip", "客户端IP")
_comment_column("operation_logs", "created_at", "创建时间")
_comment_column("operation_logs", "updated_at", "更新时间")
# --- notifications 表 ---
_comment_table("notifications", "通知消息表")
_comment_column("notifications", "id", "主键ID")
_comment_column("notifications", "user_id", "接收用户IDNULL表示全体广播")
_comment_column("notifications", "title", "通知标题")
_comment_column("notifications", "content", "通知内容")
_comment_column("notifications", "type", "通知类型:system系统公告/billing账单通知等")
_comment_column("notifications", "is_read", "是否已读")
_comment_column("notifications", "related_id", "关联业务ID")
_comment_column("notifications", "created_at", "创建时间")
_comment_column("notifications", "updated_at", "更新时间")
# --- recharge_packages 表 ---
_comment_table("recharge_packages", "积分充值套餐表")
_comment_column("recharge_packages", "id", "主键ID")
_comment_column("recharge_packages", "name", "套餐名称")
_comment_column("recharge_packages", "credits", "套餐包含积分")
_comment_column("recharge_packages", "price", "套餐价格(元)")
_comment_column("recharge_packages", "bonus_credits", "赠送积分")
_comment_column("recharge_packages", "description", "套餐描述")
_comment_column("recharge_packages", "package_type", "套餐类型:normal普通/gift赠送首充等")
_comment_column("recharge_packages", "is_gift", "是否赠送套餐")
_comment_column("recharge_packages", "is_active", "是否启用")
_comment_column("recharge_packages", "sort_order", "排序值,越小越靠前")
_comment_column("recharge_packages", "created_at", "创建时间")
_comment_column("recharge_packages", "updated_at", "更新时间")
# --- payment_orders 表 ---
_comment_table("payment_orders", "支付订单表")
_comment_column("payment_orders", "id", "主键ID")
_comment_column("payment_orders", "user_id", "下单用户ID")
_comment_column("payment_orders", "order_no", "订单号,唯一")
_comment_column("payment_orders", "amount", "支付金额(元)")
_comment_column("payment_orders", "credits", "获得积分总数(含赠送)")
_comment_column("payment_orders", "payment_method", "支付方式:wxpay/alipay等")
_comment_column("payment_orders", "status", "订单状态:pending待支付/paid已支付/refunded已退款/failed失败")
_comment_column("payment_orders", "paid_at", "支付成功时间")
_comment_column("payment_orders", "trade_no", "第三方支付流水号")
_comment_column("payment_orders", "refund_trade_no", "退款流水号")
_comment_column("payment_orders", "refunded_at", "退款完成时间")
_comment_column("payment_orders", "refund_amount", "退款金额")
_comment_column("payment_orders", "created_at", "创建时间")
_comment_column("payment_orders", "updated_at", "更新时间")
# --- video_upscale_tasks 表 ---
_comment_table("video_upscale_tasks", "视频超分任务表")
_comment_column("video_upscale_tasks", "id", "主键ID")
_comment_column("video_upscale_tasks", "chat_generation_task_id", "关联AI创作任务ID,与generation_record_id二选一")
_comment_column("video_upscale_tasks", "generation_record_id", "关联项目生成记录ID,与chat_generation_task_id二选一")
_comment_column("video_upscale_tasks", "api_generation_task_id", "关联API生成任务ID")
_comment_column("video_upscale_tasks", "status", "任务状态:pending/processing/success/failed")
_comment_column("video_upscale_tasks", "stage", "阶段:upscale_queued/upscale_processing等")
_comment_column("video_upscale_tasks", "processor_key", "处理节点标识")
_comment_column("video_upscale_tasks", "attempt_count", "执行尝试次数")
_comment_column("video_upscale_tasks", "failure_count", "失败次数")
_comment_column("video_upscale_tasks", "manual_retry_count", "手动重试次数")
_comment_column("video_upscale_tasks", "next_retry_at", "下次重试时间")
_comment_column("video_upscale_tasks", "last_error", "最后错误信息")
_comment_column("video_upscale_tasks", "source_local_path", "源视频本地路径")
_comment_column("video_upscale_tasks", "source_file_size_bytes", "源文件大小(字节)")
_comment_column("video_upscale_tasks", "source_width", "源视频宽度")
_comment_column("video_upscale_tasks", "source_height", "源视频高度")
_comment_column("video_upscale_tasks", "source_duration_seconds", "源视频时长秒数")
_comment_column("video_upscale_tasks", "source_deleted_at", "源文件删除时间")
_comment_column("video_upscale_tasks", "source_delete_error", "源文件删除错误")
_comment_column("video_upscale_tasks", "source_remote_url", "源文件远程URL")
_comment_column("video_upscale_tasks", "source_remote_url_signed_at", "远程URL签名时间")
_comment_column("video_upscale_tasks", "source_remote_url_expires_at", "远程URL过期时间")
_comment_column("video_upscale_tasks", "source_remote_url_last_probe_at", "远程URL最后探测时间")
_comment_column("video_upscale_tasks", "source_remote_url_probe_status", "远程URL探测状态")
_comment_column("video_upscale_tasks", "input_source_type", "输入源类型:local/remote")
_comment_column("video_upscale_tasks", "input_source_fallback_count", "输入源回退次数")
_comment_column("video_upscale_tasks", "target_width", "目标宽度像素")
_comment_column("video_upscale_tasks", "target_height", "目标高度像素")
_comment_column("video_upscale_tasks", "effective_target_width", "实际生效目标宽度")
_comment_column("video_upscale_tasks", "effective_target_height", "实际生效目标高度")
_comment_column("video_upscale_tasks", "provider_task_id", "供应商超分任务ID")
_comment_column("video_upscale_tasks", "provider_request_json", "供应商请求JSON")
_comment_column("video_upscale_tasks", "provider_response_json", "供应商响应JSON")
_comment_column("video_upscale_tasks", "provider_output_url", "供应商输出URL")
_comment_column("video_upscale_tasks", "provider_output_url_expires_at", "供应商输出URL过期")
_comment_column("video_upscale_tasks", "provider_submitted_at", "提交供应商时间")
_comment_column("video_upscale_tasks", "final_local_path", "最终本地文件路径")
_comment_column("video_upscale_tasks", "final_resource_url
Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

+36 -36
View File
@@ -1,36 +1,36 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" id="favicon" />
<script>
// 立即从 localStorage 设置 favicon,避免闪烁
(function() {
try {
var cached = localStorage.getItem('siteInfo');
if (cached) {
var info = JSON.parse(cached);
if (info.siteLogo) {
var link = document.getElementById('favicon');
link.href = info.siteLogo;
link.type = 'image/png';
}
if (info.siteName) {
document.title = info.siteName;
}
}
} catch (e) {}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>民众智创</title>
<script type="module" crossorigin src="/assets/index-BioNCQJL.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-IrbjYl3V.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" id="favicon" />
<script>
// 立即从 localStorage 设置 favicon,避免闪烁
(function() {
try {
var cached = localStorage.getItem('siteInfo');
if (cached) {
var info = JSON.parse(cached);
if (info.siteLogo) {
var link = document.getElementById('favicon');
link.href = info.siteLogo;
link.type = 'image/png';
}
if (info.siteName) {
document.title = info.siteName;
}
}
} catch (e) {}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>民众智创</title>
<script type="module" crossorigin src="/assets/index-DidmuRtb.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-KEztuQj6.css">
</head>
<body>
<div id="root"></div>
</body>
</html>