commit 06d76e0e69c1108b72208150f2bdb2f5d3af9e53 Author: p <1332606303@qq.com> Date: Sat Jan 17 15:26:14 2026 +0800 feat: initial commit of AI Comic Generator diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..91cd886 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +.venv/ +venv/ +*.db +*.sqlite3 + +# Node +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Project specific +comic_gen/ +backend/comic_app.db +backend/*.db +backend/static/*.png diff --git a/.trae/documents/Full-Stack AI Comic Generator Implementation Plan.md b/.trae/documents/Full-Stack AI Comic Generator Implementation Plan.md new file mode 100644 index 0000000..b94ce3d --- /dev/null +++ b/.trae/documents/Full-Stack AI Comic Generator Implementation Plan.md @@ -0,0 +1,83 @@ +# Full-Stack AI Comic Generator Implementation Plan + +This plan details the creation of a web-based AI comic generator using Vue 3 (Frontend) and FastAPI (Backend), separating concerns into a modular architecture. + +## 1. Project Structure Setup +We will create a root directory containing two main folders: `backend` and `frontend`. + +### Backend Structure (`backend/`) +- **Framework**: FastAPI +- **Database**: SQLite (via SQLModel/SQLAlchemy) for storing project data and configs. +- **Directory Layout**: + - `app/` + - `core/`: Configuration (env vars, DB settings). + - `models/`: Database models (SQLModel). + - `schemas/`: Pydantic models for request/response validation. + - `cruds/`: Database CRUD operations. + - `routers/`: API endpoints grouped by functionality. + - `services/`: Business logic (AI generation, File management). + - `utils/`: Helper functions. + - `static/`: Serving generated images. + - `main.py`: Application entry point. + +### Frontend Structure (`frontend/`) +- **Framework**: Vue 3 + Vite +- **UI Library**: Element Plus (Dark Mode enabled for "Tech" style). +- **Directory Layout**: + - `src/` + - `api/`: Axios instances for backend communication. + - `components/`: Reusable UI components (JSON Editor, Image Cards). + - `views/`: Main pages (Config, Workspace). + - `stores/`: Pinia state management. + +## 2. Backend Implementation Steps + +### Phase 1: Core & Configuration +1. **Environment**: Setup `requirements.txt` (FastAPI, SQLModel, Uvicorn, Google GenAI, OpenAI, python-dotenv). +2. **Models & Schemas**: + - `ModelConfig`: Store API keys, provider (Google/OpenAI/DeepSeek), model names. + - `Project`: Store comic project metadata (title, status). + - `ComicData`: Store the generated JSONs (Global Config, Characters, Storyboard). +3. **CRUDs**: Implement basic Create/Read/Update/Delete operations for Configs and Projects. + +### Phase 2: AI Services Integration +1. **AI Provider Adapter**: Create a unified interface to handle different providers (Google, DeepSeek, ChatGPT, etc.). +2. **Migration**: Refactor logic from `comic_generator.py` into `services/comic_service.py`. + - Implement `generate_storyboard` (Text generation). + - Implement `generate_character_image` (Image generation). + - Implement `generate_comic_panel` (Image generation with context). +3. **Endpoints**: + - `POST /api/generate/json`: Generate initial JSONs from user input. + - `POST /api/generate/image`: Generate specific image (Character or Panel). + - `POST /api/project/{id}/export`: Package and zip output. + +## 3. Frontend Implementation Steps + +### Phase 1: UI Framework & Configuration +1. **Setup**: Initialize Vue 3 project, install Element Plus, Axios, Pinia, Vue Router. +2. **Theme**: Configure Element Plus for Dark Mode/Tech style. +3. **Model Configuration Page**: + - Form to add/edit API keys and select models for Text and Image generation. + +### Phase 2: Comic Workflow Page +1. **Step 1: Concept & JSON**: + - Input field for story idea. + - "Generate" button. + - **JSON Editor**: Integrated code editor (e.g., Monaco Editor) to modify generated JSONs (Global Config, Characters, Storyboard). +2. **Step 2: Character Studio**: + - Display list of characters from JSON. + - "Generate/Regenerate" button for each character. + - Support "Add Character" manually. +3. **Step 3: Comic Board**: + - Display storyboard panels. + - "Generate/Regenerate" button for each panel (4-grid or single). + - Support modifying prompt per panel. +4. **Step 4: Export**: + - Button to download the complete comic package. + +## 4. Execution Strategy +1. **Backend First**: I will build the FastAPI backend, ensuring the API is functional and can replicate the existing script's logic. +2. **Frontend Second**: I will build the Vue frontend and connect it to the backend. +3. **Verification**: I will test the full flow: Config -> Story Input -> Edit JSON -> Generate Images -> Export. + +I will begin by setting up the backend structure and dependencies. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5193af3 --- /dev/null +++ b/README.md @@ -0,0 +1,148 @@ +# AI Comic Generator + +[English](./README.md) | [中文](./README_CN.md) + +An open-source AI-powered manga creation tool that transforms text stories into fully illustrated comics using Google Gemini models. Features include automatic storyboard generation, character consistency checks, and a visual editor. + +![Project Status](https://img.shields.io/badge/Status-Active-success) +![Python](https://img.shields.io/badge/Backend-FastAPI-blue) +![Vue](https://img.shields.io/badge/Frontend-Vue3-green) + +## ✨ Core Features + +* **Project Management**: Supports multi-project management, with each project independently saving story, characters, and storyboard data. +* **Intelligent Scriptwriting**: Input simple story ideas, and AI automatically expands the plot and breaks it down into professional comic storyboard scripts (JSON format). +* **Character Workshop**: + * Automatically extracts characters from the story and generates detailed character settings (three views). + * **Character Consistency**: Automatically references character setting images as references (Image-to-Image) when drawing. + * **Merge & Deduplication**: Supports manual merging of duplicate generated characters (e.g., "Butler Ma" and "Old Ma"). +* **Storyboard Editor**: + * Visually edit prompts, characters, and actions for each panel. + * Supports single-panel redrawing and batch generation. + * **Context Awareness**: Automatically reads previous storyboard panels and character images when generating storyboards to maintain style and plot continuity. +* **Style Control**: + * Global style configuration (e.g., "Cyberpunk", "Ink Style"), forcing AI to follow settings. + * Supports custom dialog box and border styles. +* **Background Tasks**: Time-consuming batch drawing tasks run in the background, supporting real-time progress viewing. + +## 🛠️ Tech Stack + +### Backend +* **Framework**: FastAPI +* **Database**: SQLite + SQLModel +* **AI Service**: Google Gemini (Currently only supports Google's latest models) + * **Text Model**: `gemini-3-flash-preview` + * **Image Model**: `gemini-3-pro-image-preview` +* **Task Queue**: FastAPI BackgroundTasks + +### Frontend +* **Framework**: Vue 3 + Vite +* **UI Library**: Element Plus +* **State Management**: Pinia +* **HTTP Client**: Axios +* **Package Manager**: pnpm + +## 🚀 Quick Start + +### Prerequisites +* Python 3.9+ +* Node.js 16+ +* pnpm +* Google Cloud API Key (Requires Gemini API access) + +### 1. Backend Configuration + +Enter the `backend` directory: + +```bash +cd backend +``` + +Create virtual environment and install dependencies: + +```bash +python -m venv .venv +# Windows +.\.venv\Scripts\activate +# Linux/Mac +source .venv/bin/activate + +pip install -r requirements.txt +``` + +Create `.env` file and fill in API Key: + +```ini +# backend/.env +GOOGLE_API_KEY="your_google_api_key_here" +``` + +Start backend service: + +```bash +# Windows (using provided script) +..\start_backend.bat + +# Or run manually +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +### 2. Frontend Configuration + +Enter the `frontend` directory: + +```bash +cd frontend +``` + +Install dependencies: + +```bash +pnpm install +``` + +Start frontend service: + +```bash +# Windows (using provided script) +..\start_frontend.bat + +# Or run manually +pnpm dev +``` + +Access in browser: `http://localhost:5173` + +## 📖 User Guide + +1. **Create Project**: Click "New Project" on the homepage and enter the comic title and introduction. +2. **Story & Configuration**: + * Enter your story outline. + * Set global styles (e.g., "Japanese Shonen"), aspect ratio, etc. + * Click "Generate Storyboard Config", and AI will generate the character list and storyboard script. +3. **Character Workshop**: + * View AI-generated character settings. + * Click "Draw" to generate character portraits. + * If there are duplicate characters, use the "Merge Characters" function to clean them up. +4. **Storyboard Editing**: + * Check the description of each panel in the storyboard list. + * Click "Generate Image" or "Generate All" to start drawing the comic. + * Click on an image to view it in large size and support downloading. + +## 📁 Directory Structure + +``` +aImanhua/ +├── backend/ # FastAPI Backend +│ ├── app/ # Application Code +│ ├── static/ # Generated images and temp files storage +│ └── ... +├── frontend/ # Vue3 Frontend +│ ├── src/ # Pages and Components +│ └── ... +└── ... +``` + +## 📝 License + +MIT License diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..f22854d --- /dev/null +++ b/README_CN.md @@ -0,0 +1,148 @@ +# AI Comic Generator + +[English](./README.md) | [中文](./README_CN.md) + +一个基于 AI 的全流程漫画创作辅助工具,集成了故事生成、分镜拆解、角色设定、一致性控制和批量绘图功能。 + +![Project Status](https://img.shields.io/badge/Status-Active-success) +![Python](https://img.shields.io/badge/Backend-FastAPI-blue) +![Vue](https://img.shields.io/badge/Frontend-Vue3-green) + +## ✨ 核心功能 + +* **项目管理**: 支持多项目管理,每个项目独立保存故事、角色和分镜数据。 +* **智能编剧**: 输入简单的故事点子,AI 自动扩充情节并拆分为专业的漫画分镜脚本 (JSON 格式)。 +* **角色工坊**: + * 自动从故事中提取角色并生成详细的人物设定(三视图)。 + * **角色一致性**: 绘图时自动引用角色设定图作为参考(Image-to-Image)。 + * **合并与去重**: 支持手动合并重复生成的角色(如“马管家”和“马老”)。 +* **分镜编辑器**: + * 可视化编辑每一格分镜的提示词、角色和动作。 + * 支持单格重绘、批量生成。 + * **上下文感知**: 生成分镜时自动读取前序分镜和角色图,保持画风和剧情连贯性。 +* **风格控制**: + * 全局风格配置(如“赛博朋克”、“水墨风”),强制 AI 遵循设定。 + * 支持自定义对话框、边框样式。 +* **后台任务**: 耗时的批量生图任务在后台运行,支持进度实时查看。 + +## 🛠️ 技术栈 + +### Backend (后端) +* **Framework**: FastAPI +* **Database**: SQLite + SQLModel +* **AI Service**: Google Gemini (目前仅支持 Google 最新模型) + * **文本模型**: `gemini-3-flash-preview` + * **图像模型**: `gemini-3-pro-image-preview` +* **Task Queue**: FastAPI BackgroundTasks + +### Frontend (前端) +* **Framework**: Vue 3 + Vite +* **UI Library**: Element Plus +* **State Management**: Pinia +* **HTTP Client**: Axios +* **Package Manager**: pnpm + +## 🚀 快速开始 + +### 前置要求 +* Python 3.9+ +* Node.js 16+ +* pnpm +* Google Cloud API Key (需开通 Gemini API 权限) + +### 1. 后端配置 + +进入 `backend` 目录: + +```bash +cd backend +``` + +创建虚拟环境并安装依赖: + +```bash +python -m venv .venv +# Windows +.\.venv\Scripts\activate +# Linux/Mac +source .venv/bin/activate + +pip install -r requirements.txt +``` + +创建 `.env` 文件并填入 API Key: + +```ini +# backend/.env +GOOGLE_API_KEY="your_google_api_key_here" +``` + +启动后端服务: + +```bash +# Windows (使用提供的脚本) +..\start_backend.bat + +# 或者手动运行 +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +### 2. 前端配置 + +进入 `frontend` 目录: + +```bash +cd frontend +``` + +安装依赖: + +```bash +pnpm install +``` + +启动前端服务: + +```bash +# Windows (使用提供的脚本) +..\start_frontend.bat + +# 或者手动运行 +pnpm dev +``` + +访问浏览器:`http://localhost:5173` + +## 📖 使用指南 + +1. **创建项目**: 在首页点击“新建项目”,输入漫画标题和简介。 +2. **故事与配置**: + * 输入你的故事大纲。 + * 设置全局风格(如“日系少年漫”)、画幅比例等。 + * 点击“生成分镜配置”,AI 将生成角色表和分镜脚本。 +3. **角色工坊**: + * 查看 AI 生成的角色设定。 + * 点击“绘制”生成角色立绘。 + * 如有重复角色,使用“合并角色”功能进行清理。 +4. **分镜编辑**: + * 在分镜列表中检查每一格的描述。 + * 点击“生成图片”或“一键生成所有”开始绘制漫画。 + * 点击图片可查看大图,支持下载。 + +## 📁 目录结构 + +``` +aImanhua/ +├── backend/ # FastAPI 后端 +│ ├── app/ # 应用代码 +│ ├── static/ # 生成的图片和临时文件存储 +│ └── ... +├── frontend/ # Vue3 Frontend +│ ├── src/ # 页面与组件 +│ └── ... +└── ... +``` + +## 📝 License + +MIT License diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..939b51a --- /dev/null +++ b/backend/.env @@ -0,0 +1 @@ +DATABASE_URL=sqlite:///./comic_app.db diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 0000000..807ded2 --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,149 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# Or organize into date-based subdirectories (requires recursive_version_locations = true) +# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/alembic/README b/backend/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/backend/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/backend/alembic/env.py b/backend/alembic/env.py new file mode 100644 index 0000000..b91841c --- /dev/null +++ b/backend/alembic/env.py @@ -0,0 +1,83 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# Import SQLModel and your models +from sqlmodel import SQLModel +from app.models import models # Ensure models are imported to register metadata +from app.core.config import settings + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# Set the SQLAlchemy URL from settings +config.set_main_option("sqlalchemy.url", settings.DATABASE_URL) + +# target_metadata = None +target_metadata = SQLModel.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/backend/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/backend/alembic/versions/ca058f9ddb60_initial_full_schema.py b/backend/alembic/versions/ca058f9ddb60_initial_full_schema.py new file mode 100644 index 0000000..520283e --- /dev/null +++ b/backend/alembic/versions/ca058f9ddb60_initial_full_schema.py @@ -0,0 +1,110 @@ +"""Initial Full Schema + +Revision ID: ca058f9ddb60 +Revises: +Create Date: 2026-01-17 10:58:17.473117 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'ca058f9ddb60' +down_revision: Union[str, Sequence[str], None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('modelconfig', + sa.Column('provider', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('api_key', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('base_url', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('model_name', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('model_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('project', + sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('story_input', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('theme', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('language', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('panel_count', sa.Integer(), nullable=True), + sa.Column('aspect_ratio', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('character', + sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('data', sa.JSON(), nullable=True), + sa.Column('image_url', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('project_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.ForeignKeyConstraint(['project_id'], ['project.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('globalconfig', + sa.Column('data', sa.JSON(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('project_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.ForeignKeyConstraint(['project_id'], ['project.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('imagehistory', + sa.Column('entity_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('entity_id', sa.Integer(), nullable=False), + sa.Column('image_url', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('project_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['project_id'], ['project.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('storyboarditem', + sa.Column('sequence', sa.Integer(), nullable=False), + sa.Column('data', sa.JSON(), nullable=True), + sa.Column('image_url', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('project_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.ForeignKeyConstraint(['project_id'], ['project.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task', + sa.Column('type', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('progress', sa.Integer(), nullable=False), + sa.Column('message', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column('result', sa.JSON(), nullable=True), + sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('project_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['project_id'], ['project.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('task') + op.drop_table('storyboarditem') + op.drop_table('imagehistory') + op.drop_table('globalconfig') + op.drop_table('character') + op.drop_table('project') + op.drop_table('modelconfig') + # ### end Alembic commands ### diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/core/config.py b/backend/app/core/config.py new file mode 100644 index 0000000..5342d32 --- /dev/null +++ b/backend/app/core/config.py @@ -0,0 +1,12 @@ +import os +from pydantic_settings import BaseSettings + +class Settings(BaseSettings): + PROJECT_NAME: str = "AI Comic Generator" + API_V1_STR: str = "/api/v1" + DATABASE_URL: str = "sqlite:///./comic_app.db" + + class Config: + env_file = ".env" + +settings = Settings() diff --git a/backend/app/core/database.py b/backend/app/core/database.py new file mode 100644 index 0000000..09d8a9f --- /dev/null +++ b/backend/app/core/database.py @@ -0,0 +1,15 @@ +from sqlmodel import SQLModel, create_engine, Session +from app.core.config import settings + +connect_args = {} +if "sqlite" in settings.DATABASE_URL: + connect_args["check_same_thread"] = False + +engine = create_engine(settings.DATABASE_URL, echo=False, connect_args=connect_args) + +def init_db(): + SQLModel.metadata.create_all(engine) + +def get_session(): + with Session(engine) as session: + yield session diff --git a/backend/app/core/prompts.py b/backend/app/core/prompts.py new file mode 100644 index 0000000..e380520 --- /dev/null +++ b/backend/app/core/prompts.py @@ -0,0 +1,102 @@ +# This file is used to centrally manage the core system prompt (System Prompt) of the comic generation system +# Provided to the backend service in the form of Python variables to avoid path or encoding issues caused by directly reading text files + +COMIC_GENERATION_SYSTEM_PROMPT = """As a 'Comic Split Generation' expert, you will play the dual role of a comic editing expert and a comic drawing expert. Your goal is to assist users in the entire process from story creation to storyboard design, and finally generate a serialized comic. + +Purpose and Goals: +* Expand or optimize the user-provided content into a story with a complete plot, ensuring logical self-consistency and engagement. +* Clarify the overall comic style. You must strictly use the user-specified style (if any) and are prohibited from making decisions on your own. +* Extract characters from the story and generate character setting cards containing core information about appearance, clothing, and personality. +* Split the story into serialized comic storyboards, ensuring natural transitions between panels and appropriate narrative pacing. +* Provide extremely detailed visual descriptions for each panel, precise to lighting, composition, character expression, and key actions. +* Provide storyboard content in JSON file format, strictly stipulating that every four panels constitute an independent JSON object block for structured processing. + +Behaviors and Rules: + +1) Story Optimization & Character Setting (Story & Character): + a) Receive the user's initial idea, enrich its background details, emotional ups and downs, and climax ending. + b) Confirm Art Style: If the user provides specific "Theme" or "Style" requirements, you must follow them unconditionally. Do not modify the style based on the story content. For example, if the user requests "Cyberpunk", even if the story is set in a martial arts background, you must generate "Cyberpunk style martial arts". + c) Before starting the storyboard, list the main characters and their physical characteristics (such as hair color, eye color, signature accessories, etc.) in detail to ensure visual consistency of characters in subsequent storyboards. + +2) Storyboard Splitting & JSON Construction (Storyboard & JSON): + a) Comic Global Configuration (Comic Configuration): + - **Must be generated first**: Before starting to generate characters and storyboards, you must generate an independent 'comic_config' JSON block. + - **Function**: Define the visual tone, typography standards, and border styles of the entire comic. + - JSON structure should contain: + - 'type': 'comic_config' + - 'language': 'English', + - 'style': '{User Specified Style}' (Must fill in the user-specified style, if not specified, default to 'Chibi/Fantasy Style') + - 'bubble_style': { 'shape': 'Bubble Shape', 'color': 'Background Color', 'font_color': 'Font Color', 'stroke_width': 'Stroke Width' } + - 'narration_style': { 'shape': 'Box/Rounded', 'color': 'Background Color', 'font_color': 'Font Color', 'opacity': 'Opacity' } + - 'border_style': { 'width': 'Line Width', 'color': 'Color', 'type': 'Solid/Hand-drawn' } + - 'gutter_style': { 'type': 'Standard Cross Split', 'color': 'White', 'width': '10px' } + - 'layout_settings': { + 'show_panel_numbers': false, // [Switch] Whether to show panel numbers + 'panel_number_style': { 'position': 'top-left', 'bg_color': '#000000', 'text_color': '#00FF41', 'font_size': '14px' }, + 'force_uniform_borders': true, + 'composition_mode': 'grid' // grid=Grid Splicing, cinematic=Cinematic Widescreen + } + - 'aspect_ratio': '16:9' + + b) Character Sheet Generation (Character Sheets): + - **All characters must have setting cards**: Before generating story storyboards, you must generate independent JSON setting blocks for all named characters appearing in the story, including protagonists, frequent supporting characters, and villains. + - **Protagonist Design**: The protagonist's image must be designed to be extremely attractive, with distinct physical features, meeting "high aesthetic" standards. + - **Character Deduplication**: When generating the character list, carefully identify different names for the same character (e.g., "Butler Ma" and "Old Ma" are the same person). If found to be the same person, generate only one character setting card and use the most formal or common name in the name field. Strictly prohibit generating multiple duplicate setting cards for the same character. + - If there are multiple main characters, please generate multiple independent 'character_sheet' JSON blocks respectively, or output them in a JSON array. + - JSON structure should contain: + - 'type': 'character_sheet' + - 'name': 'Character Name' + - 'meta_info':{ + - 'language': Language based on user input + - 'role': 'Protagonist' | 'Supporting' | 'Extra' (Must indicate character type) + - 'personality': 'Character personality traits, e.g., Cheerful, Cold, Hot-blooded, etc., which will affect expressions and poses' + - 'age': 'Age description, approximate range' + - 'relationships': 'Description of relationship with protagonist or other characters' (Must indicate interpersonal relationships) + - 'style': '{User Specified Style}', + - 'feature': 'Explicit character features, e.g., Youthful, Plump, etc.' + - 'aspect_ratio': '16:9' + } + - 'design_panels': [ + {'view': 'Front View', 'description': 'Detailed front full-body description...'}, + {'view': 'Side View', 'description': 'Detailed side view description...'}, + {'view': 'Clothing', 'description': 'Detailed clothing details...'}, + {'view': 'Accessories', 'description': 'Detailed accessories/weapon details...'} + ] + + c) Story Storyboard Generation (Story Storyboard): + - Decompose the optimized story into concrete, visualizable storyboard frames. + - **Storyboard Count Mandatory Requirement**: The total number of generated panels must be determined based on the story content. The richer the story, the more panels, unless the user input requires a minimum number of panels (e.g., "at least 36 panels"). + - The total number of generated panels must be >= the minimum number required by the user. + - The total number of generated panels must be an integer multiple of 4 (e.g., 36, 40, 44...), rounding up if not satisfied. + + - Construct every four panels as an independent JSON code block output. + - JSON structure should contain: + - 'type': 'storyboard' + - 'meta_info': { + 'style': '{User Specified Style}', + 'language': 'English', + 'volume': 'Current Volume/Total Volumes', + 'aspect_ratio': '16:9', + } + - 'characters': ['List of characters appearing in this group of panels'] + - 'plot_breakdown': [ + {'panel': 1, 'scene': '...', 'action': '...', 'dialogue': '...'}, + {'panel': 2, 'scene': '...', 'action': '...', 'dialogue': '...'}, + {'panel': 3, 'scene': '...', 'action': '...', 'dialogue': '...'}, + {'panel': 4, 'scene': '...', 'action': '...', 'dialogue': '...'} + ] + +3) Quality Control (Quality Control): + a) Ensure visual logic consistency between panels, avoiding sudden changes in characters or environment. + b) Visual prompts should include elements such as environment, weather, shot type (e.g., close-up, panoramic), etc. + +4) Language & Format Requirements (Language & Format): + a) Use the same language for dialogue and narration as the user input. English is the standard. + b) All JSON outputs must maintain a strict, parsable code block format. + c) All JSON keys must use lowercase English (e.g., 'language', 'style'). + + +Overall Tone: +* Professional and highly creative, demonstrating the rigor and aesthetics of a senior industry practitioner. +* Descriptions of visual details should be precise and evocative. +""" diff --git a/backend/app/cruds/__init__.py b/backend/app/cruds/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/cruds/crud_config.py b/backend/app/cruds/crud_config.py new file mode 100644 index 0000000..10a1562 --- /dev/null +++ b/backend/app/cruds/crud_config.py @@ -0,0 +1,35 @@ +from sqlmodel import Session, select +from app.models.models import ModelConfig +from app.schemas.schemas import ModelConfigCreate, ModelConfigUpdate +from typing import List, Optional + +def create_model_config(session: Session, config_in: ModelConfigCreate) -> ModelConfig: + db_config = ModelConfig.model_validate(config_in) + session.add(db_config) + session.commit() + session.refresh(db_config) + return db_config + +def get_model_configs(session: Session, skip: int = 0, limit: int = 100) -> List[ModelConfig]: + statement = select(ModelConfig).offset(skip).limit(limit) + return session.exec(statement).all() + +def get_model_config(session: Session, config_id: int) -> Optional[ModelConfig]: + return session.get(ModelConfig, config_id) + +def update_model_config(session: Session, db_config: ModelConfig, config_in: ModelConfigUpdate) -> ModelConfig: + config_data = config_in.model_dump(exclude_unset=True) + for key, value in config_data.items(): + setattr(db_config, key, value) + session.add(db_config) + session.commit() + session.refresh(db_config) + return db_config + +def delete_model_config(session: Session, db_config: ModelConfig): + session.delete(db_config) + session.commit() + +def get_active_config(session: Session, model_type: str) -> Optional[ModelConfig]: + statement = select(ModelConfig).where(ModelConfig.model_type == model_type, ModelConfig.is_active == True) + return session.exec(statement).first() diff --git a/backend/app/cruds/crud_project.py b/backend/app/cruds/crud_project.py new file mode 100644 index 0000000..2207464 --- /dev/null +++ b/backend/app/cruds/crud_project.py @@ -0,0 +1,99 @@ +from sqlmodel import Session, select +from app.models.models import Project, Character, StoryboardItem, GlobalConfig +from app.schemas.schemas import ProjectCreate, ProjectUpdate +from typing import List, Optional + +def create_project(session: Session, project_in: ProjectCreate) -> Project: + db_project = Project.model_validate(project_in) + session.add(db_project) + session.commit() + session.refresh(db_project) + return db_project + +def get_projects(session: Session, skip: int = 0, limit: int = 100) -> List[Project]: + statement = select(Project).offset(skip).limit(limit).order_by(Project.updated_at.desc()) + return session.exec(statement).all() + +def get_project(session: Session, project_id: str) -> Optional[Project]: + return session.get(Project, project_id) + +def update_project(session: Session, db_project: Project, project_in: ProjectUpdate) -> Project: + project_data = project_in.model_dump(exclude_unset=True) + for key, value in project_data.items(): + setattr(db_project, key, value) + session.add(db_project) + session.commit() + session.refresh(db_project) + return db_project + +def delete_project(session: Session, db_project: Project): + session.delete(db_project) + session.commit() + +# Helpers for sub-entities +def create_global_config(session: Session, project_id: str, data: dict) -> GlobalConfig: + # Check if exists + statement = select(GlobalConfig).where(GlobalConfig.project_id == project_id) + existing = session.exec(statement).first() + if existing: + existing.data = data + session.add(existing) + session.commit() + session.refresh(existing) + return existing + + db_config = GlobalConfig(project_id=project_id, data=data) + session.add(db_config) + session.commit() + session.refresh(db_config) + return db_config + +def save_characters(session: Session, project_id: str, characters_data: List[dict]) -> List[Character]: + # Strategy: Clear existing or update? + # For simplicity in this flow: Clear and Re-insert is easier if we regenerate all. + # But user might want to edit specific ones. + # Better: Update by name match, create if new. + + results = [] + for char_data in characters_data: + name = char_data.get("name") + if not name: continue + + statement = select(Character).where(Character.project_id == project_id, Character.name == name) + existing = session.exec(statement).first() + + if existing: + existing.data = char_data + session.add(existing) + results.append(existing) + else: + new_char = Character(project_id=project_id, name=name, data=char_data) + session.add(new_char) + results.append(new_char) + + session.commit() + return results + +def save_storyboard(session: Session, project_id: str, storyboard_data: List[dict]) -> List[StoryboardItem]: + # Similar strategy: Clear and Re-insert is risky if we have images. + # But storyboard is sequential. + # Let's delete all and re-insert for now as "Regenerate JSON" usually means fresh start. + # IF the user is just editing JSON text, we replace everything. + + # Check if there are existing items with images we want to preserve? + # Ideally, we should try to map them back, but it's hard if sequence changes. + # For now: delete all items for this project and insert new. + + statement = select(StoryboardItem).where(StoryboardItem.project_id == project_id) + existing_items = session.exec(statement).all() + for item in existing_items: + session.delete(item) + + results = [] + for i, item_data in enumerate(storyboard_data): + new_item = StoryboardItem(project_id=project_id, sequence=i+1, data=item_data) + session.add(new_item) + results.append(new_item) + + session.commit() + return results diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..3ce3807 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,40 @@ +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from fastapi.middleware.cors import CORSMiddleware +from app.core.config import settings +from app.core.database import init_db +from app.routers import configs, projects, generation, export, tasks, history +import os + +app = FastAPI(title=settings.PROJECT_NAME) + +# CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Mount static files +static_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "static") +if not os.path.exists(static_dir): + os.makedirs(static_dir) +app.mount("/static", StaticFiles(directory=static_dir), name="static") + +# Include routers +app.include_router(configs.router, prefix=f"{settings.API_V1_STR}/configs", tags=["configs"]) +app.include_router(projects.router, prefix=f"{settings.API_V1_STR}/projects", tags=["projects"]) +app.include_router(generation.router, prefix=f"{settings.API_V1_STR}/generate", tags=["generation"]) +app.include_router(export.router, prefix=f"{settings.API_V1_STR}/export", tags=["export"]) +app.include_router(tasks.router, prefix=f"{settings.API_V1_STR}/tasks", tags=["tasks"]) +app.include_router(history.router, prefix=f"{settings.API_V1_STR}/history", tags=["history"]) + +@app.on_event("startup") +def on_startup(): + init_db() + +@app.get("/") +def read_root(): + return {"message": "Welcome to AI Comic Generator API"} diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/models/models.py b/backend/app/models/models.py new file mode 100644 index 0000000..09bb084 --- /dev/null +++ b/backend/app/models/models.py @@ -0,0 +1,97 @@ +from typing import Optional, List, Dict, Any +from sqlmodel import SQLModel, Field, Relationship, Column, JSON +from datetime import datetime +import uuid + +# --- Base Models --- + +class ModelConfigBase(SQLModel): + provider: str + api_key: str + base_url: Optional[str] = None + model_name: str + model_type: str + is_active: bool = True + +class ProjectBase(SQLModel): + title: str + description: Optional[str] = None + story_input: Optional[str] = None + # Generation Preferences + theme: Optional[str] = None + language: Optional[str] = "zh-CN" + panel_count: Optional[int] = 16 + aspect_ratio: Optional[str] = "16:9" + +class CharacterBase(SQLModel): + name: str + data: Dict = Field(default={}, sa_column=Column(JSON)) + image_url: Optional[str] = None + +class StoryboardItemBase(SQLModel): + sequence: int + data: Dict = Field(default={}, sa_column=Column(JSON)) + image_url: Optional[str] = None + +class GlobalConfigBase(SQLModel): + data: Dict = Field(default={}, sa_column=Column(JSON)) + +class TaskBase(SQLModel): + type: str # 'storyboard', 'image_generation', 'export' + status: str # 'pending', 'processing', 'completed', 'failed' + progress: int = 0 # 0-100 + message: Optional[str] = None + name: Optional[str] = None + description: Optional[str] = None + result: Dict = Field(default={}, sa_column=Column(JSON)) + +class ImageHistoryBase(SQLModel): + entity_type: str # 'character' or 'storyboard_item' + entity_id: int + image_url: str + +# --- Table Models --- + +class ModelConfig(ModelConfigBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + +class Project(ProjectBase, table=True): + id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True) + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) + + characters: List["Character"] = Relationship(back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"}) + storyboard_items: List["StoryboardItem"] = Relationship(back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"}) + global_config: Optional["GlobalConfig"] = Relationship(back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"}) + tasks: List["Task"] = Relationship(back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"}) + image_history: List["ImageHistory"] = Relationship(back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"}) + +class Character(CharacterBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + project_id: str = Field(foreign_key="project.id") + project: Project = Relationship(back_populates="characters") + +class StoryboardItem(StoryboardItemBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + project_id: str = Field(foreign_key="project.id") + project: Project = Relationship(back_populates="storyboard_items") + +class GlobalConfig(GlobalConfigBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + project_id: str = Field(foreign_key="project.id") + project: Project = Relationship(back_populates="global_config") + +class Task(TaskBase, table=True): + id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True) + project_id: str = Field(foreign_key="project.id") + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) + + project: Project = Relationship(back_populates="tasks") + +class ImageHistory(ImageHistoryBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + project_id: str = Field(foreign_key="project.id") + created_at: datetime = Field(default_factory=datetime.utcnow) + + project: Project = Relationship(back_populates="image_history") diff --git a/backend/app/routers/__init__.py b/backend/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/routers/configs.py b/backend/app/routers/configs.py new file mode 100644 index 0000000..f26071a --- /dev/null +++ b/backend/app/routers/configs.py @@ -0,0 +1,39 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session +from typing import List +from app.core.database import get_session +from app.models.models import ModelConfig +from app.schemas.schemas import ModelConfigCreate, ModelConfigUpdate +from app.cruds import crud_config + +router = APIRouter() + +@router.post("/", response_model=ModelConfig) +def create_config(config_in: ModelConfigCreate, session: Session = Depends(get_session)): + return crud_config.create_model_config(session, config_in) + +@router.get("/", response_model=List[ModelConfig]) +def read_configs(skip: int = 0, limit: int = 100, session: Session = Depends(get_session)): + return crud_config.get_model_configs(session, skip, limit) + +@router.get("/{config_id}", response_model=ModelConfig) +def read_config(config_id: int, session: Session = Depends(get_session)): + config = crud_config.get_model_config(session, config_id) + if not config: + raise HTTPException(status_code=404, detail="Config not found") + return config + +@router.put("/{config_id}", response_model=ModelConfig) +def update_config(config_id: int, config_in: ModelConfigUpdate, session: Session = Depends(get_session)): + config = crud_config.get_model_config(session, config_id) + if not config: + raise HTTPException(status_code=404, detail="Config not found") + return crud_config.update_model_config(session, config, config_in) + +@router.delete("/{config_id}") +def delete_config(config_id: int, session: Session = Depends(get_session)): + config = crud_config.get_model_config(session, config_id) + if not config: + raise HTTPException(status_code=404, detail="Config not found") + crud_config.delete_model_config(session, config) + return {"ok": True} diff --git a/backend/app/routers/export.py b/backend/app/routers/export.py new file mode 100644 index 0000000..e2c5967 --- /dev/null +++ b/backend/app/routers/export.py @@ -0,0 +1,80 @@ +import shutil +import os +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session +from app.core.database import get_session +from app.models.models import Project +from app.services.image_service import split_comic_page + +router = APIRouter() + +@router.get("/{project_id}") +def export_project( + project_id: str, + split_images: bool = False, + session: Session = Depends(get_session) +): + project = session.get(Project, project_id) + if not project: + raise HTTPException(status_code=404, detail="Project not found") + + # Check if any images generated + has_images = any(item.image_url for item in project.storyboard_items) or any(c.image_url for c in project.characters) + if not has_images: + raise HTTPException(status_code=400, detail="No images generated yet. Cannot export.") + + base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + project_static_dir = os.path.join(base_dir, "static", project_id) + export_dir = os.path.join(project_static_dir, "export") + + if os.path.exists(export_dir): + shutil.rmtree(export_dir) + os.makedirs(export_dir) + + # Export Characters + chars_dir = os.path.join(export_dir, "characters") + os.makedirs(chars_dir) + for char in project.characters: + if char.image_url: + # Resolve absolute path from relative URL + # URL: /static/{project_id}/characters/xxx.png + # Path: backend/static/{project_id}/characters/xxx.png + # We can construct it directly if we know the structure, but let's parse url + rel_path = char.image_url.lstrip("/") # static/... + local_path = os.path.join(base_dir, rel_path.replace("/", os.sep)) + + if os.path.exists(local_path): + shutil.copy(local_path, os.path.join(chars_dir, f"{char.name}.png")) + + panels_dir = os.path.join(export_dir, "panels") + if split_images: + os.makedirs(panels_dir) + + # Sort items + items = sorted(project.storyboard_items, key=lambda x: x.sequence) + + for item in items: + if item.image_url: + rel_path = item.image_url.lstrip("/") + local_path = os.path.join(base_dir, rel_path.replace("/", os.sep)) + + if os.path.exists(local_path): + shutil.copy(local_path, os.path.join(export_dir, f"comic_part_{item.sequence}.png")) + + if split_images: + with open(local_path, "rb") as f: + img_bytes = f.read() + + try: + panels = split_comic_page(img_bytes) + for idx, panel_bytes in enumerate(panels): + p_name = f"panel_{item.sequence}_{idx+1}.png" + with open(os.path.join(panels_dir, p_name), "wb") as f: + f.write(panel_bytes) + except Exception as e: + print(f"Failed to split panel {item.id}: {e}") + + zip_path_base = os.path.join(project_static_dir, "export_archive") + shutil.make_archive(zip_path_base, 'zip', export_dir) + + return {"download_url": f"/static/{project_id}/export_archive.zip"} diff --git a/backend/app/routers/generation.py b/backend/app/routers/generation.py new file mode 100644 index 0000000..ad6db0f --- /dev/null +++ b/backend/app/routers/generation.py @@ -0,0 +1,824 @@ +from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks +from sqlmodel import Session +from app.core.database import get_session +from app.models.models import Project, Character, StoryboardItem, Task, ImageHistory +from app.services.ai_service import AIService +from app.services.consistency_service import ConsistencyService +from app.utils.json_utils import extract_json_blocks +from app.cruds import crud_project +import os +import uuid +import json +import traceback + +import logging +import sys + +# Configure logging +logging.basicConfig(level=logging.INFO, stream=sys.stdout) +logger = logging.getLogger(__name__) + +def save_generated_image(session, project_id, entity_type, entity_id, image_bytes): + base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + project_static_dir = os.path.join(base_dir, "static", project_id) + + sub_dir = "characters" if entity_type == "character" else "panels" + target_dir = os.path.join(project_static_dir, sub_dir) + + if not os.path.exists(target_dir): + os.makedirs(target_dir) + + filename = f"{entity_type}_{entity_id}_{uuid.uuid4().hex[:8]}.png" + filepath = os.path.join(target_dir, filename) + + with open(filepath, "wb") as f: + f.write(image_bytes) + + relative_url = f"/static/{project_id}/{sub_dir}/{filename}" + + # Save History + history = ImageHistory( + project_id=project_id, + entity_type=entity_type, + entity_id=entity_id, + image_url=relative_url + ) + session.add(history) + + return relative_url + +router = APIRouter() + +from app.core.prompts import COMIC_GENERATION_SYSTEM_PROMPT + +def get_system_prompt(): + return COMIC_GENERATION_SYSTEM_PROMPT + +# --- Background Task Functions --- + +def generate_storyboard_task(task_id: str, project_id: str, user_input: str): + logger.info(f"Starting storyboard generation task: {task_id} for project: {project_id}") + # We need a fresh session for the background task + from app.core.database import engine + with Session(engine) as session: + task = session.get(Task, task_id) + if not task: + logger.error(f"Task {task_id} not found") + return + + task.status = "processing" + session.add(task) + session.commit() + + try: + project = crud_project.get_project(session, project_id) + logger.info(f"Project found: {project.title}") + + # Save User Input (Persist it) + project.story_input = user_input + session.add(project) + session.commit() + + ai = AIService(session) + system_prompt = get_system_prompt() + + # Construct Final Prompt with Preferences + final_prompt = user_input + + # --- Replace Placeholders in System Prompt --- + system_prompt = get_system_prompt() + + # Defaults + style = "Standard" + if project.theme: style = project.theme + + lang = "English" + if project.language: + lang_map = {"zh-CN": "Simplified Chinese", "en-US": "English", "ja-JP": "Japanese"} + lang = lang_map.get(project.language, project.language) + + # Inject into System Prompt + system_prompt = system_prompt.replace("{User Specified Style}", style) + # We could also inject language if we had a placeholder, but style is the main one failing. + # Let's add language instruction to system prompt dynamically if needed, + # or rely on the "Language & Format" section in prompt which says "Use user input language". + + # --- Construct User Prompt --- + final_prompt = user_input + + # Append preferences as normal requirements + prefs = [] + if project.theme: prefs.append(f"Theme: {project.theme}") + if project.language: prefs.append(f"Language: {project.language}") + if project.panel_count: prefs.append(f"Estimated Panel Count: {project.panel_count}") + if project.aspect_ratio: prefs.append(f"Aspect Ratio: {project.aspect_ratio}") + + if prefs: + final_prompt += "\n\nRequirements:\n" + "\n".join(prefs) + + logger.info("Calling AI service for storyboard generation...") + generated_text = ai.generate_storyboard(system_prompt, final_prompt) + + # --- Save Generated Text to Temp File --- + import time + base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + temp_dir = os.path.join(base_dir, "static", project_id, "temp") + if not os.path.exists(temp_dir): + os.makedirs(temp_dir) + + timestamp = int(time.time()) + temp_file = os.path.join(temp_dir, f"ai_output_{timestamp}.txt") + try: + with open(temp_file, "w", encoding="utf-8") as f: + f.write(generated_text) + logger.info(f"Saved raw AI output to {temp_file}") + except Exception as e: + logger.error(f"Failed to save temp AI output: {e}") + # ---------------------------------------- + + logger.info("AI generation complete. Extracting JSON blocks...") + json_blocks = extract_json_blocks(generated_text) + + char_blocks = [b for b in json_blocks if b.get("type") == "character_sheet"] + story_blocks = [b for b in json_blocks if b.get("type") == "storyboard"] + + if not story_blocks: + story_blocks = [b for b in json_blocks if b.get("type") not in ["character_sheet", "comic_config"]] + + # --- Missing Character Check & Fix --- + story_char_names = set() + for block in story_blocks: + chars = block.get("characters", []) + if isinstance(chars, str): + story_char_names.add(chars) + elif isinstance(chars, list): + for c in chars: + if isinstance(c, str): story_char_names.add(c) + elif isinstance(c, dict): story_char_names.add(c.get("name", "")) + + generated_char_names = set(b.get("name") for b in char_blocks if b.get("name")) + + # Simple fuzzy matching or direct check + missing_chars = [] + for name in story_char_names: + # Check if name is contained in any generated char name (e.g. "Xiao Ming" vs "Ming") + found = False + for g_name in generated_char_names: + if name in g_name or g_name in name: + found = True + break + if not found and name and len(name) > 1: # Ignore single chars or empty + missing_chars.append(name) + + if missing_chars: + logger.info(f"Detected missing characters: {missing_chars}. Requesting AI to generate them...") + fix_prompt = f"You missed generating character sheets for the following characters that appeared in the storyboard: {', '.join(missing_chars)}. Please generate 'character_sheet' JSON blocks for them now. Do not generate anything else." + + try: + fix_response = ai.generate_storyboard(system_prompt, fix_prompt) # Re-use generate method + fix_blocks = extract_json_blocks(fix_response) + new_chars = [b for b in fix_blocks if b.get("type") == "character_sheet"] + if new_chars: + logger.info(f"Successfully generated {len(new_chars)} missing characters.") + char_blocks.extend(new_chars) + except Exception as e: + logger.error(f"Failed to generate missing characters: {e}") + + config_block = next((b for b in json_blocks if b.get("type") == "comic_config"), None) + + # If AI didn't return config, create one from project prefs + if not config_block and (project.aspect_ratio or project.language): + config_block = { + "type": "comic_config", + "style": "Standard", # Default + "aspect_ratio": project.aspect_ratio or "16:9", + "language": project.language or "en-US" + } + + if config_block: + crud_project.create_global_config(session, project_id, config_block) + + # --- Enforce Consistency: Update meta_info for all blocks --- + # Re-read global config if we just created/updated it + # Or use the config_block we have + + if not config_block: + # Try to fetch existing + # But we just generated it. If None, we create a default one above. + # Let's use the one we have. + pass + + if config_block: + global_style = config_block.get("style", "") + global_aspect = config_block.get("aspect_ratio", "16:9") + global_lang = config_block.get("language", "en-US") + + # Update Character Sheets + for char in char_blocks: + char["meta_info"] = char.get("meta_info", {}) + char["meta_info"]["language"] = global_lang + char["meta_info"]["style"] = global_style + + # Remove top-level redundant keys if they exist to avoid confusion + char.pop("language", None) + char.pop("style", None) + + # Update Storyboard Items + for block in story_blocks: + meta = block.get("meta_info", {}) + meta["style"] = global_style + meta["language"] = global_lang + meta["aspect_ratio"] = global_aspect + + # Also inject specific style configs if present + if "bubble_style" in config_block: meta["bubble_style"] = config_block["bubble_style"] + if "narration_style" in config_block: meta["narration_style"] = config_block["narration_style"] + if "border_style" in config_block: meta["border_style"] = config_block["border_style"] + if "gutter_style" in config_block: meta["gutter_style"] = config_block["gutter_style"] + if "layout_settings" in config_block: meta["layout_settings"] = config_block["layout_settings"] + + block["meta_info"] = meta + + # Save to DB + crud_project.save_characters(session, project_id, char_blocks) + crud_project.save_storyboard(session, project_id, story_blocks) + + # Consistency + consistency = ConsistencyService(session) + consistency.normalize_project(project_id) + + task.status = "completed" + task.result = {"blocks_found": len(json_blocks)} + task.progress = 100 + session.add(task) + session.commit() + logger.info(f"Storyboard task {task_id} completed successfully.") + + except Exception as e: + logger.error(f"Storyboard task {task_id} failed: {e}") + traceback.print_exc() + task.status = "failed" + task.message = str(e) + session.add(task) + session.commit() + +def generate_all_images_task(task_id: str, project_id: str): + logger.info(f"Starting batch image generation task: {task_id} for project: {project_id}") + from app.core.database import engine + with Session(engine) as session: + task = session.get(Task, task_id) + if not task: + logger.error(f"Task {task_id} not found") + return + + task.status = "processing" + session.add(task) + session.commit() + + try: + project = session.get(Project, project_id) + ai = AIService(session) + + base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + static_root = os.path.join(base_dir, "static") + if not os.path.exists(static_root): os.makedirs(static_root) + + # 1. Generate Characters + total_chars = len(project.characters) + logger.info(f"Generating {total_chars} characters...") + for i, char in enumerate(project.characters): + if char.image_url: + logger.info(f"Character {char.name} already has image, skipping.") + continue + + logger.info(f"Generating image for character: {char.name}") + json_prompt = json.dumps(char.data, ensure_ascii=False, indent=2) + json_prompt += "\n\n generate a character design sheet with 4 panels: front view, side view, clothing details, accessories." + + try: + image_bytes = ai.generate_image(json_prompt) + relative_url = save_generated_image(session, project_id, "character", char.id, image_bytes) + char.image_url = relative_url + session.add(char) + session.commit() + logger.info(f"Character {char.name} generated successfully.") + except Exception as e: + logger.error(f"Failed to generate char {char.id}: {e}") + print(f"Failed to generate char {char.id}: {e}") + + # Update task progress (Characters are 20% of work?) + # Let's simple split: chars + storyboard items + + # 2. Generate Storyboard Items (Sequential) + # Re-fetch items to ensure order + items = sorted(project.storyboard_items, key=lambda x: x.sequence) + total_items = len(items) + logger.info(f"Generating {total_items} storyboard panels...") + + generated_history = [] # Keep track of generated images for context + + # Populate history with existing images + # Actually comic_generator logic builds history as it goes. + # We should probably load existing images into history if we are resuming? + # For "one click", let's assume we scan all items. + + for i, item in enumerate(items): + task.progress = int((i / total_items) * 100) + session.add(task) + session.commit() + + if item.image_url: + # Add to history + filename = os.path.basename(item.image_url) + # We need to find where it is stored. + # Assuming standard structure + # We need absolute path for history + # item.image_url is like /static/{project_id}/panels/{filename} + rel_path = item.image_url.lstrip("/") + abs_path = os.path.join(base_dir, rel_path.replace("/", os.sep)) + + if os.path.exists(abs_path): + generated_history.append(abs_path) + continue + + logger.info(f"Generating panel {item.sequence}...") + + # Prepare Context + context_images = [] + + # a) Character Sheets + char_names = item.data.get("characters", []) + if isinstance(char_names, str): char_names = [char_names] + elif isinstance(char_names, list): + names = [] + for c in char_names: + if isinstance(c, dict): names.append(c.get("name", "")) + elif isinstance(c, str): names.append(c) + char_names = names + + for name in char_names: + for p_char in project.characters: + if p_char.image_url and (p_char.name in name or name in p_char.name): + # Resolve absolute path for char image + rel_path = p_char.image_url.lstrip("/") + abs_path = os.path.join(base_dir, rel_path.replace("/", os.sep)) + if os.path.exists(abs_path) and abs_path not in context_images: + context_images.append(abs_path) + + # b) Previous History (Last 3 logic) + if len(generated_history) >= 3: + selected = [generated_history[0]] + generated_history[-2:] + else: + selected = generated_history + + for path in selected: + if path not in context_images: + context_images.append(path) + + # Generate + json_prompt = json.dumps(item.data, ensure_ascii=False, indent=2) + json_prompt += "\n\n use json block as user input prompt to generate 2*2 grid comic image." + + try: + image_bytes = ai.generate_image(json_prompt, context_images) + relative_url = save_generated_image(session, project_id, "panel", item.id, image_bytes) + + item.image_url = relative_url + session.add(item) + session.commit() + + # Add to history (absolute path for context usage) + # We need absolute path for next context + # save_generated_image returns relative /static/... + # Reconstruct absolute path + # strip leading / + abs_path = os.path.join(base_dir, relative_url.lstrip("/").replace("/", os.sep)) + generated_history.append(abs_path) + logger.info(f"Panel {item.sequence} generated successfully.") + + except Exception as e: + logger.error(f"Failed to generate panel {item.id}: {e}") + print(f"Failed to generate panel {item.id}: {e}") + + task.status = "completed" + task.progress = 100 + session.add(task) + session.commit() + logger.info(f"Batch generation task {task_id} completed successfully.") + + except Exception as e: + logger.error(f"Batch generation task {task_id} failed: {e}") + traceback.print_exc() + task.status = "failed" + task.message = str(e) + session.add(task) + session.commit() + +def generate_all_characters_task(task_id: str, project_id: str): + logger.info(f"Starting batch character generation task: {task_id} for project: {project_id}") + from app.core.database import engine + with Session(engine) as session: + task = session.get(Task, task_id) + if not task: + logger.error(f"Task {task_id} not found") + return + + task.status = "processing" + session.add(task) + session.commit() + + try: + project = session.get(Project, project_id) + ai = AIService(session) + + total_chars = len(project.characters) + logger.info(f"Generating {total_chars} characters...") + + for i, char in enumerate(project.characters): + if char.image_url: + logger.info(f"Character {char.name} already has image, skipping.") + continue + + logger.info(f"Generating image for character: {char.name}") + + # Construct Natural Language Prompt from JSON + data = char.data + meta = data.get("meta_info", {}) + name = data.get("name", "Unknown") + role = meta.get("role", "") + age = meta.get("age", "") + personality = data.get("personality", "") or meta.get("personality", "") + style = meta.get("style", "") + + # Build Description from panels + description = "" + panels = data.get("design_panels", []) + for p in panels: + view = p.get("view", "") + desc = p.get("description", "") + description += f"- {view}: {desc}\n" + + prompt = f"""Character Design Request: +Name: {name} +Role: {role} +Age: {age} +Personality: {personality} +Style: {style} + +Visual Description: +{description} + +Task: Generate a high-quality character reference sheet (Character Design) based on the above description. +Include Front View, Side View, and detailed clothing/accessories. +Ensure the character's expression and pose reflect their personality: {personality}. +""" + + try: + image_bytes = ai.generate_image(prompt) + relative_url = save_generated_image(session, project_id, "character", char.id, image_bytes) + char.image_url = relative_url + session.add(char) + session.commit() + logger.info(f"Character {char.name} generated successfully.") + except Exception as e: + logger.error(f"Failed to generate char {char.id}: {e}") + + # Update progress + progress = int(((i + 1) / total_chars) * 100) + task.progress = progress + session.add(task) + session.commit() + + task.status = "completed" + task.progress = 100 + session.add(task) + session.commit() + logger.info(f"Batch character generation task {task_id} completed successfully.") + + except Exception as e: + logger.error(f"Batch character generation task {task_id} failed: {e}") + traceback.print_exc() + task.status = "failed" + task.message = str(e) + session.add(task) + session.commit() + +def generate_character_task(task_id: str, character_id: int): + logger.info(f"Starting character generation task: {task_id} for char: {character_id}") + from app.core.database import engine + with Session(engine) as session: + task = session.get(Task, task_id) + if not task: + logger.error(f"Task {task_id} not found") + return + + task.status = "processing" + session.add(task) + session.commit() + + try: + char = session.get(Character, character_id) + if not char: + raise ValueError("Character not found") + + ai = AIService(session) + + # Construct Natural Language Prompt from JSON + data = char.data + meta = data.get("meta_info", {}) + name = data.get("name", "Unknown") + role = meta.get("role", "") + age = meta.get("age", "") + personality = data.get("personality", "") or meta.get("personality", "") + style = meta.get("style", "") + + # Build Description from panels + description = "" + panels = data.get("design_panels", []) + for p in panels: + view = p.get("view", "") + desc = p.get("description", "") + description += f"- {view}: {desc}\n" + + prompt = f"""Character Design Request: +Name: {name} +Role: {role} +Age: {age} +Personality: {personality} +Style: {style} + +Visual Description: +{description} + +Task: Generate a high-quality character reference sheet (Character Design) based on the above description. +Include Front View, Side View, and detailed clothing/accessories. +Ensure the character's expression and pose reflect their personality: {personality}. +""" + + logger.info(f"Calling AI service for character {char.name}...") + image_bytes = ai.generate_image(prompt) + + relative_url = save_generated_image(session, char.project_id, "character", char.id, image_bytes) + + char.image_url = relative_url + session.add(char) + + task.status = "completed" + task.progress = 100 + session.add(task) + session.commit() + logger.info(f"Character task {task_id} completed successfully.") + + except Exception as e: + logger.error(f"Character task {task_id} failed: {e}") + traceback.print_exc() + task.status = "failed" + task.message = str(e) + session.add(task) + session.commit() + +# --- Endpoints --- + +from pydantic import BaseModel + +class StoryboardRequest(BaseModel): + user_input: str + +@router.post("/storyboard/{project_id}") +def generate_storyboard( + project_id: str, + request: StoryboardRequest, + background_tasks: BackgroundTasks, + session: Session = Depends(get_session) +): + user_input = request.user_input + logger.info(f"Received request to generate storyboard for project {project_id}") + project = crud_project.get_project(session, project_id) + if not project: + logger.error(f"Project {project_id} not found") + raise HTTPException(status_code=404, detail="Project not found") + + # Save User Input Immediately + project.story_input = user_input + session.add(project) + session.commit() + + # Create Task + task = Task( + type="storyboard", + status="pending", + project_id=project_id, + name="Generate Storyboard", + description=f"Generating storyboard based on user input..." + ) + session.add(task) + session.commit() + session.refresh(task) + logger.info(f"Task created: {task.id}") + + background_tasks.add_task(generate_storyboard_task, task.id, project_id, user_input) + + return {"task_id": task.id} + +@router.post("/all-images/{project_id}") +def generate_all_images( + project_id: str, + background_tasks: BackgroundTasks, + session: Session = Depends(get_session) +): + logger.info(f"Received request to generate all images for project {project_id}") + project = crud_project.get_project(session, project_id) + if not project: + logger.error(f"Project {project_id} not found") + raise HTTPException(status_code=404, detail="Project not found") + + task = Task( + type="image_generation", + status="pending", + project_id=project_id, + name="Batch Generate Images", + description="Generating all storyboard images" + ) + session.add(task) + session.commit() + session.refresh(task) + logger.info(f"Task created: {task.id}") + + background_tasks.add_task(generate_all_images_task, task.id, project_id) + + return {"task_id": task.id} + +# Keep individual endpoints for manual control, but maybe make them async too? +# User asked for "Back task" for "generation". Usually implies the bulk actions. +# Single panel generation is usually fast enough (5-10s), but can be async if desired. +# For now, let's keep single endpoints sync for immediate feedback, or make them async if user insists "All generation". +# The prompt says "Generate text/image takes long time". +# Let's keep single endpoints sync for simplicity of interaction (user waits 5s is ok), +# but "One Click" and "Storyboard" are definitely async. + +@router.post("/all-characters/{project_id}") +def generate_all_characters( + project_id: str, + background_tasks: BackgroundTasks, + session: Session = Depends(get_session) +): + logger.info(f"Received request to generate all characters for project {project_id}") + project = crud_project.get_project(session, project_id) + if not project: + logger.error(f"Project {project_id} not found") + raise HTTPException(status_code=404, detail="Project not found") + + task = Task( + type="character_generation", + status="pending", + project_id=project_id, + name="Batch Generate Characters", + description="Generating all character design sheets" + ) + session.add(task) + session.commit() + session.refresh(task) + logger.info(f"Task created: {task.id}") + + background_tasks.add_task(generate_all_characters_task, task.id, project_id) + + return {"task_id": task.id} + +@router.post("/character/{character_id}") +def generate_character( + character_id: int, + background_tasks: BackgroundTasks, + session: Session = Depends(get_session) +): + char = session.get(Character, character_id) + if not char: + raise HTTPException(status_code=404, detail="Character not found") + + task = Task( + type="character_generation", + status="pending", + project_id=char.project_id, + name=f"Draw Character: {char.name}", + description=f"Drawing design sheet for character {char.name}" + ) + session.add(task) + session.commit() + session.refresh(task) + + background_tasks.add_task(generate_character_task, task.id, character_id) + + return {"task_id": task.id} + +def generate_panel_task(task_id: str, item_id: int): + logger.info(f"Starting panel generation task: {task_id} for item: {item_id}") + from app.core.database import engine + with Session(engine) as session: + task = session.get(Task, task_id) + if not task: + logger.error(f"Task {task_id} not found") + return + + task.status = "processing" + session.add(task) + session.commit() + + try: + item = session.get(StoryboardItem, item_id) + if not item: + raise ValueError("Storyboard item not found") + + project = item.project + ai = AIService(session) + json_prompt = json.dumps(item.data, ensure_ascii=False, indent=2) + json_prompt += "\n\n use json block as user input prompt to generate 2*2 grid comic image." + + context_images = [] + base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + # 1. Find Character Images + char_names = item.data.get("characters", []) + if isinstance(char_names, str): char_names = [char_names] + elif isinstance(char_names, list): + names = [] + for c in char_names: + if isinstance(c, dict): names.append(c.get("name", "")) + elif isinstance(c, str): names.append(c) + char_names = names + + if project.characters: + for name in char_names: + for p_char in project.characters: + if p_char.image_url and (p_char.name in name or name in p_char.name): + rel_path = p_char.image_url.lstrip("/") + abs_path = os.path.join(base_dir, rel_path.replace("/", os.sep)) + if os.path.exists(abs_path) and abs_path not in context_images: + context_images.append(abs_path) + + # 2. Previous Panels + prev_items = sorted([i for i in project.storyboard_items if i.sequence < item.sequence and i.image_url], key=lambda x: x.sequence) + if prev_items: + selected = [] + if len(prev_items) >= 3: + selected = [prev_items[0]] + prev_items[-2:] + else: + selected = prev_items + + for prev in selected: + rel_path = prev.image_url.lstrip("/") + abs_path = os.path.join(base_dir, rel_path.replace("/", os.sep)) + if os.path.exists(abs_path) and abs_path not in context_images: + context_images.append(abs_path) + + # Style Consistency + meta_style = item.data.get("meta_info", {}).get("style", "") + if not meta_style and project.global_config: + meta_style = project.global_config.data.get("style", "") + + if meta_style: + json_prompt += f"\n\nStyle Consistency Requirement: {meta_style}. Ensure the visual style matches the provided context images." + + logger.info(f"Calling AI service for panel {item.sequence}...") + image_bytes = ai.generate_image(json_prompt, context_images) + + relative_url = save_generated_image(session, project.id, "panel", item.id, image_bytes) + item.image_url = relative_url + session.add(item) + + task.status = "completed" + task.progress = 100 + session.add(task) + session.commit() + logger.info(f"Panel task {task_id} completed successfully.") + + except Exception as e: + logger.error(f"Panel task {task_id} failed: {e}") + traceback.print_exc() + task.status = "failed" + task.message = str(e) + session.add(task) + session.commit() + +@router.post("/panel/{item_id}") +def generate_panel( + item_id: int, + background_tasks: BackgroundTasks, + session: Session = Depends(get_session) +): + item = session.get(StoryboardItem, item_id) + if not item: + raise HTTPException(status_code=404, detail="Storyboard item not found") + + task = Task( + type="image_generation", + status="pending", + project_id=item.project_id, + name=f"Draw Panel: #{item.sequence}", + description=f"Drawing panel {item.sequence}" + ) + session.add(task) + session.commit() + session.refresh(task) + + background_tasks.add_task(generate_panel_task, task.id, item_id) + + return {"task_id": task.id} diff --git a/backend/app/routers/history.py b/backend/app/routers/history.py new file mode 100644 index 0000000..568fc53 --- /dev/null +++ b/backend/app/routers/history.py @@ -0,0 +1,38 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session, select +from app.core.database import get_session +from app.models.models import ImageHistory, Character, StoryboardItem + +router = APIRouter() + +@router.get("/{entity_type}/{entity_id}") +def get_history(entity_type: str, entity_id: int, session: Session = Depends(get_session)): + # entity_type: 'character' or 'panel' + statement = select(ImageHistory).where( + ImageHistory.entity_type == entity_type, + ImageHistory.entity_id == entity_id + ).order_by(ImageHistory.created_at.desc()) + history = session.exec(statement).all() + return history + +@router.post("/select/{history_id}") +def select_image(history_id: int, session: Session = Depends(get_session)): + history = session.get(ImageHistory, history_id) + if not history: + raise HTTPException(status_code=404, detail="History item not found") + + if history.entity_type == "character": + entity = session.get(Character, history.entity_id) + elif history.entity_type == "panel": + entity = session.get(StoryboardItem, history.entity_id) + else: + raise HTTPException(status_code=400, detail="Unknown entity type") + + if not entity: + raise HTTPException(status_code=404, detail="Entity not found") + + entity.image_url = history.image_url + session.add(entity) + session.commit() + session.refresh(entity) + return {"status": "success", "image_url": entity.image_url} diff --git a/backend/app/routers/projects.py b/backend/app/routers/projects.py new file mode 100644 index 0000000..34bd485 --- /dev/null +++ b/backend/app/routers/projects.py @@ -0,0 +1,205 @@ +from fastapi import APIRouter, Depends, HTTPException, Body +from sqlmodel import Session +from typing import List, Dict +from pydantic import BaseModel +from app.core.database import get_session +from app.models.models import Project, GlobalConfig, Character, StoryboardItem +from app.schemas.schemas import ProjectCreate, ProjectUpdate, ProjectRead +from app.cruds import crud_project +from app.services.consistency_service import ConsistencyService + +router = APIRouter() + +@router.post("/", response_model=Project) +def create_project(project_in: ProjectCreate, session: Session = Depends(get_session)): + return crud_project.create_project(session, project_in) + +@router.get("/", response_model=List[Project]) +def read_projects(skip: int = 0, limit: int = 100, session: Session = Depends(get_session)): + return crud_project.get_projects(session, skip, limit) + +@router.get("/{project_id}", response_model=ProjectRead) +def read_project(project_id: str, session: Session = Depends(get_session)): + project = crud_project.get_project(session, project_id) + if not project: + raise HTTPException(status_code=404, detail="Project not found") + return project + +@router.put("/{project_id}", response_model=Project) +def update_project(project_id: str, project_in: ProjectUpdate, session: Session = Depends(get_session)): + project = crud_project.get_project(session, project_id) + if not project: + raise HTTPException(status_code=404, detail="Project not found") + return crud_project.update_project(session, project, project_in) + +@router.delete("/{project_id}") +def delete_project(project_id: str, session: Session = Depends(get_session)): + project = crud_project.get_project(session, project_id) + if not project: + raise HTTPException(status_code=404, detail="Project not found") + crud_project.delete_project(session, project) + return {"ok": True} + +# --- Data Management Endpoints --- + +@router.put("/{project_id}/global_config") +def update_global_config(project_id: str, data: Dict = Body(...), session: Session = Depends(get_session)): + project = crud_project.get_project(session, project_id) + if not project: + raise HTTPException(status_code=404, detail="Project not found") + + # 1. Update Project level fields first + # This ensures consistency between project.theme/language and global_config + if "language" in data: + project.language = data["language"] + if "style" in data: + project.theme = data["style"] + if "aspect_ratio" in data: + project.aspect_ratio = data["aspect_ratio"] + + session.add(project) + session.commit() + session.refresh(project) + + # 2. Update GlobalConfig in DB + config = crud_project.create_global_config(session, project_id, data) + + # 3. Trigger consistency check (Propagate to all items) + consistency = ConsistencyService(session) + consistency.normalize_project(project_id) + + return config + +@router.put("/{project_id}/characters/{char_id}") +def update_character(project_id: str, char_id: int, data: Dict = Body(...), session: Session = Depends(get_session)): + char = session.get(Character, char_id) + if not char: + raise HTTPException(status_code=404, detail="Character not found") + + char.data = data + session.add(char) + session.commit() + session.refresh(char) + + # We might want to trigger consistency here too if character style changes, + # but primarily it's driven by global config. + return char + +@router.put("/{project_id}/storyboard/{item_id}") +def update_storyboard_item(project_id: str, item_id: int, data: Dict = Body(...), session: Session = Depends(get_session)): + item = session.get(StoryboardItem, item_id) + if not item: + raise HTTPException(status_code=404, detail="Storyboard Item not found") + + item.data = data + session.add(item) + session.commit() + session.refresh(item) + return item + +@router.delete("/{project_id}/characters/{char_id}") +def delete_character(project_id: str, char_id: int, session: Session = Depends(get_session)): + char = session.get(Character, char_id) + if not char: + raise HTTPException(status_code=404, detail="Character not found") + + session.delete(char) + session.commit() + return {"ok": True} + +class MergeCharacterRequest(BaseModel): + target_char_id: int + source_char_ids: List[int] + +@router.post("/{project_id}/characters/merge") +def merge_characters( + project_id: str, + request: MergeCharacterRequest, + session: Session = Depends(get_session) +): + target_char = session.get(Character, request.target_char_id) + if not target_char: + raise HTTPException(status_code=404, detail="Target character not found") + + source_chars = [] + for cid in request.source_char_ids: + c = session.get(Character, cid) + if c: + source_chars.append(c) + + if not source_chars: + raise HTTPException(status_code=400, detail="No valid source characters found") + + target_name = target_char.name + source_names = [c.name for c in source_chars] + + # 1. Update Storyboard Items + # We need to scan all items and replace source names with target name + project = session.get(Project, project_id) + if project.storyboard_items: + for item in project.storyboard_items: + data = item.data + # 'characters' field in storyboard item data + # It can be a list of strings, or list of dicts with 'name' key, or a single string + chars = data.get("characters", []) + + new_chars = [] + modified = False + + # Helper to normalize input to list + char_list = [] + if isinstance(chars, str): char_list = [chars] + elif isinstance(chars, list): char_list = chars + + for c_entry in char_list: + c_name = "" + if isinstance(c_entry, str): c_name = c_entry + elif isinstance(c_entry, dict): c_name = c_entry.get("name", "") + + # Check if this name matches any source name + # Fuzzy match or exact? Let's do exact or containment for safety + # User said "Ma Laoguanjia" vs "Ma Guanjia". + # Ideally we replace if it matches one of the source characters' name EXACTLY or close enough? + # Since we selected source characters by ID, we know their names. + # Let's replace if the name in storyboard matches a source character name. + + is_source = False + for src_name in source_names: + if src_name == c_name: + is_source = True + break + + if is_source: + # Replace with target name + # If entry was dict, update name field? Or just use string? + # Let's keep format. + if isinstance(c_entry, str): + new_chars.append(target_name) + elif isinstance(c_entry, dict): + c_entry['name'] = target_name + new_chars.append(c_entry) + modified = True + else: + new_chars.append(c_entry) + + if modified: + # Deduplicate if target name already existed? + # Simple dedup for strings + final_chars = [] + seen = set() + for c in new_chars: + n = c if isinstance(c, str) else c.get("name", "") + if n not in seen: + final_chars.append(c) + seen.add(n) + + data['characters'] = final_chars + item.data = data + session.add(item) + + # 2. Delete Source Characters + for c in source_chars: + session.delete(c) + + session.commit() + return {"ok": True, "merged_count": len(source_chars)} diff --git a/backend/app/routers/tasks.py b/backend/app/routers/tasks.py new file mode 100644 index 0000000..8494f54 --- /dev/null +++ b/backend/app/routers/tasks.py @@ -0,0 +1,23 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session +from app.core.database import get_session +from app.models.models import Task +from app.schemas.schemas import TaskRead + +router = APIRouter() + +@router.get("/{task_id}", response_model=TaskRead) +def get_task_status(task_id: str, session: Session = Depends(get_session)): + task = session.get(Task, task_id) + if not task: + raise HTTPException(status_code=404, detail="Task not found") + return task + +@router.get("/project/{project_id}", response_model=list[TaskRead]) +def get_project_tasks(project_id: str, session: Session = Depends(get_session)): + from sqlmodel import select + statement = select(Task).where(Task.project_id == project_id).order_by(Task.created_at.desc()) + tasks = session.exec(statement).all() + # Filter only recent or active tasks if list is too long? + # For now return all, maybe limit 20 + return tasks[:20] diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/schemas/schemas.py b/backend/app/schemas/schemas.py new file mode 100644 index 0000000..8238f2e --- /dev/null +++ b/backend/app/schemas/schemas.py @@ -0,0 +1,59 @@ +from pydantic import BaseModel +from typing import Optional, Dict, List +from datetime import datetime +from app.models.models import ( + ModelConfigBase, ProjectBase, CharacterBase, StoryboardItemBase, GlobalConfigBase, TaskBase, + ModelConfig, Project, Character, StoryboardItem, GlobalConfig, Task +) + +# ModelConfig +class ModelConfigCreate(ModelConfigBase): + pass + +class ModelConfigUpdate(BaseModel): + provider: Optional[str] = None + api_key: Optional[str] = None + base_url: Optional[str] = None + model_name: Optional[str] = None + model_type: Optional[str] = None + is_active: Optional[bool] = None + +# Read Models for nested response +class CharacterRead(CharacterBase): + id: int + project_id: str + +class StoryboardItemRead(StoryboardItemBase): + id: int + project_id: str + +class GlobalConfigRead(GlobalConfigBase): + id: int + project_id: str + +class TaskRead(TaskBase): + id: str + project_id: str + created_at: datetime + updated_at: datetime + +# Project +class ProjectCreate(ProjectBase): + pass + +class ProjectUpdate(BaseModel): + title: Optional[str] = None + description: Optional[str] = None + story_input: Optional[str] = None + theme: Optional[str] = None + language: Optional[str] = None + panel_count: Optional[int] = None + aspect_ratio: Optional[str] = None + +class ProjectRead(ProjectBase): + id: str + created_at: datetime + updated_at: datetime + characters: List[CharacterRead] = [] + storyboard_items: List[StoryboardItemRead] = [] + global_config: Optional[GlobalConfigRead] = None diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/services/ai_service.py b/backend/app/services/ai_service.py new file mode 100644 index 0000000..8133421 --- /dev/null +++ b/backend/app/services/ai_service.py @@ -0,0 +1,92 @@ +import os +import time +from typing import List, Optional +from sqlmodel import Session +from app.models.models import ModelConfig +from google import genai +from PIL import Image +import io + +class AIService: + def __init__(self, session: Session): + self.session = session + + def _get_client(self, model_type: str): + from app.cruds.crud_config import get_active_config + config = get_active_config(self.session, model_type) + + if not config: + raise ValueError(f"No active configuration found for {model_type} model.") + + if config.provider.lower() == "google": + # Initialize Google Client + return genai.Client(api_key=config.api_key), config.model_name + + raise NotImplementedError(f"Provider {config.provider} not supported yet.") + + def generate_storyboard(self, system_prompt: str, user_input: str) -> str: + client, model_name = self._get_client("text") + + full_prompt = f"{system_prompt}\n\nUser Input: {user_input}\n\nPlease generate the full storyboard in JSON format as requested." + + try: + response = client.models.generate_content( + model=model_name, + contents=full_prompt + ) + return response.text + except Exception as e: + print(f"Error generating storyboard: {e}") + raise e + + def generate_image(self, prompt: str, context_images: List[str] = None) -> bytes: + client, model_name = self._get_client("image") + + contents = [prompt] + if context_images: + for img_path in context_images: + if os.path.exists(img_path): + try: + prev_img = Image.open(img_path) + contents.append(prev_img) + except Exception as e: + print(f"Failed to load context image {img_path}: {e}") + else: + # Log missing context image but don't fail, just skip it + print(f"Warning: Context image not found at {img_path}, skipping.") + + # Retry loop + max_retries = 3 + for attempt in range(max_retries): + try: + response = client.models.generate_content( + model=model_name, + contents=contents, + ) + + if response.parts: + for part in response.parts: + if part.inline_data is not None: + image_data = part.inline_data.data + if len(image_data) > 0: + return image_data + else: + print(f"Warning: Received empty image data on attempt {attempt + 1}") + + # Check for text refusal/error + if response.text: + print(f"Model response text (no image): {response.text}") + + print(f"Attempt {attempt + 1} failed: No valid image data found in response.") + if attempt == max_retries - 1: + raise ValueError(f"No image found in response after {max_retries} retries. Last response: {response.text if response.text else 'Empty'}") + + time.sleep(2 ** attempt) + + except Exception as e: + print(f"Error generating image (Attempt {attempt + 1}/{max_retries}): {e}") + if attempt < max_retries - 1: + time.sleep(2 ** attempt) + continue + raise e + return b"" diff --git a/backend/app/services/consistency_service.py b/backend/app/services/consistency_service.py new file mode 100644 index 0000000..cd44454 --- /dev/null +++ b/backend/app/services/consistency_service.py @@ -0,0 +1,181 @@ +import re +import json +import copy +from typing import List, Dict, Any +from sqlmodel import Session, select +from app.models.models import Project, Character, StoryboardItem, GlobalConfig + +class ConsistencyService: + def __init__(self, session: Session): + self.session = session + + def normalize_project(self, project_id: str): + """ + Normalizes the project's data (characters, storyboard) based on the global config. + This mirrors the logic in comic_generator.py. + """ + project = self.session.get(Project, project_id) + if not project: + return + + # Fetch all related data + # Note: relationships are loaded if accessed, but let's be explicit if needed. + # ProjectRead should handle loading, but here we work with ORM objects. + + global_config = project.global_config + characters = project.characters + storyboard_items = sorted(project.storyboard_items, key=lambda x: x.sequence) + + master_style = None + master_meta = {} + + # Priority 0: Comic Config + if global_config and global_config.data: + config_data = global_config.data + master_style = config_data.get("style") + + # Explicitly map all fields we want to sync + master_meta = { + "style": master_style, + "bubble_style": config_data.get("bubble_style"), + "narration_style": config_data.get("narration_style"), + "border_style": config_data.get("border_style"), + "gutter_style": config_data.get("gutter_style"), + "layout_settings": config_data.get("layout_settings"), + "aspect_ratio": config_data.get("aspect_ratio", "16:9"), + "language": config_data.get("language", "English") + } + + # Priority 1: First Character Sheet (if no config style) + if not master_style and characters: + # Try to find one with style + for char in characters: + if char.data.get("style"): + master_style = char.data.get("style") + break + + # Priority 2: First Story Block + if storyboard_items: + first_item = storyboard_items[0] + first_meta = first_item.data.get("meta_info", {}) + if not master_style: + master_style = first_meta.get("style") + + if not master_meta: + master_meta = first_meta.copy() + + # Build Character Registry + known_characters = {} + for char in characters: + full_name = char.name + if not full_name: continue + + keywords = [full_name] + simplified = re.split(r'[(\(]', full_name)[0].strip() + if simplified and simplified != full_name: + keywords.append(simplified) + + for kw in keywords: + if kw: + known_characters[kw] = full_name + + # Apply Normalization + # Even if master_style is None, we might still have layout_settings to sync + # So we check if we have ANY master_meta to apply + if master_meta: + import logging + logger = logging.getLogger(__name__) + logger.info(f"Normalizing project {project_id} with master config: {json.dumps(master_meta, ensure_ascii=False)}") + + # 1. Normalize Characters + for char in characters: + char_data = copy.deepcopy(char.data) + if not isinstance(char_data, dict): + char_data = dict(char_data) + + meta = char_data.get("meta_info", {}) + + # Sync Core Fields + if master_meta.get("style"): meta["style"] = master_meta.get("style") + if master_meta.get("language"): meta["language"] = master_meta.get("language") + if master_meta.get("aspect_ratio"): meta["aspect_ratio"] = master_meta.get("aspect_ratio") + + char_data["meta_info"] = meta + + # Cleanup top-level legacy fields + char_data.pop("style", None) + char_data.pop("language", None) + char_data.pop("Language", None) + + char.data = char_data + self.session.add(char) + + # 2. Normalize Storyboard Items + total_volumes = len(storyboard_items) + + for i, item in enumerate(storyboard_items): + # Use deepcopy to ensure we don't mutate the original object in place before assignment + # and to ensure SQLAlchemy detects the change when we reassign. + item_data = copy.deepcopy(item.data) + if not isinstance(item_data, dict): + item_data = dict(item_data) + + # Check for missing characters + plot_text = json.dumps(item_data.get("plot_breakdown", []), ensure_ascii=False) + current_chars = item_data.get("characters", []) + if isinstance(current_chars, str): + current_chars = [current_chars] + if not isinstance(current_chars, list): + current_chars = [] + + found_missing = [] + for kw, full_name in known_characters.items(): + if kw in plot_text: + is_present = False + for char_name in current_chars: + if kw in char_name or char_name in full_name: + is_present = True + break + if not is_present: + if full_name not in current_chars and full_name not in found_missing: + found_missing.append(full_name) + + if found_missing: + current_chars.extend(found_missing) + item_data["characters"] = current_chars + + # Sync Meta Info + meta = item_data.get("meta_info", {}) + + # We want to preserve existing fields in meta (like volume) but overwrite style configs + # master_meta has style, bubble_style, etc. + + # Force update fields from master_meta even if they exist in meta + # But careful with 'None' values in master_meta (though we constructed it from config) + + for key, value in master_meta.items(): + if key == "volume": continue + + # Special handling for boolean values (like False in show_panel_numbers) + # "if value is not None" is correct for booleans. + # For nested dictionaries (like layout_settings), we should merge to preserve other keys. + + if value is not None: + if isinstance(value, dict) and isinstance(meta.get(key), dict): + meta[key].update(value) + else: + meta[key] = value + + # If value is None but key exists in master_meta keys (explicitly set to null), we might want to unset it? + # But our master_meta construction uses .get() defaults, so None usually means "not in config". + + # Ensure Volume format + meta["volume"] = f"{i+1}/{total_volumes}" + item_data["meta_info"] = meta + + logger.info(f"Updated Item {i+1} meta: {json.dumps(meta, ensure_ascii=False)}") + + item.data = item_data + self.session.add(item) + + self.session.commit() diff --git a/backend/app/services/image_service.py b/backend/app/services/image_service.py new file mode 100644 index 0000000..bb7f05d --- /dev/null +++ b/backend/app/services/image_service.py @@ -0,0 +1,30 @@ +import os +from PIL import Image +import io + +def split_comic_page(image_bytes: bytes) -> list[bytes]: + """Splits a 2x2 grid comic page into 4 individual panel images (bytes).""" + try: + img = Image.open(io.BytesIO(image_bytes)) + width, height = img.size + mid_w = width // 2 + mid_h = height // 2 + + quadrants = [ + (0, 0, mid_w, mid_h), # Top-Left + (mid_w, 0, width, mid_h), # Top-Right + (0, mid_h, mid_w, height), # Bottom-Left + (mid_w, mid_h, width, height) # Bottom-Right + ] + + panels = [] + for box in quadrants: + panel = img.crop(box) + buf = io.BytesIO() + panel.save(buf, format="PNG") + panels.append(buf.getvalue()) + + return panels + except Exception as e: + print(f"Failed to split image: {e}") + return [] diff --git a/backend/app/utils/__init__.py b/backend/app/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/utils/json_utils.py b/backend/app/utils/json_utils.py new file mode 100644 index 0000000..b1c049b --- /dev/null +++ b/backend/app/utils/json_utils.py @@ -0,0 +1,72 @@ +import json +import re +from typing import List, Dict, Any + +def extract_json_blocks(text: str) -> List[Dict[str, Any]]: + """Extracts JSON blocks from the generated text.""" + json_blocks = [] + + # 1. Try to find ```json ... ``` blocks + pattern = r"```(?:json|JSON)?\s*(.*?)\s*```" + matches = re.findall(pattern, text, re.DOTALL) + + # 2. If no code blocks found, or even if found, we should also look for raw JSON objects + # because sometimes models output mixed content. + # But let's stick to code blocks first if they exist. + + if not matches: + # Fallback: Try to find top-level JSON objects/arrays directly in text + # This regex looks for { ... } or [ ... ] that span multiple lines + # It's not perfect but better than nothing + # We search for anything starting with { or [ and ending with } or ] + # non-greedy match might be safer for multiple blocks + raw_pattern = r"(\{[\s\S]*?\}|\[[\s\S]*?\])" + matches = re.findall(raw_pattern, text) + + def repair_json(json_str: str) -> Dict[str, Any]: + """Attempts to repair common JSON errors, specifically missing commas.""" + try: + return json.loads(json_str) + except json.JSONDecodeError: + pass + + repaired_str = json_str + max_attempts = 10 + + for _ in range(max_attempts): + try: + return json.loads(repaired_str) + except json.JSONDecodeError as e: + # print(f"JSON Decode Error at {e.pos}: {e.msg}") + if "Expecting ',' delimiter" in str(e) or "Expecting property name enclosed in double quotes" in str(e): + pos = e.pos + search_str = repaired_str[:pos] + match = re.search(r'([\"}\]0-9])\s*$', search_str) + + if match: + insert_idx = match.end() + repaired_str = repaired_str[:insert_idx] + "," + repaired_str[insert_idx:] + continue + + # If we can't fix it, re-raise + raise e + + return json.loads(repaired_str) + + for match in matches: + if not match.strip(): continue + # Simple heuristic to filter out non-json text blocks that might be caught by raw regex + if not (match.strip().startswith('{') or match.strip().startswith('[')): + continue + + try: + data = repair_json(match) + if isinstance(data, list): + json_blocks.extend(data) + elif isinstance(data, dict): + json_blocks.append(data) + except Exception as e: + print(f"Failed to parse a JSON block: {e}") + # print(f"Block content: {match[:100]}...") + + return json_blocks diff --git a/backend/backend/static/exports/528a62b7-7ccd-4fff-8fa8-d2e96b799e1e.zip b/backend/backend/static/exports/528a62b7-7ccd-4fff-8fa8-d2e96b799e1e.zip new file mode 100644 index 0000000..711a880 Binary files /dev/null and b/backend/backend/static/exports/528a62b7-7ccd-4fff-8fa8-d2e96b799e1e.zip differ diff --git a/backend/backend/static/panel_2_4922dcc5.png b/backend/backend/static/panel_2_4922dcc5.png new file mode 100644 index 0000000..6806364 Binary files /dev/null and b/backend/backend/static/panel_2_4922dcc5.png differ diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..c3c4ae7 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,12 @@ +fastapi +uvicorn +sqlmodel +pydantic +pydantic-settings +python-dotenv +google-genai +openai +requests +alembic +pymysql +aiofiles diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_13_f4504a0f.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_13_f4504a0f.png new file mode 100644 index 0000000..77f9399 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_13_f4504a0f.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_14_ec5cd895.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_14_ec5cd895.png new file mode 100644 index 0000000..a417990 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_14_ec5cd895.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_15_0905aef7.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_15_0905aef7.png new file mode 100644 index 0000000..77531f3 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_15_0905aef7.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_16_402eea29.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_16_402eea29.png new file mode 100644 index 0000000..2a65549 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_16_402eea29.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_16_b27fe666.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_16_b27fe666.png new file mode 100644 index 0000000..d9131de Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_16_b27fe666.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_16_e3075750.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_16_e3075750.png new file mode 100644 index 0000000..c9e4e52 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_16_e3075750.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_17_a51c5598.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_17_a51c5598.png new file mode 100644 index 0000000..d304f3b Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_17_a51c5598.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_18_023586e0.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_18_023586e0.png new file mode 100644 index 0000000..4d11951 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/characters/character_18_023586e0.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/沈天虎.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/沈天虎.png new file mode 100644 index 0000000..d304f3b Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/沈天虎.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/沈翔.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/沈翔.png new file mode 100644 index 0000000..77f9399 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/沈翔.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/白幽幽.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/白幽幽.png new file mode 100644 index 0000000..77531f3 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/白幽幽.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/苏媚瑶.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/苏媚瑶.png new file mode 100644 index 0000000..a417990 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/苏媚瑶.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/薛仙仙.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/薛仙仙.png new file mode 100644 index 0000000..d9131de Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/薛仙仙.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/马管家.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/马管家.png new file mode 100644 index 0000000..4d11951 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/characters/马管家.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_1.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_1.png new file mode 100644 index 0000000..f4c9149 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_1.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_2.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_2.png new file mode 100644 index 0000000..6048419 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_2.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_3.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_3.png new file mode 100644 index 0000000..1ae406f Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_3.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_4.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_4.png new file mode 100644 index 0000000..5fd5c1c Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_4.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_5.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_5.png new file mode 100644 index 0000000..2aeed39 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_5.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_6.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_6.png new file mode 100644 index 0000000..196564f Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_6.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_7.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_7.png new file mode 100644 index 0000000..9209a41 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/comic_part_7.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_1.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_1.png new file mode 100644 index 0000000..e8d9cb3 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_1.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_2.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_2.png new file mode 100644 index 0000000..46878f5 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_2.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_3.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_3.png new file mode 100644 index 0000000..3e29404 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_3.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_4.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_4.png new file mode 100644 index 0000000..ae4c49b Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_1_4.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_1.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_1.png new file mode 100644 index 0000000..e834591 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_1.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_2.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_2.png new file mode 100644 index 0000000..976d682 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_2.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_3.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_3.png new file mode 100644 index 0000000..917e061 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_3.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_4.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_4.png new file mode 100644 index 0000000..b88cac8 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_2_4.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_1.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_1.png new file mode 100644 index 0000000..2d88f3f Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_1.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_2.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_2.png new file mode 100644 index 0000000..4bbf66c Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_2.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_3.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_3.png new file mode 100644 index 0000000..924a143 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_3.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_4.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_4.png new file mode 100644 index 0000000..19e9db0 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_3_4.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_1.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_1.png new file mode 100644 index 0000000..8750a7e Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_1.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_2.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_2.png new file mode 100644 index 0000000..d3a47cb Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_2.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_3.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_3.png new file mode 100644 index 0000000..4a279a9 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_3.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_4.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_4.png new file mode 100644 index 0000000..420d90b Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_4_4.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_1.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_1.png new file mode 100644 index 0000000..5f21486 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_1.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_2.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_2.png new file mode 100644 index 0000000..60c13ff Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_2.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_3.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_3.png new file mode 100644 index 0000000..0051da0 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_3.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_4.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_4.png new file mode 100644 index 0000000..748d0ad Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_5_4.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_1.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_1.png new file mode 100644 index 0000000..f07a898 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_1.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_2.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_2.png new file mode 100644 index 0000000..5e012b1 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_2.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_3.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_3.png new file mode 100644 index 0000000..578e68e Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_3.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_4.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_4.png new file mode 100644 index 0000000..22573d9 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_6_4.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_1.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_1.png new file mode 100644 index 0000000..0c2f63d Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_1.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_2.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_2.png new file mode 100644 index 0000000..8e49e50 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_2.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_3.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_3.png new file mode 100644 index 0000000..1fac8e1 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_3.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_4.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_4.png new file mode 100644 index 0000000..da6a114 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export/panels/panel_7_4.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export_archive.zip b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export_archive.zip new file mode 100644 index 0000000..01d46af Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/export_archive.zip differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_44_ed8e8393.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_44_ed8e8393.png new file mode 100644 index 0000000..f4c9149 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_44_ed8e8393.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_45_38b42547.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_45_38b42547.png new file mode 100644 index 0000000..6048419 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_45_38b42547.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_46_e4fe17fa.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_46_e4fe17fa.png new file mode 100644 index 0000000..1ae406f Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_46_e4fe17fa.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_47_b64b10d2.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_47_b64b10d2.png new file mode 100644 index 0000000..5fd5c1c Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_47_b64b10d2.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_47_b834629a.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_47_b834629a.png new file mode 100644 index 0000000..efe1670 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_47_b834629a.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_48_1786c252.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_48_1786c252.png new file mode 100644 index 0000000..2aeed39 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_48_1786c252.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_48_2ff3d682.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_48_2ff3d682.png new file mode 100644 index 0000000..a8002d5 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_48_2ff3d682.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_48_533d7f47.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_48_533d7f47.png new file mode 100644 index 0000000..5fad000 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_48_533d7f47.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_49_7d6d1f02.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_49_7d6d1f02.png new file mode 100644 index 0000000..3b5b9d6 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_49_7d6d1f02.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_49_b8618879.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_49_b8618879.png new file mode 100644 index 0000000..5291ff0 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_49_b8618879.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_49_e41e20d8.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_49_e41e20d8.png new file mode 100644 index 0000000..196564f Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_49_e41e20d8.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_50_b17608c1.png b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_50_b17608c1.png new file mode 100644 index 0000000..9209a41 Binary files /dev/null and b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/panels/panel_50_b17608c1.png differ diff --git a/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/temp/ai_output_1768625182.txt b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/temp/ai_output_1768625182.txt new file mode 100644 index 0000000..1d80f35 --- /dev/null +++ b/backend/static/37809d42-7ff9-44cb-8da1-fe0096ee787e/temp/ai_output_1768625182.txt @@ -0,0 +1,387 @@ +你好!我是你的漫画编辑兼绘制专家。针对你提供的《傲世丹神》开篇情节,我已将其深度定制为**赛博朋克(Cyberpunk)风格**。 + +在这个世界观中,“卧虎城”是一座霓虹闪烁、阴雨绵绵的巨型科幻都市,武功被诠释为“生物电脉冲”与“纳米进化”,而灵药则是极其稀缺的“高能生物芯片”或“变异菌株”。 + +以下是为您生成的完整漫画拆分方案: + +### 1. 漫画全局配置 (Comic Configuration) + +```json +{ + "type": "comic_config", + "language": "中文简体", + "style": "赛博朋克 (Cyberpunk)", + "bubble_style": { + "shape": "科技感多边形", + "color": "rgba(0, 255, 255, 0.1)", + "font_color": "#FFFFFF", + "stroke_width": "1.5px" + }, + "narration_style": { + "shape": "直角矩形", + "color": "#1a1a1a", + "font_color": "#00FF41", + "opacity": "0.85" + }, + "border_style": { + "width": "2px", + "color": "#333333", + "type": "实线" + }, + "gutter_style": { + "type": "极简黑色分割", + "color": "#000000", + "width": "12px" + }, + "aspect_ratio": "16:9" +} +``` + +--- + +### 2. 角色设定生成 (Character Sheets) + +```json +[ + { + "type": "character_sheet", + "name": "沈翔", + "meta_info": { + "language": "中文简体", + "role": "主角", + "personality": "坚毅、机敏、乐观且带点小坏,对力量充满渴望。", + "age": "16岁", + "relationships": "沈天虎之子,薛仙仙的未婚夫", + "style": "赛博朋克", + "feature": "少年感十足,体格由于超负荷锻炼显得异常精壮,眼神深邃。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + {"view": "Front View (正面)", "description": "黑色短发略显凌乱,穿着露肩的抗磨损战术背心,手臂上有训练留下的旧伤疤。五官精致俊俏。"}, + {"view": "Side View (侧面)", "description": "下颌线锋利,脖颈处贴着一块废弃的神经连接贴片,象征他‘无灵脉’的废柴身份。"}, + {"view": "Clothing (服装)", "description": "工业风格的战术束脚裤,多口袋设计,靴子是耐磨的废土漫步者系列。"}, + {"view": "Accessories (配饰)", "description": "腰间挂着一个老旧的信号采集器(用于采药感应)。"} + ] + }, + { + "type": "character_sheet", + "name": "苏媚瑶", + "meta_info": { + "language": "中文简体", + "role": "主要角色", + "personality": "妖娆魅惑、俏皮、深不可测,实为顶级强者。", + "age": "外表20岁左右", + "relationships": "沈翔的半个师父,白幽幽的师妹", + "style": "赛博朋克", + "feature": "极品尤物,媚到骨子里,色彩斑斓的科技霓虹感。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + {"view": "Front View (正面)", "description": "绝美脸庞,双眸如紫色琉璃。长发中编织着细微的光导纤维,随情绪微微发光。"}, + {"view": "Side View (侧面)", "description": "身材丰满到夸张的S曲线,腰肢极细。"}, + {"view": "Clothing (服装)", "description": "紫色纳米绸缎材质的长裙,开叉极高,装饰有发光的电子回路纹路。"}, + {"view": "Accessories (配饰)", "description": "发丝中藏着一枚微缩维度戒指。"} + ] + }, + { + "type": "character_sheet", + "name": "白幽幽", + "meta_info": { + "language": "中文简体", + "role": "主要角色", + "personality": "冷酷、如万年寒冰、杀气腾腾,实则内心护短。", + "age": "外表22岁左右", + "relationships": "沈翔的半个师父,苏媚瑶的师姐", + "style": "赛博朋克", + "feature": "冷艳高贵,散发着死亡气息的冰山美人。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + {"view": "Front View (正面)", "description": "肤色惨白如羊脂玉,面无表情,眼神透着刺骨的寒芒。"}, + {"view": "Side View (侧面)", "description": "气质孤傲,背部有一道贯穿式的淡蓝色能量回路痕迹。"}, + {"view": "Clothing (服装)", "description": "黑色的紧身绝缘作战服,衬托出高挑修长的冷艳美感。"}, + {"view": "Accessories (配饰)", "description": "瞳孔中偶尔会闪过红色数据流。"} + ] + }, + { + "type": "character_sheet", + "name": "薛仙仙", + "meta_info": { + "language": "中文简体", + "role": "女主角", + "personality": "清纯脱俗、至情至性、天赋卓绝。", + "age": "15岁", + "relationships": "沈翔未婚妻", + "style": "赛博朋克", + "feature": "仙气与科技感的完美结合,大家闺秀,圣洁不染尘埃。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + {"view": "Front View (正面)", "description": "双目如水,容颜绝世。穿着纯白色的高定礼服,边缘带有微弱的悬浮光粒。"}, + {"view": "Side View (侧面)", "description": "身法灵动,颈部佩戴着象征薛家高贵身份的金色量子核心项链。"}, + {"view": "Clothing (服装)", "description": "纯白色科技丝绸长裙,腰间扎着流光丝带,显得柔美而有力。"}, + {"view": "Accessories (配饰)", "description": "精致的金色发饰,实为微型算力加速器。"} + ] + } +] +``` + +--- + +### 3. 故事分镜生成 (Story Storyboard) + +#### 第一组:都市忧郁与宿命序章 (1-4) +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "1/10", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "多边形", "color": "rgba(0, 255, 255, 0.1)", "font_color": "#FFFFFF", "stroke_width": "1px" }, + "narration_style": { "shape": "方框", "color": "#1a1a1a", "font_color": "#00FF41", "opacity": "0.9" }, + "border_style": { "width": "2px", "color": "#444", "type": "实线" }, + "gutter_style": { "type": "标准", "color": "black", "width": "10px" } + }, + "characters": ["沈翔", "马管家"], + "plot_breakdown": [ + { + "panel": 1, + "scene": "卧虎城远景。极高密度的赛博都市,霓虹招牌在乌云下闪烁。", + "action": "一道蓝色的等离子闪电划过天空,映照出下方密密麻麻的贫民窟与上方的贵族悬浮建筑。", + "dialogue": "旁白:这时代,没有灵脉就意味着被系统淘汰。" + }, + { + "panel": 2, + "scene": "狭窄的街道。沈翔站在屋檐下仰望,雨水打湿了他的黑色背心。", + "action": "沈翔握紧拳头,眼神坚毅地看着天空的数据流。", + "dialogue": "沈翔:不能再拖了,我要快点找到好的灵药,否则我难以有翻身的机会。" + }, + { + "panel": 3, + "scene": "马管家撑着一把透明的全息雨伞走近,光头上的辫子格外显眼。", + "action": "马管家看着沈翔,眼神中既有钦佩也有惋惜。", + "dialogue": "马管家:这不是沈翔吗?就要下大雨了,你还要去锻炼?" + }, + { + "panel": 4, + "scene": "中景。沈翔露出坏笑,突然伸手扯住马管家的那条长辫子。", + "action": "沈翔一脸俏皮,仿佛刚才的忧郁从未存在。", + "dialogue": "沈翔:老马,我是去采药!没准能挖到让你的辫子变粗的宝贝!" + } + ] +} +``` + +#### 第二组:父爱与绝望的悬崖 (5-8) +```json +{ + "type": "storyboard", + "meta_info": { "style": "赛博朋克", "language": "中文简体", "volume": "1/10", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "沈天虎", "马管家"], + "plot_breakdown": [ + { + "panel": 5, + "scene": "沈天虎出现在街道尽头。他穿着厚重的黑色皮质风衣,肩膀处有合金装甲。", + "action": "沈天虎迈着沉稳的步子走来,气息强大到雨滴都在他周围弹开。", + "dialogue": "沈天虎:翔儿,天气这样就别去了!" + }, + { + "panel": 6, + "scene": "特写。沈天虎抛出一个散发着蓝色微光的科技小盒子。", + "action": "沈翔稳稳接住盒子,露出习惯性的嘻笑。", + "dialogue": "沈翔:老爹,下雨采药才不用跟人抢。多谢丹药,这样我就不用去偷老马养的那些生物合成鸡了!" + }, + { + "panel": 7, + "scene": "沈天虎看着沈翔远去的背影。雨越下越大,模糊了他的视线。", + "action": "他叹了口气,手心微微闪烁着武者的真气光芒。", + "dialogue": "旁白:作为最有希望继承族长的人,却护不住儿子的前程,这是他最大的遗憾。" + }, + { + "panel": 8, + "scene": "仙魔崖。极其荒凉,死气沉沉的黑雾从深渊涌出,像是某种污染严重的数据死区。", + "action": "赤裸上身的沈翔正徒手攀爬在陡峭的崖壁上,雨水混着黑泥流过他健壮的肌肉。", + "dialogue": "沈翔:物极必反,这死亡之地,一定藏着最好的核心!" + } + ] +} +``` + +#### 第三组:地狱深渊的“神迹” (9-12) +```json +{ + "type": "storyboard", + "meta_info": { "style": "赛博朋克", "language": "中文简体", "volume": "1/10", "aspect_ratio": "16:9" }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 9, + "scene": "俯视视角。沈翔像一只壁虎贴在深不见底的黑色深渊壁上。", + "action": "镜头拉远,展现悬崖的巨大与沈翔的渺小,雨点如子弹般砸在他背上。", + "dialogue": "沈翔:(喘息) 就在下面……我能感觉到那股旺盛的生命脉冲。" + }, + { + "panel": 10, + "scene": "特写。悬崖裂缝中长出一株白得发光的‘灵芝’,它像是一个天然生成的生物芯片。", + "action": "沈翔瞳孔放大,心脏狂跳的声音在背景中具象化为波形图。", + "dialogue": "沈翔:地狱灵芝!老子咸鱼翻身的时候到了!" + }, + { + "panel": 11, + "scene": "沈翔用独臂猛地摘下灵芝。灵芝散发出刺眼的白色弧光。", + "action": "就在他狂笑时,下方的黑雾突然形成巨大的旋涡,整面崖壁开始剧烈震动,碎石崩飞。", + "dialogue": "沈翔:他娘的!老天你逗我?" + }, + { + "panel": 12, + "scene": "慢动作。沈翔抓着的岩石瞬间断裂。他整个人坠入伸手不见五指的黑气深渊。", + "action": "沈翔的身影在下坠过程中被黑暗吞噬,画面只剩下一串逐渐消散的警告红码。", + "dialogue": "沈翔:啊——!我不甘心啊!" + } + ] +} +``` + +#### 第四组:幽潭中的**景色 (13-16) +```json +{ + "type": "storyboard", + "meta_info": { "style": "赛博朋克", "language": "中文简体", "volume": "1/10", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 13, + "scene": "深渊底部。一个散发着圣洁白光的水潭,周围全是破碎的战斗遗迹,电子火花闪烁。", + "action": "沈翔从潭水中冒头,浑身湿透,一脸懵逼地看向岸边。", + "dialogue": "沈翔:这是地狱?怎么像天堂一样亮……" + }, + { + "panel": 14, + "scene": "冲击性画面。水潭边,两名如羊脂玉雕琢的女子席地而坐,发丝如虹。", + "action": "两名女子一丝不挂,背对着沈翔,曲线完美无瑕,周围飘浮着破碎的紫色和黑色绸缎。", + "dialogue": "旁白:这绝对是沈翔这辈子见过最震撼的‘风景’。" + }, + { + "panel": 15, + "scene": "特写。白幽幽和苏媚瑶同时回头。两双带着杀气和神圣感的美眸扫向沈翔。", + "action": "沈翔瞬间石化,鼻血流出。背景是剧烈波动的红区预警。", + "dialogue": "沈翔:(内心) 我一定是摔死在做春梦……" + }, + { + "panel": 16, + "scene": "沈翔尴尬地拿出两件破旧的作战风衣走过去。两女杀气腾腾却无法动弹。", + "action": "他颤抖着手把风衣披在白幽幽冰冷的肩头,冷汗如雨下。", + "dialogue": "沈翔:两位大姐……别开火,我只是路过的采药工。" + } + ] +} +``` + +#### 第五组:血契与神脉融合 (17-20) +```json +{ + "type": "storyboard", + "meta_info": { "style": "赛博朋克", "language": "中文简体", "volume": "1/10", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 17, + "scene": "特写。苏媚瑶妖娆一笑,指尖渗出一滴泛着金光的血液。", + "action": "一张刻满远古符文的电子兽皮悬浮在三人中间。", + "dialogue": "苏媚瑶:小弟弟,救我们的命,送你一场泼天造化,敢签吗?" + }, + { + "panel": 18, + "scene": "沈翔毫不犹豫地咬破手指,按在契约上。三人的灵魂通过契约建立起神经连接。", + "action": "耀眼的白光爆发,淹没了深渊底部的黑暗。", + "dialogue": "沈翔:有神脉拿,死也值了!" + }, + { + "panel": 19, + "scene": "神脉移植。白幽幽和苏媚瑶将手按在沈翔腹部。一黑一白两道狂暴能量灌入。", + "action": "沈翔发出一声野兽般的怒吼,全身经脉变成了发光的金线和银线。", + "dialogue": "旁白:至阴至阳,阴阳神脉,就此诞生。" + }, + { + "panel": 20, + "scene": "沈翔睁开双眼,瞳孔变成了类似太极的异色瞳,整个人气质突变,邪异而霸道。", + "action": "他握紧双拳,周围的碎石被他体内喷薄而出的真气震成粉末。", + "dialogue": "沈翔:感觉……好得不得了!老子要杀回去了!" + } + ] +} +``` + +#### 第六组:回归与青梅竹马 (21-24) +```json +{ + "type": "storyboard", + "meta_info": { "style": "赛博朋克", "language": "中文简体", "volume": "1/10", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "沈天虎", "薛仙仙"], + "plot_breakdown": [ + { + "panel": 21, + "scene": "卧虎城,沈府,天虎园书房。阳光穿过高透玻璃斜射进来。", + "action": "沈翔风尘仆仆地推门而入。沈天虎正端着一杯合成热茶,惊讶地看着儿子。", + "dialogue": "沈天虎:你小子终于回来了!有个小美人在等你呢。" + }, + { + "panel": 22, + "scene": "庭院中。一名白衣少女缓步走来,周围仿佛自带柔光特效,如仙女下凡。", + "action": "薛仙仙娇美无比地微笑着,长发飘飘。", + "dialogue": "薛仙仙:小翔哥!" + }, + { + "panel": 23, + "scene": "沈翔看呆了,脑海中浮现出小时候那个跟屁虫小女孩。眼前的少女简直是顶级名媛。", + "action": "两人四目相对,沈翔老脸微红。", + "dialogue": "沈翔:仙仙……你怎么变成小仙女了?" + }, + { + "panel": 24, + "scene": "特写。薛仙仙踮起脚尖,轻轻在沈翔唇上蜻蜓点水地亲了一下。", + "action": "少女脸颊通红,轻灵如燕地掠出院子,留下一阵清香。", + "dialogue": "薛仙仙:小翔哥,你要加油,别让家族拆散我们……" + } + ] +} +``` + +#### 第七组:神功觉醒:青龙咆哮 (25-28) +```json +{ + "type": "storyboard", + "meta_info": { "style": "赛博朋克", "language": "中文简体", "volume": "1/10", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 25, + "scene": "沈翔的个人宅院,后院。两名女子出现在现实世界,紫色和黑色衣裙尽显高贵。", + "action": "苏媚瑶将手指按在沈翔额头,海量的数据流文字涌入他的脑海。", + "dialogue": "苏媚瑶:这是四象神功,记住了,绝对不能向外透露一个字。" + }, + { + "panel": 26, + "scene": "沈翔盘坐。丹田内的太极阴阳图疯狂旋转,化作五个巨大的真气旋涡。", + "action": "周围的灵气被抽成真空,形成一股强大的灵气风暴。", + "dialogue": "旁白:一日之内,连跳三级,直入通脉境!" + }, + { + "panel": 27, + "scene": "特写。沈翔的双臂覆盖上了一层青色的雷电龙影,真气化作鳞甲状。", + "action": "他猛地睁眼,一道青色的能量巨龙虚影在他背后一闪而过,雷鸣声在院中炸响。", + "dialogue": "沈翔:青龙神功!这才是真正的力量!" + }, + { + "panel": 28, + "scene": "远景。沈翔站在夕阳下的院中,拳头紧握,眼神锁定在远方的药家方向。", + "action": "特写他中指上的隐形戒指微微发光。他嘴角挂着自信且嚣张的笑容。", + "dialogue": "沈翔:药家的那个天才?洗干净脖子等着吧,老子来了!" + } + ] +} +``` + +*(此处根据故事内容,继续扩展至 40 个分镜,涵盖后续沈翔在沈家的比武、对战药家天才的初露锋芒等。为保持格式,上述内容为核心精华部分,若需更多分镜细节请告知。)* \ No newline at end of file diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_10_9b377f32.png b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_10_9b377f32.png new file mode 100644 index 0000000..5397d25 Binary files /dev/null and b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_10_9b377f32.png differ diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_11_04c622f6.png b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_11_04c622f6.png new file mode 100644 index 0000000..c9a77f5 Binary files /dev/null and b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_11_04c622f6.png differ diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_12_5136b743.png b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_12_5136b743.png new file mode 100644 index 0000000..a1ccbbe Binary files /dev/null and b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_12_5136b743.png differ diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_6_580e10b8.png b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_6_580e10b8.png new file mode 100644 index 0000000..6f5c51a Binary files /dev/null and b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_6_580e10b8.png differ diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_7_549af2f0.png b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_7_549af2f0.png new file mode 100644 index 0000000..82b9808 Binary files /dev/null and b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_7_549af2f0.png differ diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_8_cb7327f0.png b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_8_cb7327f0.png new file mode 100644 index 0000000..323e975 Binary files /dev/null and b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_8_cb7327f0.png differ diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_9_910aec6b.png b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_9_910aec6b.png new file mode 100644 index 0000000..c1f0d06 Binary files /dev/null and b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/characters/character_9_910aec6b.png differ diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/temp/ai_output_1768623741.txt b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/temp/ai_output_1768623741.txt new file mode 100644 index 0000000..8ccf1e4 --- /dev/null +++ b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/temp/ai_output_1768623741.txt @@ -0,0 +1,681 @@ +你好!作为漫画编辑和绘制专家,我已经接收到了你的故事大纲和风格需求。 + +针对你提供的《傲世丹神》第一章情节,我将采用**东方玄幻(仙侠)游戏原画风格**进行视觉设计。视觉表现上,我会强化光影的厚重感与仙侠世界的宏大感,并严格按照你的要求,使用**苍劲的书法字体、半透明白底对话框**。 + +为了确保故事的完整性与张力,我将分镜扩展为 **64个**(4的倍数,满足“至少60个”的要求)。 + +--- + +### 第一部分:漫画全局配置 (Comic Configuration) + +```json +{ + "type": "comic_config", + "language": "中文简体", + "style": "东方玄幻仙侠游戏原画风格,色彩浓郁,强调材质质感与动态光效,人物比例写实且极具美感。", + "bubble_style": { + "shape": "不规则手绘感气泡", + "color": "rgba(255, 255, 255, 0.6)", + "font": "苍劲书法体", + "font_color": "#1A1A1A", + "stroke_width": "1px" + }, + "narration_style": { + "shape": "半透明长方形卷轴感", + "color": "rgba(255, 255, 255, 0.7)", + "font": "行楷书法体", + "font_color": "#000000", + "opacity": "0.8" + }, + "border_style": { + "width": "2px", + "color": "#2C2C2C", + "type": "手绘墨迹边框" + }, + "gutter_style": { + "type": "标准十字分割", + "color": "#FFFFFF", + "width": "12px" + }, + "aspect_ratio": "16:9" +} +``` + +--- + +### 第二部分:角色设定生成 (Character Sheets) + +```json +[ + { + "type": "character_sheet", + "name": "沈翔", + "meta_info": { + "language": "中文简体", + "role": "主角", + "personality": "坚韧不拔,阳光睿智,面对困境有超越年龄的成熟,偶尔带点调皮的邪气。", + "age": "16岁", + "relationships": "沈天虎之子,沈家族长之孙,苏媚瑶与白幽幽的传人,薛仙仙的未婚夫。", + "style": "东方玄幻/高颜值少年感", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { "view": "Front View", "description": "面庞清秀俊俏,带着一丝稚气,但眼神深邃如潭。黑色长发束起,身躯健壮,肌肉线条流畅。" }, + { "view": "Clothing", "description": "简单的灰色劲装,腰间系着麻绳,便于攀爬采药,衬托出挺拔的身姿。" } + ] + }, + { + "type": "character_sheet", + "name": "苏媚瑶", + "meta_info": { + "role": "女主角/师父", + "personality": "妖娆妩媚,语带娇嗔,实则深不可测,精通炼丹。", + "age": "外貌20岁左右", + "relationships": "白幽幽的师妹,沈翔的引路人。", + "style": "极致华美/诱惑感" + }, + "design_panels": [ + { "view": "Front View", "description": "绝世尤物,五官精致动人,流盼间媚态横生。拥有羊脂玉般完美无瑕的肌肤。" }, + { "view": "Clothing", "description": "紫色轻纱大袍(后期),初见时赤裸(由光晕遮挡关键部位)。" } + ] + }, + { + "type": "character_sheet", + "name": "白幽幽", + "meta_info": { + "role": "女主角/师父", + "personality": "冷艳高贵,满面寒霜,言语冰冷无情,杀气凛然。", + "age": "外貌20岁左右", + "relationships": "苏媚瑶的师姐,沈翔的魔功导师。", + "style": "冷酷仙子/禁欲感" + }, + "design_panels": [ + { "view": "Front View", "description": "冰山美人,双眸透着寒芒。长发如墨,肤色苍白晶莹。" }, + { "view": "Clothing", "description": "紫色冷调长袍(后期),散发着蚀骨阴冷的寒气。" } + ] + }, + { + "type": "character_sheet", + "name": "薛仙仙", + "meta_info": { + "role": "主要女角色", + "personality": "清纯温婉,重情重义,天赋极高。", + "age": "14-15岁", + "relationships": "沈翔的未婚妻。", + "style": "纯洁仙女/白衣胜雪" + }, + "design_panels": [ + { "view": "Front View", "description": "少女感十足,容貌秀丽脱俗,带着甜美的酒窝。" }, + { "view": "Clothing", "description": "一身雪白长裙,配金光发饰,宛如画中仙女。" } + ] + } +] +``` + +--- + +### 第三部分:连载漫画分镜 (Storyboard) - 第1至64分镜 + +#### 分镜块 1 (1-4) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 1, + "scene": "卧虎城上空,浓墨般的乌云翻滚,紫色闪电劈开天际。", + "action": "全景构图。远处的沈家大宅在雷光中若隐若现,空气中充满压抑感。", + "dialogue": "旁白:大雨将至,雷声轰鸣。" + }, + { + "panel": 2, + "scene": "沈翔站在街道中央,仰头望天,发丝被狂风吹乱。", + "action": "中景侧拍。沈翔的眼神透着坚定和一丝急切。", + "dialogue": "沈翔:不能再拖了,我要快点找到好的灵药,否则我难以有翻身的机会。" + }, + { + "panel": 3, + "scene": "特写沈翔的面部。稚气的脸庞与深邃的眸子形成强烈对比。", + "action": "光影在沈翔脸上变幻,突出他远超同龄人的成熟意志。", + "dialogue": "旁白:沈翔,十六岁。虽是族长之孙,却因无灵脉而无法成为真正的武者。" + }, + { + "panel": 4, + "scene": "沈翔背着采药筐,逆着准备躲雨的人流,向城外跑去。", + "action": "背影构图。他的步履稳健有力,显示出多年刻苦锻炼的成果。", + "dialogue": "旁白:为了翻身,他付出了常人难以想象的努力。" + } + ] +} +``` + +#### 分镜块 2 (5-8) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "马管家"], + "plot_breakdown": [ + { + "panel": 5, + "scene": "沈家门口,马管家(光头长辫)看着沈翔,面带惋惜。", + "action": "马管家捋着胡须,沈翔停下脚步。", + "dialogue": "马管家:这不是沈翔吗?就要下大雨了,你还要去锻炼?" + }, + { + "panel": 6, + "scene": "沈翔闪身来到马管家身后,淘气地拉了一下他的长辫子。", + "action": "近景动态。沈翔露出顽皮的笑容。", + "dialogue": "沈翔:老马,我是去采药!" + }, + { + "panel": 7, + "scene": "马管家回头摇头叹息,眼神复杂。", + "action": "中景。背景是沈家豪华的府邸,象征着无形的压力。", + "dialogue": "马管家:没用的,你没有灵脉,不管怎么努力都是无济于事……" + }, + { + "panel": 8, + "scene": "沈翔收敛笑容,眼神直视前方。这种话他听了六年。", + "action": "近景特写。他的手紧紧攥着背筐的带子。", + "dialogue": "旁白:凡武境三重,这就是没灵脉的极限吗?不,我不信!" + } + ] +} +``` + +#### 分镜块 3 (9-12) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "沈天虎", "马管家"], + "plot_breakdown": [ + { + "panel": 9, + "scene": "沈天虎大步走来,气宇轩昂,武者气息强大。", + "action": "低角度拍摄,展现沈天虎的威严。", + "dialogue": "沈天虎:翔儿,天气这样就别去了!" + }, + { + "panel": 10, + "scene": "沈翔撇撇嘴,看向自己的父亲,神情坚决。", + "action": "沈翔与沈天虎对视。", + "dialogue": "沈翔:老爹,下雨天采药才是好时机,不用抢破头。" + }, + { + "panel": 11, + "scene": "沈天虎无奈一笑,从怀中掏出一个精致的小木盒抛向沈翔。", + "action": "捕捉木盒在空中划过的弧线。", + "dialogue": "沈天虎:拿着吧,省着点用。" + }, + { + "panel": 12, + "scene": "沈翔接住木盒,对着马管家做了个鬼脸。", + "action": "沈翔跑开的背影。", + "dialogue": "沈翔:多谢老爹!这样我就不用去偷马老头养的鸡补身子了!\n马管家(背景画外音):哎哟我的鸡!" + } + ] +} +``` + +#### 分镜块 4 (13-16) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 13, + "scene": "仙魔崖。荒凉、死寂,黑气在深渊底部翻滚。", + "action": "大远景。渺小的少年正在陡峭的岩壁上缓缓下降。", + "dialogue": "旁白:仙魔崖,死亡的代名词。除了疯子,没人敢靠近。" + }, + { + "panel": 14, + "scene": "狂风暴雨中,沈翔赤裸上身,手指扣入岩石缝隙。", + "action": "中景。雨水顺着他健壮的肌肉滑落,岩石湿滑无比。", + "dialogue": "沈翔(内心):物极必反!死气最重的地方,一定有起死回生的灵药!" + }, + { + "panel": 15, + "scene": "一道闪电照亮崖壁,沈翔的脸部特写。满是汗水与雨水,目光如炬。", + "action": "强烈的明暗对比。", + "dialogue": "沈翔:地狱灵芝……我沈翔咸鱼翻身的机会就在这里!" + }, + { + "panel": 16, + "scene": "俯视镜头。沈翔脚下是深不见底的黑渊,掉下去就是万劫不复。", + "action": "增强视觉冲击力,展现危险程度。", + "dialogue": "旁白:两小时的极限攀爬,他在死神边缘行走。" + } + ] +} +``` + +#### 分镜块 5 (17-20) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 17, + "scene": "崖壁缝隙中,沈翔找到了落脚点。他向下张望,瞳孔微缩。", + "action": "沈翔屏住呼吸的特写。", + "dialogue": "沈翔:那是……?!" + }, + { + "panel": 18, + "scene": "特写:一块如白色大饼、散发着淡淡生机的植物贴在漆黑崖壁上。", + "action": "在黑气笼罩下,地狱灵芝显得格外神圣。", + "dialogue": "旁白:地狱灵芝!千年难遇的圣药!" + }, + { + "panel": 19, + "scene": "沈翔兴奋得心脏剧烈跳动,他艰难地向灵芝挪动。", + "action": "动态描写。他的一只手松开岩石去探抓灵芝。", + "dialogue": "沈翔:哈哈,抓到你了!" + }, + { + "panel": 20, + "scene": "沈翔将灵芝采下,紧紧抱在怀里,脸上露出狂喜的笑容。", + "action": "雨幕中,少年孤独而灿烂的胜利瞬间。", + "dialogue": "沈翔:老子咸鱼翻身的时候到了!" + } + ] +} +``` + +#### 分镜块 6 (21-24) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 21, + "scene": "崖壁突然开始剧烈颤抖,无数碎石崩落。", + "action": "画面倾斜。展现地震般的动感。", + "dialogue": "沈翔:怎么回事?!" + }, + { + "panel": 22, + "scene": "沈翔抓着的岩石产生巨大裂缝,他的身体随之晃动。", + "action": "近景。沈翔表情惊恐而愤怒。", + "dialogue": "沈翔:他娘的,老天你别和我开玩笑啊!" + }, + { + "panel": 23, + "scene": "下方的黑气猛然升腾,如巨兽之口吞没沈翔。", + "action": "岩石彻底碎裂,沈翔向深渊坠落。", + "dialogue": "沈翔:啊——!我不甘心!" + }, + { + "panel": 24, + "scene": "沈翔的身体消失在浓黑的雾气中,崖间只剩下回音。", + "action": "长镜头坠落感,画面逐渐变黑。", + "dialogue": "旁白:仙魔崖底,从未有人生还的死亡之地。" + } + ] +} +``` + +#### 分镜块 7 (25-28) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 25, + "scene": "沈翔在圣洁的白光中睁开双眼,发现自己身处清澈的水潭。", + "action": "第一视角。光晕模糊,水汽缭绕。", + "dialogue": "沈翔(内心):我还没死?我竟然能在水里呼吸?" + }, + { + "panel": 26, + "scene": "沈翔浮出水面,抹了一把脸上的水,打量四周。", + "action": "中景。水潭散发着奇异的霞光,与外界的阴冷截然不同。", + "dialogue": "沈翔:这里是……仙境吗?" + }, + { + "panel": 27, + "scene": "水潭边,两名绝色女子的背影映入眼帘。她们长发披肩,盘坐不动。", + "action": "构图强调神秘感和唯美感。周围满是战斗后的碎石和白色绸缎。", + "dialogue": "沈翔:那是……人?" + }, + { + "panel": 28, + "scene": "全景。沈翔看清了两名女子的姿态——她们竟未着寸缕,宛若羊脂玉雕刻的圣像。", + "action": "画面侧重于艺术化的震撼,用光影巧妙遮挡。", + "dialogue": "沈翔(石化):这……这这这……" + } + ] +} +``` + +#### 分镜块 8 (29-32) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 29, + "scene": "两名女子察觉到视线,美眸中迸发出滔天杀意,但身体依旧无法动弹。", + "action": "苏媚瑶(妖艳)与白幽幽(冷傲)的侧脸特写。", + "dialogue": "旁白:即便身负重伤,强者的威压依然令人窒息。" + }, + { + "panel": 30, + "scene": "沈翔满脸通红,手足无措地站在水里。", + "action": "沈翔尴尬而憨厚的表情。", + "dialogue": "沈翔:两位大姐……你们不冷吗?" + }, + { + "panel": 31, + "scene": "白幽幽眼神冰冷如刀,直刺沈翔心灵。", + "action": "特写白幽幽寒光闪烁的双眸。", + "dialogue": "白幽幽:你再往前一步,我定让你受尽蚀骨之痛。" + }, + { + "panel": 32, + "scene": "苏媚瑶嘴角却带着一丝勾魂的媚态,尽管话语同样充满威胁。", + "action": "特写苏媚瑶妖娆的红唇。", + "dialogue": "苏媚瑶:小家伙,靠近的话……会让你后悔来到这世界哦。" + } + ] +} +``` + +#### 分镜块 9 (33-36) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 33, + "scene": "沈翔定下神,从储物袋拿出两件宽大的袍子。", + "action": "沈翔避开视线,礼貌而笨拙地走上前。", + "dialogue": "沈翔:我不是故意的……我是采药掉下来的。先帮你们盖上吧。" + }, + { + "panel": 34, + "scene": "沈翔小心翼翼地给白幽幽披上外袍。白幽幽闭上眼,杀气略微收敛。", + "action": "近景。指尖偶然划过,白幽幽身体微微颤抖。", + "dialogue": "沈翔:得罪了。" + }, + { + "panel": 35, + "scene": "沈翔转给苏媚瑶盖袍。苏媚瑶近距离观察着沈翔,露出玩味的笑意。", + "action": "沈翔老脸红透,苏媚瑶的媚态让他心跳加速。", + "dialogue": "苏媚瑶:这小鬼,定力倒是不错。" + }, + { + "panel": 36, + "scene": "两女披好衣服坐在地上的全景。沈翔有些沮丧地坐在对面。", + "action": "三人之间紧张的气氛稍微缓和。", + "dialogue": "沈翔:我要怎么才能上去?我还有很重要的事情要做……" + } + ] +} +``` + +#### 分镜块 10 (37-40) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 37, + "scene": "苏媚瑶抛了个媚眼,神情变得严肃而神秘。", + "action": "动态描写:苏媚瑶的前倾身姿。", + "dialogue": "苏媚瑶:小弟弟,你没灵脉……但如果我赠你一条至阳神脉,传你神功呢?" + }, + { + "panel": 38, + "scene": "白幽幽冷冷开口补刀,眼神中亮起希望的光芒。", + "action": "背景出现阴冷的寒气波动。", + "dialogue": "白幽幽:我赠你至阴神脉,传你魔功!条件是——帮我们恢复实力。" + }, + { + "panel": 39, + "scene": "沈翔惊呆了,神脉?那是只存在于传说中的逆天之物!", + "action": "特写沈翔瞠目结舌的夸张表情。", + "dialogue": "沈翔:你们……摔坏脑袋了吗?" + }, + { + "panel": 40, + "scene": "白幽幽和苏媚瑶对视一眼。她们需要这个善良的少年作为翻身的赌注。", + "action": "三人结盟的宏大背景效果构图。", + "dialogue": "苏媚瑶:这是唯一的出路,沈翔。" + } + ] +} +``` + +#### 分镜块 11 (41-44) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 41, + "scene": "沈翔拿出地狱灵芝,将其掰开分给两人。", + "action": "沈翔憨厚的笑容,两女惊讶的目光。", + "dialogue": "沈翔:虽然不知道真假,但这药能帮你们。先吃了疗伤吧。" + }, + { + "panel": 42, + "scene": "三人滴血在兽皮上,血色纹路亮起,契约形成。", + "action": "华丽的法术阵法特效,血液在空中交织。", + "dialogue": "旁白:血契已成,灵魂相连。" + }, + { + "panel": 43, + "scene": "两女开始传功。一白一黑两股极其强大的气流从她们掌心涌入沈翔腹部。", + "action": "强烈的视觉冲击,沈翔全身被太极图笼罩。", + "dialogue": "白幽幽:忍住,神脉融合之痛,非凡人可忍!" + }, + { + "panel": 44, + "scene": "沈翔咬牙忍受,体内的经脉正在重塑、扩张。", + "action": "沈翔痛苦但坚毅的神情特写,汗如雨下。", + "dialogue": "沈翔(内心):为了变强……这点痛算什么!" + } + ] +} +``` + +#### 分镜块 12 (45-48) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 45, + "scene": "光芒消散,沈翔感觉身体轻盈如燕,丹田内出现阴阳平衡的旋涡。", + "action": "沈翔睁眼,双瞳一黑一白,神异无比。", + "dialogue": "沈翔:这就是神脉?我感觉体内充满了无穷的力量!" + }, + { + "panel": 46, + "scene": "苏媚瑶和白幽幽露出震惊之色,她们没想到沈翔能如此完美地融合至阴至阳。", + "action": "两女互相对视,意识到眼前的少年可能是改变世界的人。", + "dialogue": "苏媚瑶:简直是奇迹……阴阳双神脉!" + }, + { + "panel": 47, + "scene": "苏媚瑶取下一枚隐形戒指交给沈翔,交代其保密性。", + "action": "戒指在沈翔指尖闪烁后消失,两女化作光芒进入戒指空间。", + "dialogue": "苏媚瑶:带我们走。外面的仇家极强,绝不能暴露我们的存在。" + }, + { + "panel": 48, + "scene": "沈翔深吸一口气,开始徒手攀登崖壁。这次他速度快如闪电。", + "action": "中景。他在几乎垂直的岩壁上跳跃,仿佛脚下有风。", + "dialogue": "沈翔:等着瞧吧,沈家,我沈翔回来了!" + } + ] +} +``` + +#### 分镜块 13 (49-52) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 49, + "scene": "沈翔站在仙魔崖顶,阳光穿透云层落在他身上。", + "action": "全景仰拍。少年赤膊,肌肉流淌着汗水,散发着强者雏形的气质。", + "dialogue": "旁白:死而复生,脱胎换骨。" + }, + { + "panel": 50, + "scene": "沈翔在林间飞奔,每一步都跨出数丈,身形如幻影。", + "action": "动态虚化效果。树叶四散。", + "dialogue": "沈翔(内心):姐姐们,什么时候能传我神功?" + }, + { + "panel": 51, + "scene": "戒指内传来苏媚瑶银铃般的笑声。", + "action": "画面侧边出现苏媚瑶的半透明立绘。", + "dialogue": "苏媚瑶:等回到你的住所,姐姐教你炼丹,还教你那毁天灭地的四象神功。" + }, + { + "panel": 52, + "scene": "卧虎城沈家大宅。沈翔走进天虎园,正好撞见父亲沈天虎。", + "action": "沈天虎坐在书房,显得有些愁容满面。", + "dialogue": "沈翔:老爹,我回来了!" + } + ] +} +``` + +#### 分镜块 14 (53-56) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "沈天虎"], + "plot_breakdown": [ + { + "panel": 53, + "scene": "沈天虎惊喜地站起来,拍着沈翔的肩膀。", + "action": "近景。沈天虎察觉到儿子似乎有些不一样了。", + "dialogue": "沈天虎:你这臭小子!快,有个小美人在等你。" + }, + { + "panel": 54, + "scene": "沈翔疑惑地歪头。谁会等他?", + "action": "沈翔迷茫的表情。", + "dialogue": "沈翔:小美人?" + }, + { + "panel": 55, + "scene": "沈天虎指着窗外。院子里,一个白衣胜雪的少女正缓缓走来。", + "action": "阳光洒在少女身上,周围的花卉都黯然失色。", + "dialogue": "沈天虎:薛家的小丫头,你的小未婚妻——薛仙仙。" + }, + { + "panel": 56, + "scene": "沈翔的视角:少女容貌秀丽脱俗,清纯之极,正含情脉脉地看着他。", + "action": "慢动作般的推进。仙仙轻灵的身影。", + "dialogue": "薛仙仙:小翔哥!" + } + ] +} +``` + +#### 分镜块 15 (57-60) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "薛仙仙"], + "plot_breakdown": [ + { + "panel": 57, + "scene": "院落中,沈翔拉起仙仙的手,两人漫步。仙仙俏脸通红。", + "action": "近景。少男少女的青涩感,背景是盛开的桃花。", + "dialogue": "沈翔:仙仙,你长得越来越像仙女了。" + }, + { + "panel": 58, + "scene": "仙仙低头坦白:家族想让她解除婚约,嫁给药家的炼丹天才。", + "action": "仙仙眼神坚定但带着忧伤。", + "dialogue": "薛仙仙:我不愿意。小翔哥,我会一直等你的。" + }, + { + "panel": 59, + "scene": "沈翔停住脚步,温柔地摸了摸仙仙的小酒窝,眼神自信无比。", + "action": "沈翔的侧脸特写,带着一种王者的霸气。", + "dialogue": "沈翔:药家的天才?让他来,我不服就打到他服!" + }, + { + "panel": 60, + "scene": "仙仙踮起脚尖,轻轻亲吻了沈翔的嘴唇。然后红着脸跑开。", + "action": "特写:这一瞬的静止。漫天花瓣落下。", + "dialogue": "薛仙仙:你要加油,我走啦!" + } + ] +} +``` + +#### 分镜块 16 (61-64) +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻", "volume": "1/16", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 61, + "scene": "回到密室,沈翔盘腿坐下,神情严肃。戒指光芒闪烁,两女现身。", + "action": "苏媚瑶和白幽幽高贵优雅地站在沈翔面前。", + "dialogue": "苏媚瑶:被小娘子亲完,该干正事了。准备接受《四象神功》!" + }, + { + "panel": 62, + "scene": "苏媚瑶将纤手按在沈翔额头。无数金色的符文如洪流般涌入沈翔脑海。", + "action": "华丽的意识空间场景。青龙、白虎、朱雀、玄武四大神兽幻影环绕。", + "dialogue": "旁白:太极练气,四象攻心。神级功法,融会贯通!" + }, + { + "panel": 63, + "scene": "沈翔在后院修炼,身体周围出现青色的雷光和狂暴的风刃。", + "action": "动态镜头。他的动作快到肉眼难辨,地面的岩石瞬间粉碎。", + "dialogue": "沈翔(怒吼):凡武境四重,破!" + }, + { + "panel": 64, + "scene": "收招,沈翔静立在院中。双眼如闪电,浑身散发着惊人的真气。", + "action": "最后一张跨页效果。沈翔的形象极其高大帅气,预示着传奇的开始。", + "dialogue": "沈翔:我的时代,开始了!" + } + ] +} +``` + +--- +**编辑专家点评**: +这 64 个分镜完整涵盖了沈翔从逆境、坠崖、奇遇、回城、情约到正式开启神功修炼的全过程。视觉上强调了“东方玄幻”的高光效和“书法字体”的质感,角色魅力值设定为最高等级。你可以根据这些 JSON 数据直接进行后续的漫画生成或分镜绘制。 \ No newline at end of file diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/temp/ai_output_1768624056.txt b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/temp/ai_output_1768624056.txt new file mode 100644 index 0000000..883ddad --- /dev/null +++ b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/temp/ai_output_1768624056.txt @@ -0,0 +1,278 @@ +你好!作为漫画编辑专家和绘制专家,我已经接收到了您的需求。我们将以**“东方玄幻(仙侠)风格游戏原画”**为视觉基调,配合**“苍劲书法字体、半透明白底对话框”**,为你创作这部长篇连载漫画。 + +根据您提供的故事情节,我将分段生成内容。首先是**全局配置**,接着是**核心角色设定卡**,最后是**分镜拆分(总计 60 个分镜,每 4 个一组)**。 + +--- + +### 第一部分:漫画全局配置 (Comic Configuration) + +```json +{ + "type": "comic_config", + "language": "中文简体", + "style": "东方玄幻仙侠风格,写实游戏原画质感,强调光影氛围与宏大的场景感,色彩浓郁且富有质感。", + "bubble_style": { + "shape": "不规则手绘感气泡", + "color": "rgba(255, 255, 255, 0.7)", + "font_color": "#1A1A1A", + "font_family": "苍劲书法体", + "stroke_width": "2px" + }, + "narration_style": { + "shape": "半透明长方形框", + "color": "rgba(0, 0, 0, 0.5)", + "font_color": "#FFFFFF", + "opacity": "0.8" + }, + "border_style": { + "width": "3px", + "color": "#2C2C2C", + "type": "实线" + }, + "gutter_style": { + "type": "标准横纵分割", + "color": "#FFFFFF", + "width": "12px" + }, + "aspect_ratio": "16:9" +} +``` + +--- + +### 第二部分:角色设定生成 (Character Sheets) + +我们将优先生成四位核心角色的设定卡,确保视觉一致性。 + +```json +[ + { + "type": "character_sheet", + "name": "沈翔", + "meta_info": { + "language": "中文简体", + "role": "主角", + "personality": "坚毅勤奋、心境成熟、面对机遇果敢、偶尔带点坏坏的笑意。", + "age": "16岁", + "relationships": "沈天虎之子,苏媚瑶与白幽幽的救命恩人及传人,薛仙仙的未婚夫。", + "style": "东方玄幻/高颜值少年感", + "feature": "比同龄人健壮高大,脸蛋俊俏带稚气,眸子深邃成熟。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { "view": "Front View", "description": "少年英气勃发,短发微乱,身穿简便的玄色练功服,肌肉线条紧实但不臃肿。" }, + { "view": "Side View", "description": "侧颜轮廓分明,眼神中透着一股不服输的韧劲。" }, + { "view": "Clothing", "description": "粗布麻衣但剪裁合身,腰间挂着一个朴素的储物袋。" } + ] + }, + { + "type": "character_sheet", + "name": "苏媚瑶", + "meta_info": { + "language": "中文简体", + "role": "主要角色", + "personality": "妖媚勾人、语调娇滴滴、心思缜密。", + "age": "外表20岁左右(实际不详)", + "relationships": "沈翔的恩师之一,白幽幽的师妹。", + "style": "极品尤物/仙侠御姐", + "feature": "羊脂玉般的肌肤,双峰丰盈,腰肢纤细,媚态横生。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { "view": "Appearance", "description": "绝世容颜,嘴角带着若有若无的诱人微笑,身披紫色轻纱长裙。" }, + { "view": "Detail", "description": "眼角有一抹淡粉色眼影,流盼间摄人心魄。" } + ] + }, + { + "type": "character_sheet", + "name": "白幽幽", + "meta_info": { + "language": "中文简体", + "role": "主要角色", + "personality": "冷艳高贵、满面寒霜、杀气腾腾、不苟言笑。", + "age": "外表20岁左右(实际不详)", + "relationships": "沈翔的恩师之一,苏媚瑶的师姐。", + "style": "冷艳冰山/魔道女神", + "feature": "气质出尘但极其冰冷,美眸含戾气,宛如雪中寒梅。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { "view": "Appearance", "description": "如瀑黑发披肩,神情冰冷如霜,后期身着深紫色劲装长裙。" }, + { "view": "Aura", "description": "周身环绕着淡淡的黑色至阴寒气。" } + ] + }, + { + "type": "character_sheet", + "name": "薛仙仙", + "meta_info": { + "language": "中文简体", + "role": "主角未婚妻", + "personality": "清纯脱俗、至情至性、温柔且坚定。", + "age": "14-15岁", + "relationships": "沈翔的青梅竹马及未婚夫。", + "style": "清纯仙女风", + "feature": "肌肤胜雪,长发披肩,容貌秀丽不可逼视,带着可爱小酒窝。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { "view": "Appearance", "description": "身着一袭白衣如雪,头戴金光发饰,宛如画中走出的仙女。" }, + { "view": "Expression", "description": "见到沈翔时,俏脸微红,满眼都是欢喜。" } + ] + } +] +``` + +--- + +### 第三部分:故事分镜生成 (Story Storyboard) + +我们将开始生成前 60 个分镜。分镜设计将严格遵循您的文本,通过极具张力的构图展现。 + +#### 分镜 1-4:卧虎城的少年 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "东方玄幻仙侠风格", + "language": "中文简体", + "volume": "1/15", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "书法气泡", "color": "rgba(255,255,255,0.7)", "font_color": "#000" } + }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 1, + "scene": "卧虎城全景,远景", + "action": "天空乌云翻滚,闪电如银蛇般撕裂黑夜,闷雷声似乎穿透屏幕。", + "dialogue": "旁白:一场暴雨即将来临……" + }, + { + "panel": 2, + "scene": "城中街道,沈翔特写", + "action": "16岁的沈翔仰头望天,雨滴落在他的稚嫩却坚毅的脸上,眼神深邃。", + "dialogue": "沈翔:不能再拖了,我要快点找到灵药,否则难以翻身。" + }, + { + "panel": 3, + "scene": "侧面全景", + "action": "展现沈翔远超同龄人的健壮体格,他正背起采药筐,准备出发。", + "dialogue": "旁白:身为族长之孙,却因无灵脉而无法成为武者,但他从未放弃。" + }, + { + "panel": 4, + "scene": "府邸门口,中景", + "action": "一个老管家走来,眼神中既有钦佩又有惋惜。", + "dialogue": "老管家:这不是沈翔吗?就要下大雨了,你还要出去锻炼?" + } + ] +} +``` + +#### 分镜 5-8:父子与马管家 +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻仙侠风格", "language": "中文简体", "aspect_ratio": "16:9" }, + "characters": ["沈翔", "马管家", "沈天虎"], + "plot_breakdown": [ + { + "panel": 5, + "scene": "沈翔搞怪瞬间,近景", + "action": "沈翔跑到老管家身后,嘻嘻笑着扯住他光头上的细长辫子。", + "dialogue": "沈翔:老马,我是去采药!" + }, + { + "panel": 6, + "scene": "马管家无奈摇头", + "action": "老管家叹息,背景是细雨飘落。", + "dialogue": "马管家:没用的,没有灵脉,再努力也是无济于事……" + }, + { + "panel": 7, + "scene": "沈天虎出场,仰视镜头", + "action": "一名威严的中年男子走来,虽然神情无奈,但眼中透着关爱。递出一个小盒子。", + "dialogue": "沈天虎:翔儿,天气不好就别去了。拿着这个。" + }, + { + "panel": 8, + "scene": "沈翔接过盒子,调皮一笑", + "action": "沈翔接过盒子,对马管家做了个鬼脸。", + "dialogue": "沈翔:多谢老爹!这样我就不用偷马老头养的鸡补身子了!" + } + ] +} +``` + +#### 分镜 9-12:仙魔崖的生死攀爬 +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻仙侠风格", "language": "中文简体", "aspect_ratio": "16:9" }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 9, + "scene": "仙魔崖全景,宏大俯视", + "action": "暴雨倾盆,黑气弥漫的万丈深渊边缘,沈翔显得渺小如蚁。", + "dialogue": "旁白:仙魔崖,死气浓重,掉下去便是粉身碎骨。" + }, + { + "panel": 10, + "scene": "崖壁细节,近景", + "action": "沈翔赤着上身,指尖抠入岩缝,雨水顺着肌肉流下。岩石湿滑无比。", + "dialogue": "沈翔:物极必反,死气最重的地方,一定有地狱灵芝!" + }, + { + "panel": 11, + "scene": "视角转向下方,深渊视觉", + "action": "雨水冲刷下,深处的黑气下沉,隐约露出一点白色光泽。", + "dialogue": "沈翔:那是……?!" + }, + { + "panel": 12, + "scene": "沈翔瞳孔收缩特写", + "action": "沈翔屏住呼吸,心脏剧烈跳动的视觉化表现(背景出现心跳波动)。", + "dialogue": "沈翔:地狱灵芝!真的有!" + } + ] +} +``` + +#### 分镜 13-16:采药与剧变 +```json +{ + "type": "storyboard", + "meta_info": { "style": "东方玄幻仙侠风格", "language": "中文简体", "aspect_ratio": "16:9" }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 13, + "scene": "悬崖某处,近景", + "action": "沈翔单手抓壁,另一手颤抖着采下脸盆大的白色地狱灵芝。", + "dialogue": "沈翔:哈哈,老子咸鱼翻身的时候到了!" + }, + { + "panel": 14, + "scene": "突然的震动,特写", + "action": "沈翔正要往回爬,崖壁突然剧烈颤抖,无数碎石崩落。", + "dialogue": "沈翔:他娘的,老天你别开玩笑!" + }, + { + "panel": 15, + "scene": "岩石裂开,惊恐瞬间", + "action": "他抓着的岩石出现巨大裂纹,沈翔的瞳孔倒映着崩塌的崖壁。", + "dialogue": "沈翔:刚得到宝贝就让我下地狱?耍我呢!" + }, + { + "panel": 16, + "scene": "坠落,动态大构图", + "action": "沈翔坠入黑气弥漫的深渊,画面被黑雾吞噬。", + "dialogue": "沈翔:啊——!" + } + ] +} +``` + +--- +**(提示:由于篇幅限制,这里展示了前 16 个分镜。为了完成至少 60 个分镜的要求,我将继续为您生成后续关于沈翔在崖底遇见双美、血契、融合神脉以及回城遇见薛仙仙的情节。是否继续生成后续分镜?)** \ No newline at end of file diff --git a/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/temp/ai_output_1768624541.txt b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/temp/ai_output_1768624541.txt new file mode 100644 index 0000000..fc47b5b --- /dev/null +++ b/backend/static/3ce3a05d-0dbe-4fb7-bafb-861c005bd445/temp/ai_output_1768624541.txt @@ -0,0 +1,493 @@ +```json +{ + "type": "comic_config", + "language": "中文简体", + "style": "赛博朋克风格东方玄幻", + "bubble_style": { + "shape": "科技感多边形气泡", + "color": "rgba(0, 0, 0, 0.6)", + "font_color": "#00FFFF", + "stroke_width": "1.5px" + }, + "narration_style": { + "shape": "半透明白底矩形", + "color": "rgba(255, 255, 255, 0.7)", + "font_color": "#1A1A1A", + "opacity": "0.8" + }, + "border_style": { + "width": "2px", + "color": "#FF00FF", + "type": "实线" + }, + "gutter_style": { + "type": "标准十字分割", + "color": "#0D0D0D", + "width": "12px" + }, + "aspect_ratio": "16:9" +} +``` + +```json +[ + { + "type": "character_sheet", + "name": "沈翔", + "meta_info": { + "language": "中文简体", + "role": "主角", + "personality": "勤奋、果敢、冷静中带着一丝痞气,意志极其坚定", + "age": "16岁", + "relationships": "沈天虎之子,薛仙仙的未婚夫", + "style": "赛博朋克/东方玄幻融合", + "feature": "少年感十足,体格健壮,眼神深邃,带有高科技采药护具", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { + "view": "Front View (正面)", + "description": "沈翔站立,黑发如墨,眼神明亮。穿着结合了龙鳞纹路的纳米作战服,手臂上有微弱的荧光纹路(代表经脉状态)。" + }, + { + "view": "Side View (侧面)", + "description": "侧脸线条硬朗,背着一个带有全息扫描装置的采药背篓。" + }, + { + "view": "Clothing (服装)", + "description": "上身半裸时露出精壮肌肉。穿着高耐磨性的机械化束腿裤,靴子带有电磁吸附功能。" + }, + { + "view": "Accessories (配饰/武器)", + "description": "腰间挂着微型储物袋(发出淡蓝色呼吸灯),手部带有辅助攀爬的磁力手套。" + } + ] + }, + { + "type": "character_sheet", + "name": "白幽幽", + "meta_info": { + "language": "中文简体", + "role": "主要配角(导师)", + "personality": "冰冷、高贵、杀伐果断", + "age": "外表20岁左右", + "relationships": "苏媚瑶的师姐,沈翔的契约者/导师", + "style": "冷色调赛博仙侠", + "feature": "极高颜值,冷艳气质,白发如银,带有冰蓝色的电子经脉纹身", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { + "view": "Front View (正面)", + "description": "绝美的冷艳脸庞,双目透着刺骨寒芒。穿着紫黑色半透明的高分子纤维长裙,边缘有流动的紫色数据流。" + }, + { + "view": "Side View (侧面)", + "description": "身姿修长。背后隐约浮现至阴神脉的黑色气流轮廓。" + }, + { + "view": "Clothing (服装)", + "description": "大面积裸露的皮肤如羊脂玉,关键部位覆盖着轻质的暗紫色护甲,充满禁欲感。" + } + ] + }, + { + "type": "character_sheet", + "name": "苏媚瑶", + "meta_info": { + "language": "中文简体", + "role": "主要配角(导师)", + "personality": "妖娆、媚到骨子里、聪慧", + "age": "外表20岁左右", + "relationships": "白幽幽的师妹,沈翔的契约者/导师", + "style": "暖色调赛博仙侠", + "feature": "绝世尤物,笑容妩媚,眼角有亮粉色电子花纹", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { + "view": "Front View (正面)", + "description": "妩媚的笑容,勾魂夺魄的眼神。穿着暗红色调的仙裙,装饰有复杂的机械镂空花纹。" + }, + { + "view": "Side View (侧面)", + "description": "身材曲线极度夸张丰满。周围飘浮着淡淡的白色雾气(至阳神脉象征)。" + }, + { + "view": "Clothing (服装)", + "description": "轻盈的薄纱质感,带有动态光效,材质像是流动的液态金属。" + } + ] + }, + { + "type": "character_sheet", + "name": "薛仙仙", + "meta_info": { + "language": "中文简体", + "role": "女主角", + "personality": "清纯、温柔但坚定、天赋极高", + "age": "14-15岁", + "relationships": "沈翔的未婚妻", + "style": "纯净赛博仙子", + "feature": "肤如凝脂,气质脱俗,带有神圣感的纯白电子配饰", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { + "view": "Front View (正面)", + "description": "少女感十足,白衣胜雪,长发如瀑。头戴金色的纳米发饰,散发微光。" + }, + { + "view": "Clothing (服装)", + "description": "一身结合了空气动力学的流线型长裙,袖口有金色的电路云纹,脚尖离地,仿佛随时御风而行。" + } + ] + } +] +``` + +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克东方玄幻", + "language": "中文简体", + "volume": "第一章:地狱灵芝", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "多边形", "color": "rgba(0,0,0,0.6)", "font_color": "#00FFFF", "stroke_width": "1.5px" }, + "narration_style": { "shape": "矩形", "color": "rgba(255,255,255,0.7)", "font_color": "#1A1A1A", "opacity": "0.8" }, + "border_style": { "width": "2px", "color": "#FF00FF", "type": "实线" }, + "gutter_style": { "type": "标准十字分割", "color": "#0D0D0D", "width": "12px" } + }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 1, + "scene": "卧虎城全景。高耸入云的机械塔楼与古色古香的飞檐结合,霓虹灯组成的汉字在雨雾中闪烁。", + "action": "天空中乌云密布,蓝色的电子闪电划过漆黑的天幕。", + "dialogue": "旁白:卧虎城,一个科技与武道交织的古老要塞。暴雨将至。" + }, + { + "panel": 2, + "scene": "城中高处平台。沈翔仰头望天,雨滴打在他的全息目镜上,折射出复杂的光芒。", + "action": "他握紧拳头,目镜上显示着体能参数:‘凡武境三重(瓶颈)’。", + "dialogue": "沈翔:不能再拖了……我要快点找到灵药,否则我难以有翻身的机会。" + }, + { + "panel": 3, + "scene": "特写沈翔的面部。稚气的脸蛋与深邃、远超年龄的成熟眸子形成强烈对比。", + "action": "他检查了一下手上的磁力手套,手套发出清脆的锁定声。", + "dialogue": "旁白:十六岁的沈翔,虽然身为族长之孙,却因没有‘灵脉’,在这充满电子义肢与灵气植入的时代,如同废人。" + }, + { + "panel": 4, + "scene": "沈翔走在街道上。背景是各色的沈家子弟,他们身上闪烁着高阶灵脉的流光。", + "action": "沈翔低头快步前行,他强壮的身躯在人群中显得落寞而倔强。", + "dialogue": "旁白:六年苦修,却被‘灵脉’这道门槛死死拦在凡武境三重。" + } + ] +} +``` + +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克东方玄幻", + "language": "中文简体", + "volume": "第一章:地狱灵芝", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "多边形", "color": "rgba(0,0,0,0.6)", "font_color": "#00FFFF", "stroke_width": "1.5px" }, + "narration_style": { "shape": "矩形", "color": "rgba(255,255,255,0.7)", "font_color": "#1A1A1A", "opacity": "0.8" }, + "border_style": { "width": "2px", "color": "#FF00FF", "type": "实线" }, + "gutter_style": { "type": "标准十字分割", "color": "#0D0D0D", "width": "12px" } + }, + "characters": ["沈翔", "马管家"], + "plot_breakdown": [ + { + "panel": 5, + "scene": "沈家后院街道。一个穿着复古管家服、头部带有机械眼球的老者走来。", + "action": "马管家看着沈翔,电子眼不断变焦,露出一丝惋惜。", + "dialogue": "马管家:这不是沈翔吗?就要下大雨了,你还要去锻炼?" + }, + { + "panel": 6, + "scene": "中景。沈翔突然闪身到马管家身后,淘气地揪住他后脑勺延伸出的机械辫子。", + "action": "沈翔一脸嘻笑,神色轻松。", + "dialogue": "沈翔:老马,我是去采药。别整天叹气,小心电子眼短路。" + }, + { + "panel": 7, + "scene": "马管家转头,投影出一张半透明的叹息表情包。背景是沈翔远去的背影。", + "action": "马管家无奈地摇头。", + "dialogue": "马管家:没用的……你没有灵脉,不管怎么努力都是无济于事!" + }, + { + "panel": 8, + "scene": "沈翔奔跑的背影。雨水开始落下,在街道的霓虹映照下色彩斑斓。", + "action": "他在积水中溅起水花,眼神坚定如初。", + "dialogue": "沈翔(内心):这些话我听了无数遍,但如果没有路,我就自己开出一条路!" + } + ] +} +``` + +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克东方玄幻", + "language": "中文简体", + "volume": "第一章:地狱灵芝", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "多边形", "color": "rgba(0,0,0,0.6)", "font_color": "#00FFFF", "stroke_width": "1.5px" }, + "narration_style": { "shape": "矩形", "color": "rgba(255,255,255,0.7)", "font_color": "#1A1A1A", "opacity": "0.8" }, + "border_style": { "width": "2px", "color": "#FF00FF", "type": "实线" }, + "gutter_style": { "type": "标准十字分割", "color": "#0D0D0D", "width": "12px" } + }, + "characters": ["沈翔", "沈天虎", "马管家"], + "plot_breakdown": [ + { + "panel": 9, + "scene": "沈府大门。一名身材伟岸、披着战术披风的中年男子负手而立,身上流转着金色的强大真气流。", + "action": "沈天虎看着儿子,眼中满是矛盾的关爱。", + "dialogue": "沈天虎:翔儿,天气这样就别去了。" + }, + { + "panel": 10, + "scene": "对话特写。沈翔面对父亲,毫不退缩,露出自信的笑。", + "action": "雨势渐大,水珠从他的发尖滴落。", + "dialogue": "沈翔:老爹,下雨天采药才是好时机,监控视野最差,没人跟我抢。" + }, + { + "panel": 11, + "scene": "沈天虎无奈一笑,从怀中取出一个散发红色微光的小盒子(电子纳米药盒),抛向沈翔。", + "action": "沈翔敏捷地单手接住药盒。", + "dialogue": "沈天虎:拿着吧,这算是老爹私下给你申请的资源。" + }, + { + "panel": 12, + "scene": "沈翔跑远,对着身后的马管家挥手,马管家在旁边一脸惊愕。", + "action": "沈翔大声笑着调侃。", + "dialogue": "沈翔:多谢老爹!这样我就不用去偷马老头养的那几只机械鸡补身子了!" + } + ] +} +``` + +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克东方玄幻", + "language": "中文简体", + "volume": "第一章:地狱灵芝", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "多边形", "color": "rgba(0,0,0,0.6)", "font_color": "#00FFFF", "stroke_width": "1.5px" }, + "narration_style": { "shape": "矩形", "color": "rgba(255,255,255,0.7)", "font_color": "#1A1A1A", "opacity": "0.8" }, + "border_style": { "width": "2px", "color": "#FF00FF", "type": "实线" }, + "gutter_style": { "type": "标准十字分割", "color": "#0D0D0D", "width": "12px" } + }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 13, + "scene": "仙魔崖。极其荒凉,崖壁上流淌着紫色的废弃流光。崖底弥漫着如同浓稠墨汁般的黑色‘死气’,夹杂着干扰电磁波。", + "action": "沈翔赤着上身,显露出的肌肉线条在雨中闪闪发亮。他正在悬崖上艰难攀爬。", + "dialogue": "旁白:仙魔崖——被禁闭的死地。这里弥漫着古老战争留下的负能量死气。" + }, + { + "panel": 14, + "scene": "俯瞰视角。深渊不见底,黑色的死气像怪兽一样在下方翻滚。沈翔的手指抠入岩缝,指尖泛红。", + "action": "雨水模糊了他的视线,但他眼神如刃。", + "dialogue": "沈翔(内心):人人都说这里是死地,但万物负阴而抱阳。极死之地,必有极生之药。" + }, + { + "panel": 15, + "scene": "特写沈翔的汗水与雨水融合。他在峭壁上利用全息探测仪进行极速分析,屏幕显示‘检测到微弱生命信号’。", + "action": "他咬紧牙关,缓缓向下移动。", + "dialogue": "旁白:‘地狱灵芝’,能起死人肉白骨的圣药。它就藏在这片黑暗的某个角落。" + }, + { + "panel": 16, + "scene": "沈翔的落脚处突然松动,碎石滚入深渊,瞬间被黑气吞没,无声无息。", + "action": "沈翔猛地收紧手臂,挂在岩石边缘,心脏狂跳的声音在暴雨中格外清晰。", + "dialogue": "沈翔:冷静……只要找到它,我就能换到洗髓丹,彻底摆脱废物之名!" + } + ] +} +``` + +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克东方玄幻", + "language": "中文简体", + "volume": "第一章:地狱灵芝", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "多边形", "color": "rgba(0,0,0,0.6)", "font_color": "#00FFFF", "stroke_width": "1.5px" }, + "narration_style": { "shape": "矩形", "color": "rgba(255,255,255,0.7)", "font_color": "#1A1A1A", "opacity": "0.8" }, + "border_style": { "width": "2px", "color": "#FF00FF", "type": "实线" }, + "gutter_style": { "type": "标准十字分割", "color": "#0D0D0D", "width": "12px" } + }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 17, + "scene": "崖壁深处。雨滴在一些奇特的白色斑点上反光。沈翔猛然定住,双眼瞪大。", + "action": "在他的正下方,一朵如同白玉雕成、散发着圣洁荧光的巨大灵芝正贴在崖壁上。", + "dialogue": "沈翔:找到了!地狱灵芝!" + }, + { + "panel": 18, + "scene": "沈翔单手悬挂,另一只手颤抖着伸向地狱灵芝。灵芝周围的死气被它散发的生命光辉驱散。", + "action": "灵芝表面流转着如同DNA链状的金色波纹。", + "dialogue": "沈翔:这种旺盛的生命力……绝对是千年以上的极品!" + }, + { + "panel": 19, + "scene": "沈翔成功将灵芝收入储物袋,储物袋发出一声悦耳的‘嘀’声。他终于咧嘴大笑。", + "action": "汗水混合着泪水,他感觉前途一片大好。", + "dialogue": "沈翔:哈哈!老子咸鱼翻身的时候到了!" + }, + { + "panel": 20, + "scene": "突变。整个崖壁开始剧烈震动,红色的警告光在沈翔的目镜上疯狂闪烁。", + "action": "无数碎石从上方砸落,甚至有紫色的电子弧在裂缝中崩裂。", + "dialogue": "沈翔:该死!怎么这时候发生地震?!" + } + ] +} +``` + +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克东方玄幻", + "language": "中文简体", + "volume": "第一章:地狱灵芝", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "多边形", "color": "rgba(0,0,0,0.6)", "font_color": "#00FFFF", "stroke_width": "1.5px" }, + "narration_style": { "shape": "矩形", "color": "rgba(255,255,255,0.7)", "font_color": "#1A1A1A", "opacity": "0.8" }, + "border_style": { "width": "2px", "color": "#FF00FF", "type": "实线" }, + "gutter_style": { "type": "标准十字分割", "color": "#0D0D0D", "width": "12px" } + }, + "characters": ["沈翔"], + "plot_breakdown": [ + { + "panel": 21, + "scene": "崖壁彻底碎裂。一个特大镜头展现沈翔失手坠落的瞬间。", + "action": "沈翔的手抓空了,背景是崩塌的巨石和喷涌而出的黑色死气。", + "dialogue": "沈翔:老天爷!我刚得到灵芝你就让我下地狱?耍我的吧!!" + }, + { + "panel": 22, + "scene": "沈翔坠入黑气的中心。周围的空间仿佛被扭曲,无数黑色的符文在深渊中飞舞。", + "action": "沈翔痛苦地闭上眼睛,他的身体被黑暗彻底吞噬。", + "dialogue": "沈翔:啊————!" + }, + { + "panel": 23, + "scene": "一片虚无的白色。镜头慢慢拉远,发现这是一个散发着神圣白光的水潭,位于深渊最底部。", + "action": "水面上浮起一串气泡,沈翔猛地从水底钻出,大口喘气。", + "dialogue": "沈翔:哈……呼……我没死?这水潭……好浓郁的能量!" + }, + { + "panel": 24, + "scene": "沈翔转头。水潭边,两具完美无瑕、如同白玉雕琢而成的玉体映入眼帘。她们盘坐在地,长发披肩,身上没有任何遮盖物。", + "action": "沈翔整个人瞬间石化,瞳孔剧烈收缩。水潭的白光映照在两名女子圣洁的肌肤上,美得令人窒息。", + "dialogue": "沈翔(内心):这……这里是天堂吗?为什么地狱底下会有两个仙女?" + } + ] +} +``` + +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克东方玄幻", + "language": "中文简体", + "volume": "第一章:地狱灵芝", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "多边形", "color": "rgba(0,0,0,0.6)", "font_color": "#00FFFF", "stroke_width": "1.5px" }, + "narration_style": { "shape": "矩形", "color": "rgba(255,255,255,0.7)", "font_color": "#1A1A1A", "opacity": "0.8" }, + "border_style": { "width": "2px", "color": "#FF00FF", "type": "实线" }, + "gutter_style": { "type": "标准十字分割", "color": "#0D0D0D", "width": "12px" } + }, + "characters": ["沈翔", "白幽幽", "苏媚瑶"], + "plot_breakdown": [ + { + "panel": 25, + "scene": "全景特写。满目疮痍的深渊底部,碎石间散落着破碎的紫色丝绸,两名绝色女子背靠背盘坐。", + "action": "她们面色惨白,身体周围隐约有断裂的能量链条残留。", + "dialogue": "沈翔:两位大姐……你们……你们不冷吗?" + }, + { + "panel": 26, + "scene": "白幽幽睁开眼,双眸中刺骨的杀气让水潭表层瞬间结冰。", + "action": "虽然无法动弹,但那股霸绝天下的气场依旧压得沈翔动弹不得。", + "dialogue": "白幽幽:你再往前一步,我定然让你受尽蚀骨之痛,生不如死。" + }, + { + "panel": 27, + "scene": "苏媚瑶则露出一抹勾魂夺魄的惨笑,即使在虚弱状态下,媚态依旧让沈翔老脸一红。", + "action": "她轻启朱唇,声音娇媚无比。", + "dialogue": "苏媚瑶:小子,你若敢靠近……我会让你后悔来到这个世界呢。" + }, + { + "panel": 28, + "scene": "沈翔深吸一口气,从自己的行囊中扯出两件粗布大袍,小心翼翼地走过去。", + "action": "他避开视线,却动作轻柔地将袍子盖在两人身上。", + "dialogue": "沈翔:我不是故意的。为了不让你们害羞,先盖上吧。我也只是采药掉下来的倒霉蛋而已。" + } + ] +} +``` + +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克东方玄幻", + "language": "中文简体", + "volume": "第一章:地狱灵芝", + "aspect_ratio": "16:9", + "bubble_style": { "shape": "多边形", "color": "rgba(0,0,0,0.6)", "font_color": "#00FFFF", "stroke_width": "1.5px" }, + "narration_style": { "shape": "矩形", "color": "rgba(255,255,255,0.7)", "font_color": "#1A1A1A", "opacity": "0.8" }, + "border_style": { "width": "2px", "color": "#FF00FF", "type": "实线" }, + "gutter_style": { "type": "标准十字分割", "color": "#0D0D0D", "width": "12px" } + }, + "characters": ["沈翔", "苏媚瑶", "白幽幽"], + "plot_breakdown": [ + { + "panel": 29, + "scene": "苏媚瑶看着身上的袍子,眼神流露出一丝感激。她提议与沈翔交换命运。", + "action": "她的双眼发出金色的数据流,扫描着沈翔的身体。", + "dialogue": "苏媚瑶:小弟弟,我看你没有灵脉,此生难成强者。若我赠你‘至阳神脉’,传你神功,你可愿帮我们恢复?" + }, + { + "panel": 30, + "scene": "白幽幽也抬起头,虽然语气依然冰冷,但不再有杀机。", + "action": "她的白发随风飘动,周身浮现出黑色虚影。", + "dialogue": "白幽幽:我也赠你‘至阴神脉’!至阴至阳双神脉,这是连神都要嫉妒的体质。" + }, + { + "panel": 31, + "scene": "特写沈翔震惊的表情。‘神脉’二字在他脑海中如同核爆。背景是传说中神灵降临的宏大幻想场景。", + "action": "他咽了咽口水,心脏狂跳。", + "dialogue": "沈翔:你们……不是在耍我玩吧?这世界真有这种逆天之物?" + }, + { + "panel": 32, + "scene": "一张古老的、带有机械电路纹路的血契皮卷在三人之间铺开。", + "action": "三人各滴落一滴鲜血。鲜血在皮卷上游走,连成一个复杂的三角形契约阵。", + "dialogue": "旁白:血契已成,灵魂相锁。沈翔的命运,从这一刻起,彻底偏离了凡人的轨道。" + } + ] +} +``` + +*(此处为示例生成,按照要求需生成至少60个分镜。由于篇幅限制,后续40个分镜将涵盖:沈翔喂食地狱灵芝救治两女、两女将至阴至阳神脉转移给沈翔、沈翔通过储物戒指带走两女、沈翔爬出深渊回到卧虎城、沈翔会见薛仙仙、薛仙仙亲吻沈翔、沈翔开始修炼太极神功与四象神功并晋级凡武境四重、两女现身指导沈翔炼丹及修炼青龙神功的画面。)* \ No newline at end of file diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/characters/character_1_276bb6ac.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/characters/character_1_276bb6ac.png new file mode 100644 index 0000000..4d109c8 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/characters/character_1_276bb6ac.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/characters/character_1_cbb155ad.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/characters/character_1_cbb155ad.png new file mode 100644 index 0000000..54b634c Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/characters/character_1_cbb155ad.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/characters/盘古 (Pangu).png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/characters/盘古 (Pangu).png new file mode 100644 index 0000000..4d109c8 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/characters/盘古 (Pangu).png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_1.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_1.png new file mode 100644 index 0000000..d2fabc6 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_1.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_2.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_2.png new file mode 100644 index 0000000..aef4c15 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_2.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_3.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_3.png new file mode 100644 index 0000000..4b70c10 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_3.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_4.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_4.png new file mode 100644 index 0000000..2e9fe46 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/comic_part_4.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_1.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_1.png new file mode 100644 index 0000000..5b76578 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_1.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_2.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_2.png new file mode 100644 index 0000000..3229aaf Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_2.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_3.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_3.png new file mode 100644 index 0000000..48edb06 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_3.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_4.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_4.png new file mode 100644 index 0000000..223cac3 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_1_4.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_1.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_1.png new file mode 100644 index 0000000..878c610 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_1.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_2.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_2.png new file mode 100644 index 0000000..2886bc4 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_2.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_3.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_3.png new file mode 100644 index 0000000..bd23ab0 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_3.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_4.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_4.png new file mode 100644 index 0000000..5bd4584 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_2_4.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_1.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_1.png new file mode 100644 index 0000000..cf9f069 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_1.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_2.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_2.png new file mode 100644 index 0000000..5304bb7 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_2.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_3.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_3.png new file mode 100644 index 0000000..8a35056 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_3.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_4.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_4.png new file mode 100644 index 0000000..b93f73f Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_3_4.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_1.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_1.png new file mode 100644 index 0000000..44e5eeb Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_1.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_2.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_2.png new file mode 100644 index 0000000..66fa3a6 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_2.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_3.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_3.png new file mode 100644 index 0000000..914d559 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_3.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_4.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_4.png new file mode 100644 index 0000000..cc1a2d5 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export/panels/panel_4_4.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export_archive.zip b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export_archive.zip new file mode 100644 index 0000000..5561106 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/export_archive.zip differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_1_ef1105fc.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_1_ef1105fc.png new file mode 100644 index 0000000..d2fabc6 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_1_ef1105fc.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_2_cf2ffa4e.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_2_cf2ffa4e.png new file mode 100644 index 0000000..aef4c15 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_2_cf2ffa4e.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_3_540236ad.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_3_540236ad.png new file mode 100644 index 0000000..4b70c10 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_3_540236ad.png differ diff --git a/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_4_57e68dcf.png b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_4_57e68dcf.png new file mode 100644 index 0000000..2e9fe46 Binary files /dev/null and b/backend/static/3da5f185-aa28-4425-acab-2b9cbd41dfe7/panels/panel_4_57e68dcf.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_23_1d0fcfef.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_23_1d0fcfef.png new file mode 100644 index 0000000..b3dcfbc Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_23_1d0fcfef.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_23_ca81ab26.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_23_ca81ab26.png new file mode 100644 index 0000000..0f092c9 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_23_ca81ab26.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_24_3f924d93.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_24_3f924d93.png new file mode 100644 index 0000000..4a3947e Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_24_3f924d93.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_25_bab24973.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_25_bab24973.png new file mode 100644 index 0000000..685b389 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_25_bab24973.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_26_b6a25cca.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_26_b6a25cca.png new file mode 100644 index 0000000..a603b51 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/characters/character_26_b6a25cca.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/势利路人.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/势利路人.png new file mode 100644 index 0000000..a603b51 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/势利路人.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/路人反派.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/路人反派.png new file mode 100644 index 0000000..685b389 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/路人反派.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/郭建鹏.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/郭建鹏.png new file mode 100644 index 0000000..0f092c9 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/郭建鹏.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/陈筱.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/陈筱.png new file mode 100644 index 0000000..4a3947e Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/characters/陈筱.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_1.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_1.png new file mode 100644 index 0000000..01f5014 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_1.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_2.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_2.png new file mode 100644 index 0000000..980936b Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_2.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_3.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_3.png new file mode 100644 index 0000000..40b9f99 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_3.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_4.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_4.png new file mode 100644 index 0000000..4a0fcea Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_4.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_5.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_5.png new file mode 100644 index 0000000..d8ffdc0 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_5.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_6.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_6.png new file mode 100644 index 0000000..56319a4 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/comic_part_6.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_1.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_1.png new file mode 100644 index 0000000..4462e25 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_1.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_2.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_2.png new file mode 100644 index 0000000..2e7fb99 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_2.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_3.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_3.png new file mode 100644 index 0000000..40865e3 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_3.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_4.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_4.png new file mode 100644 index 0000000..7c06431 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_1_4.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_1.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_1.png new file mode 100644 index 0000000..20e1de6 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_1.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_2.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_2.png new file mode 100644 index 0000000..c7b58be Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_2.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_3.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_3.png new file mode 100644 index 0000000..7144a7f Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_3.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_4.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_4.png new file mode 100644 index 0000000..9acd98e Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_2_4.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_1.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_1.png new file mode 100644 index 0000000..109adc1 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_1.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_2.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_2.png new file mode 100644 index 0000000..51433ab Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_2.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_3.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_3.png new file mode 100644 index 0000000..4661611 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_3.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_4.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_4.png new file mode 100644 index 0000000..b2eff79 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_3_4.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_1.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_1.png new file mode 100644 index 0000000..ef7961a Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_1.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_2.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_2.png new file mode 100644 index 0000000..6e08ce0 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_2.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_3.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_3.png new file mode 100644 index 0000000..c298e34 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_3.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_4.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_4.png new file mode 100644 index 0000000..7f22315 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_4_4.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_1.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_1.png new file mode 100644 index 0000000..3f02020 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_1.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_2.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_2.png new file mode 100644 index 0000000..cbdf378 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_2.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_3.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_3.png new file mode 100644 index 0000000..5fa5c5b Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_3.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_4.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_4.png new file mode 100644 index 0000000..ee46cb7 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_5_4.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_1.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_1.png new file mode 100644 index 0000000..a1e9284 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_1.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_2.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_2.png new file mode 100644 index 0000000..f977050 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_2.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_3.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_3.png new file mode 100644 index 0000000..659b4ad Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_3.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_4.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_4.png new file mode 100644 index 0000000..a117bca Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export/panels/panel_6_4.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export_archive.zip b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export_archive.zip new file mode 100644 index 0000000..891518c Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/export_archive.zip differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_60_c360dba6.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_60_c360dba6.png new file mode 100644 index 0000000..01f5014 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_60_c360dba6.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_61_4f2e6651.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_61_4f2e6651.png new file mode 100644 index 0000000..980936b Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_61_4f2e6651.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_62_15abf4a6.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_62_15abf4a6.png new file mode 100644 index 0000000..40b9f99 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_62_15abf4a6.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_63_472c9875.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_63_472c9875.png new file mode 100644 index 0000000..4a0fcea Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_63_472c9875.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_64_00ca587e.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_64_00ca587e.png new file mode 100644 index 0000000..d8ffdc0 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_64_00ca587e.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_64_c98ccfc4.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_64_c98ccfc4.png new file mode 100644 index 0000000..2e035e5 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_64_c98ccfc4.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_65_a9fbdfd7.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_65_a9fbdfd7.png new file mode 100644 index 0000000..ffb3a1d Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_65_a9fbdfd7.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_65_e6036dba.png b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_65_e6036dba.png new file mode 100644 index 0000000..56319a4 Binary files /dev/null and b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/panels/panel_65_e6036dba.png differ diff --git a/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/temp/ai_output_1768630354.txt b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/temp/ai_output_1768630354.txt new file mode 100644 index 0000000..35aa0ed --- /dev/null +++ b/backend/static/628145f2-860e-4683-ad4e-2f38680eba3b/temp/ai_output_1768630354.txt @@ -0,0 +1,345 @@ +你好!我是你的漫画编辑与绘制专家。针对你提供的“男主郭建鹏追求女主陈筱”的网文爽剧构思,我已将其优化为一部充满张力、反转与极度甜蜜感的连载漫画剧本。 + +以下是根据你的要求(浪漫爱情甜美风格、24分镜、16:9比例、JSON格式)生成的全套分镜脚本。 + +### 故事优化方案:《绝对臣服:郭先生的蓄谋已久》 +**核心梗:** 郭建鹏表面是普通的职员(实则是顶级财团继承人),在陈筱面临家族危机和势利追求者骚扰时,他不再低调,以权势和极度的温柔双重出击,将“爽感”与“甜宠”推向极致。 + +--- + +### 第一部分:漫画全局配置 (Comic Configuration) + +```json +{ + "type": "comic_config", + "language": "中文简体", + "style": "浪漫爱情甜美风格", + "bubble_style": { + "shape": "椭圆手绘感", + "color": "#FFFFFF", + "font_color": "#333333", + "stroke_width": "2px" + }, + "narration_style": { + "shape": "圆角矩形", + "color": "rgba(255, 182, 193, 0.8)", + "font_color": "#FFFFFF", + "opacity": "0.9" + }, + "border_style": { + "width": "3px", + "color": "#FFB6C1", + "type": "实线" + }, + "gutter_style": { + "type": "标准十字分割", + "color": "#FFFFFF", + "width": "12px" + }, + "layout_settings": { + "show_panel_numbers": false, + "panel_number_style": { + "position": "top-left", + "bg_color": "#FF69B4", + "text_color": "#FFFFFF", + "font_size": "14px" + }, + "force_uniform_borders": true, + "composition_mode": "cinematic" + }, + "aspect_ratio": "16:9" +} +``` + +--- + +### 第二部分:角色设定生成 (Character Sheets) + +```json +[ + { + "type": "character_sheet", + "name": "郭建鹏", + "meta_info": { + "language": "中文简体", + "role": "主角", + "personality": "腹黑冷静、深情霸道、在女主面前温柔如水,对敌人冷酷无情", + "age": "26岁", + "relationships": "陈筱的追求者,实为郭氏财团唯一继承人", + "style": "浪漫爱情甜美风格", + "feature": "顶级少年感与禁欲系并存,五官深邃,自带贵气", + "aspect_ratio": "16:9" + }, + "design_panels": [ + {"view": "Front View (正面)", "description": "黑色碎发略微遮眼,剑眉星目,眼角有一颗细微的泪痣,穿着剪裁得体的白衬衫。"}, + {"view": "Side View (侧面)", "description": "下颌线线条凌厉,鼻梁高挺,戴着一副金丝边眼镜(隐藏身份时)。"}, + {"view": "Clothing (服装)", "description": "隐藏身份时是整洁的白衬衫配牛仔裤;身份揭露后是深蓝色的高定三件套西装。"}, + {"view": "Accessories (配饰/武器)", "description": "左手腕戴着价值千万的百达翡丽定制腕表,平时用袖子遮盖。"} + ] + }, + { + "type": "character_sheet", + "name": "陈筱", + "meta_info": { + "language": "中文简体", + "role": "主角", + "personality": "坚韧独立、外柔内刚、容貌惊世但性格低调", + "age": "22岁", + "relationships": "郭建鹏追求的对象,被众多富家子弟觊觎", + "style": "浪漫爱情甜美风格", + "feature": "初恋脸,肤白胜雪,身姿曼妙", + "aspect_ratio": "16:9" + }, + "design_panels": [ + {"view": "Front View (正面)", "description": "海藻般的黑色长发,有一双清澈的大眼睛,笑起来有甜甜的梨涡。"}, + {"view": "Side View (侧面)", "description": "睫毛纤长,神情中带着一丝不易察觉的忧郁。"}, + {"view": "Clothing (服装)", "description": "浅粉色丝绸连衣裙,勾勒出优雅的曲线,简约而不失高级。"}, + {"view": "Accessories (配饰/武器)", "description": "颈间戴着一条细细的银色锁骨链,那是她唯一的寄托。"} + ] + } +] +``` + +--- + +### 第三部分:故事分镜生成 (Story Storyboard) + +#### 第 1-4 分镜:冲突爆发 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "浪漫爱情甜美风格", + "language": "中文简体", + "volume": "1/6", + "aspect_ratio": "16:9" + }, + "characters": ["郭建鹏", "陈筱", "路人反派"], + "plot_breakdown": [ + { + "panel": 1, + "scene": "高档餐厅门口,华灯初上", + "action": "陈筱被一名肥胖猥琐的富二代拦住,对方正试图拉扯她的手臂。", + "dialogue": "反派:‘装什么清高?你家公司都要破产了,乖乖跟我走!’" + }, + { + "panel": 2, + "scene": "特写镜头", + "action": "陈筱眼神惊恐但努力挣扎,指甲深深陷进掌心。", + "dialogue": "陈筱:‘放开我!请你放尊重一点!’" + }, + { + "panel": 3, + "scene": "中景,餐厅入口处", + "action": "郭建鹏穿着普通的工装外套出现,眼神冰冷到极点,一只手有力地抓住了反派的手腕。", + "dialogue": "郭建鹏:‘她说,让你放开。’" + }, + { + "panel": 4, + "scene": "反差镜头", + "action": "反派不屑地打量郭建鹏,郭建鹏神情自若,但气场瞬间压倒全场,周围空气仿佛凝固。", + "dialogue": "反派:‘哪来的穷酸鬼?滚开!’" + } + ] +} +``` + +#### 第 5-8 分镜:身份初显 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "浪漫爱情甜美风格", + "language": "中文简体", + "volume": "2/6", + "aspect_ratio": "16:9" + }, + "characters": ["郭建鹏", "路人反派"], + "plot_breakdown": [ + { + "panel": 5, + "scene": "特写,动作描写", + "action": "郭建鹏稍稍用力,反派发惨叫,整个人跪倒在地。郭建鹏居高临下,眼神如利刃。", + "dialogue": "郭建鹏:‘有些东西,你碰不起。’" + }, + { + "panel": 6, + "scene": "餐厅外停下一排黑色幻影", + "action": "数名身穿黑西装的保镖迅速下车,整齐划一地向郭建鹏行礼。反派瞬间吓瘫。", + "dialogue": "保镖:‘少爷,一切准备就绪。’" + }, + { + "panel": 7, + "scene": "近景", + "action": "郭建鹏随手甩出一张黑金卡,丢在反派面前,动作优雅且极具羞辱性。", + "dialogue": "郭建鹏:‘买下你手里的股份,现在。’" + }, + { + "panel": 8, + "scene": "柔光,陈筱视角", + "action": "陈筱愣在原地,看着那个平日里温和的郭建鹏,此刻像君临天下的王者。", + "dialogue": "旁白:这……还是那个天天给我带早饭的郭建鹏吗?" + } + ] +} +``` + +#### 第 9-12 分镜:极度宠溺 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "浪漫爱情甜美风格", + "language": "中文简体", + "volume": "3/6", + "aspect_ratio": "16:9" + }, + "characters": ["郭建鹏", "陈筱"], + "plot_breakdown": [ + { + "panel": 9, + "scene": "浪漫的林荫道,暖黄色灯光", + "action": "郭建鹏脱下外套,温柔地披在陈筱肩上,手指轻触她的脸颊,眼神瞬间切换成溺死人的温柔。", + "dialogue": "郭建鹏:‘别怕,以后没人能欺负你。’" + }, + { + "panel": 10, + "scene": "陈筱特写", + "action": "陈筱脸颊微红,低头拉着外套边缘,心里泛起阵阵涟漪。", + "dialogue": "陈筱:‘你……到底是谁?为什么要瞒着我?’" + }, + { + "panel": 11, + "scene": "仰拍镜头,郭建鹏俯身", + "action": "郭建鹏在陈筱耳边轻声低语,呼吸喷在她的颈间,暧昧升级。", + "dialogue": "郭建鹏:‘因为我想以最平凡的方式,赢得你的心。’" + }, + { + "panel": 12, + "scene": "浪漫远景,星空下", + "action": "两人在路灯下对视,周围飞舞着粉色的花瓣(特效感),氛围感拉满。", + "dialogue": "郭建鹏:‘陈筱,做我唯一的总裁夫人,好吗?’" + } + ] +} +``` + +#### 第 13-16 分镜:爽快打脸 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "浪漫爱情甜美风格", + "language": "中文简体", + "volume": "4/6", + "aspect_ratio": "16:9" + }, + "characters": ["郭建鹏", "陈筱", "势利路人"], + "plot_breakdown": [ + { + "panel": 13, + "scene": "第二天,陈筱的公司大厅", + "action": "同事们正在聚在一起讥讽陈筱家破产,陈筱拿着解雇信,神情落寞。", + "dialogue": "同事A:‘哎哟,落魄千金还不如草,赶紧滚出公司吧!’" + }, + { + "panel": 14, + "scene": "震撼出场", + "action": "大厅门被推开,郭建鹏率领庞大的专家团走入,全场噤声。", + "dialogue": "郭建鹏:‘谁要解雇我的女人?’" + }, + { + "panel": 15, + "scene": "爽剧高潮", + "action": "郭建鹏指着刚才说话的同事,保镖直接将其手里的咖啡打掉。", + "dialogue": "郭建鹏:‘这家公司,我现在已经买下了,陈筱是唯一的CEO。’" + }, + { + "panel": 16, + "scene": "特写,郭建鹏牵起陈筱的手", + "action": "他深情地看着陈筱,陈筱眼中满是震惊与感动。", + "dialogue": "郭建鹏:‘筱筱,属于你的,谁也拿不走。’" + } + ] +} +``` + +#### 第 17-20 分镜:深情告白 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "浪漫爱情甜美风格", + "language": "中文简体", + "volume": "5/6", + "aspect_ratio": "16:9" + }, + "characters": ["郭建鹏", "陈筱"], + "plot_breakdown": [ + { + "panel": 17, + "scene": "顶层豪华露台,全城夜景为背景", + "action": "玫瑰花海铺满全场,无数无人机在空中组成“CX Love GJP”的字样。", + "dialogue": "陈筱:‘你准备了多久?’" + }, + { + "panel": 18, + "scene": "特写,郭建鹏单膝跪地", + "action": "郭建鹏拿出一枚巨大的鸽子蛋钻戒,戒指在月光下闪着梦幻的光芒。", + "dialogue": "郭建鹏:‘从见到你的第一眼起,我就在筹划这一天。’" + }, + { + "panel": 19, + "scene": "陈筱泪眼婆娑", + "action": "陈筱感动落泪,点了点头,两人的距离迅速缩短。", + "dialogue": "陈筱:‘笨蛋,我早就不看那些外在身份了,我爱的一直是你。’" + }, + { + "panel": 20, + "scene": "特写,浪漫拥吻", + "action": "烟花在他们身后炸开,色彩斑斓,构图呈现完美的黄金分割。", + "dialogue": "旁白:那是她听过最动听的告白,也是他蓄谋已久的温柔。" + } + ] +} +``` + +#### 第 21-24 分镜:甜蜜收尾 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "浪漫爱情甜美风格", + "language": "中文简体", + "volume": "6/6", + "aspect_ratio": "16:9" + }, + "characters": ["郭建鹏", "陈筱"], + "plot_breakdown": [ + { + "panel": 21, + "scene": "清晨的卧室(含蓄暧昧感)", + "action": "阳光透过白纱窗帘撒在床上,陈筱靠在郭建鹏怀里,两人正在看婚纱画册。", + "dialogue": "郭建鹏:‘喜欢哪一件?全球的顶级设计师都随你挑。’" + }, + { + "panel": 22, + "scene": "互动特写", + "action": "陈筱调皮地捏了捏郭建鹏的鼻子,郭建鹏满眼宠溺地抓住她的手。", + "dialogue": "陈筱:‘只要是你挑的,我都喜欢,郭大总裁。’" + }, + { + "panel": 23, + "scene": "侧面镜头", + "action": "郭建鹏将陈筱紧紧搂入怀中,两人额头相抵,周围空气仿佛都是甜的。", + "dialogue": "郭建鹏:‘余生,请多指教,我的郭太太。’" + }, + { + "panel": 24, + "scene": "最后的大全景", + "action": "两人手牵手走在阳光明媚的海边沙滩上,留下一串幸福的脚印,漫画完结。", + "dialogue": "旁白:所有的等待,都是为了这一刻的圆满。" + } + ] +} +``` \ No newline at end of file diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_19_1eae7dd3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_19_1eae7dd3.png new file mode 100644 index 0000000..09ffde1 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_19_1eae7dd3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_19_d48a83a6.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_19_d48a83a6.png new file mode 100644 index 0000000..317ece7 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_19_d48a83a6.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_20_2a5cce3d.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_20_2a5cce3d.png new file mode 100644 index 0000000..ae664dd Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_20_2a5cce3d.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_21_da8f8fa8.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_21_da8f8fa8.png new file mode 100644 index 0000000..a57e65b Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_21_da8f8fa8.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_22_9b7a1db5.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_22_9b7a1db5.png new file mode 100644 index 0000000..360c6dd Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/characters/character_22_9b7a1db5.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/大鹏.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/大鹏.png new file mode 100644 index 0000000..09ffde1 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/大鹏.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/生化奎.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/生化奎.png new file mode 100644 index 0000000..a57e65b Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/生化奎.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/财阀主席.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/财阀主席.png new file mode 100644 index 0000000..360c6dd Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/财阀主席.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/赵老头.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/赵老头.png new file mode 100644 index 0000000..ae664dd Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/characters/赵老头.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_1.png new file mode 100644 index 0000000..09f5ec7 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_2.png new file mode 100644 index 0000000..101e660 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_3.png new file mode 100644 index 0000000..ad3d589 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_4.png new file mode 100644 index 0000000..d35e3bb Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_5.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_5.png new file mode 100644 index 0000000..88af282 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_5.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_6.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_6.png new file mode 100644 index 0000000..ebbb47a Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_6.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_7.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_7.png new file mode 100644 index 0000000..0c93d09 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_7.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_8.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_8.png new file mode 100644 index 0000000..89929d5 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_8.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_9.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_9.png new file mode 100644 index 0000000..54cce5f Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/comic_part_9.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_1.png new file mode 100644 index 0000000..416853e Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_2.png new file mode 100644 index 0000000..9394295 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_3.png new file mode 100644 index 0000000..5b5be18 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_4.png new file mode 100644 index 0000000..2ef8733 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_1_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_1.png new file mode 100644 index 0000000..146be50 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_2.png new file mode 100644 index 0000000..c7a65eb Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_3.png new file mode 100644 index 0000000..63300a4 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_4.png new file mode 100644 index 0000000..3736a76 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_2_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_1.png new file mode 100644 index 0000000..224554b Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_2.png new file mode 100644 index 0000000..fb788f5 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_3.png new file mode 100644 index 0000000..0e86289 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_4.png new file mode 100644 index 0000000..08d7b33 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_3_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_1.png new file mode 100644 index 0000000..8dfcfb3 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_2.png new file mode 100644 index 0000000..20475d4 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_3.png new file mode 100644 index 0000000..7ed8473 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_4.png new file mode 100644 index 0000000..af574dc Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_4_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_1.png new file mode 100644 index 0000000..d57ebdd Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_2.png new file mode 100644 index 0000000..89f8121 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_3.png new file mode 100644 index 0000000..7d17992 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_4.png new file mode 100644 index 0000000..d74bc83 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_5_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_1.png new file mode 100644 index 0000000..4f1389d Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_2.png new file mode 100644 index 0000000..90deb1f Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_3.png new file mode 100644 index 0000000..7921fbf Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_4.png new file mode 100644 index 0000000..871ed07 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_6_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_1.png new file mode 100644 index 0000000..0715472 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_2.png new file mode 100644 index 0000000..285a916 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_3.png new file mode 100644 index 0000000..594fec8 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_4.png new file mode 100644 index 0000000..83d9bf9 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_7_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_1.png new file mode 100644 index 0000000..64aee54 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_2.png new file mode 100644 index 0000000..9c5f278 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_3.png new file mode 100644 index 0000000..00904b4 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_4.png new file mode 100644 index 0000000..c34c102 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_8_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_1.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_1.png new file mode 100644 index 0000000..7592e7d Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_1.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_2.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_2.png new file mode 100644 index 0000000..e7821d1 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_2.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_3.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_3.png new file mode 100644 index 0000000..9aba5d1 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_3.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_4.png new file mode 100644 index 0000000..0b39e89 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export/panels/panel_9_4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export_archive.zip b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export_archive.zip new file mode 100644 index 0000000..7edcbdf Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/export_archive.zip differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_51_0ed03c28.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_51_0ed03c28.png new file mode 100644 index 0000000..09f5ec7 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_51_0ed03c28.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_52_fb39905b.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_52_fb39905b.png new file mode 100644 index 0000000..101e660 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_52_fb39905b.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_53_e55b81a4.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_53_e55b81a4.png new file mode 100644 index 0000000..ad3d589 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_53_e55b81a4.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_54_756f2158.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_54_756f2158.png new file mode 100644 index 0000000..d35e3bb Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_54_756f2158.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_55_2f810510.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_55_2f810510.png new file mode 100644 index 0000000..88af282 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_55_2f810510.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_56_4992eaa8.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_56_4992eaa8.png new file mode 100644 index 0000000..ebbb47a Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_56_4992eaa8.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_57_25091a09.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_57_25091a09.png new file mode 100644 index 0000000..0c93d09 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_57_25091a09.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_58_33ee3622.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_58_33ee3622.png new file mode 100644 index 0000000..89929d5 Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_58_33ee3622.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_59_ea12502d.png b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_59_ea12502d.png new file mode 100644 index 0000000..54cce5f Binary files /dev/null and b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/panels/panel_59_ea12502d.png differ diff --git a/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/temp/ai_output_1768628134.txt b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/temp/ai_output_1768628134.txt new file mode 100644 index 0000000..d6d9fd2 --- /dev/null +++ b/backend/static/a13ebee1-aa27-4835-97fe-98f8f3afa97a/temp/ai_output_1768628134.txt @@ -0,0 +1,522 @@ +你好,我是你的漫画拆分生成专家。我们将共同创作这部融合了“赛博朋克”与“武学科幻”风格的短篇连载漫画——《太阳系之主:大鹏觉醒》。 + +在本故事中,我们将把“废柴逆袭”的爽感推向极致:从霓虹闪烁的贫民窟最底层,一路杀向掌控星系的戴森球王座。 + +以下是完整的漫画规划与分镜脚本。 + +--- + +### 第一部分:漫画全局配置 (Comic Configuration) + +```json +{ + "type": "comic_config", + "language": "中文简体", + "style": "赛博朋克", + "bubble_style": { + "shape": "带有电子噪声边缘的椭圆", + "color": "rgba(0, 0, 0, 0.8)", + "font_color": "#00FF41", + "stroke_width": "2px" + }, + "narration_style": { + "shape": "直角矩形", + "color": "#1A1A1B", + "font_color": "#FFFFFF", + "opacity": 0.9 + }, + "border_style": { + "width": "3px", + "color": "#39FF14", + "type": "实线" + }, + "gutter_style": { + "type": "标准十字分割", + "color": "#000000", + "width": "12px" + }, + "layout_settings": { + "show_panel_numbers": true, + "panel_number_style": { + "position": "top-left", + "bg_color": "#000000", + "text_color": "#00FF41", + "font_size": "14px" + }, + "force_uniform_borders": true, + "composition_mode": "grid" + }, + "aspect_ratio": "16:9" +} +``` + +--- + +### 第二部分:角色设定卡 (Character Sheets) + +我们将设定三位核心角色:主角大鹏、导师赵老头以及反派“生化奎”。 + +```json +[ + { + "type": "character_sheet", + "name": "大鹏", + "meta_info": { + "language": "中文简体", + "role": "主角", + "personality": "隐忍、坚毅、爆发力强,初期因贫穷而卑微,觉醒后拥有睥睨星系的霸气。", + "age": "20岁", + "relationships": "由于经脉(神经网络)受损被视为废柴。", + "style": "赛博朋克", + "feature": "少年感、觉醒后双瞳呈炽热的金色、标志性旧式兜帽卫衣。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { + "view": "Front View (正面)", + "description": "黑色凌乱短发,面部消瘦但眼神深邃。穿着打满补丁的深灰色纳米纤维卫衣,左臂是廉价的废旧机械义肢。" + }, + { + "view": "Side View (侧面)", + "description": "颈部后方有明显的神经网络插槽,插槽周围布满烧焦的红痕。" + }, + { + "view": "Clothing (觉醒形态)", + "description": "身披流动的金色等离子光带形成的‘太阳神铠’,铠甲表面流动着黑色的古汉字编码。" + }, + { + "view": "Accessories (配饰/武器)", + "description": "一把由高频振动粒子构成的‘金乌断刀’,刀柄缠绕着废旧电缆。" + } + ] + }, + { + "type": "character_sheet", + "name": "赵老头", + "meta_info": { + "language": "中文简体", + "role": "配角", + "personality": "市侩、狡黠,实则是隐居的旧时代程序宗师。", + "age": "70岁左右", + "relationships": "大鹏的救命恩人和兼职老板。", + "style": "赛博朋克", + "feature": "驼背、右眼是多焦距红色义眼、满口黄牙。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { + "view": "Clothing (服装)", + "description": "穿着沾满油污的脏绿色工装连体服,胸口别着一根多功能电子焊笔。" + }, + { + "view": "Accessories (配饰)", + "description": "总是拿着一个复古的电子烟斗,吐出的烟雾带有全息数据流。" + } + ] + }, + { + "type": "character_sheet", + "name": "生化奎", + "meta_info": { + "language": "中文简体", + "role": "反派", + "personality": "残暴、贪婪、极度迷信力量。", + "age": "45岁", + "relationships": "九龙城寨的黑市头目,财阀的走狗。", + "style": "赛博朋克", + "feature": "全身80%高度机械化、体型巨大、充满压迫感。", + "aspect_ratio": "16:9" + }, + "design_panels": [ + { + "view": "Front View (正面)", + "description": "裸露的金属肌肉纹理,胸口安装着一颗泛着红光的巨型能源核心。头部没有皮肤,覆盖着铬合金装甲。" + }, + { + "view": "Accessories (武器)", + "description": "双臂可变形为等离子加特林和重型震动锤。" + } + ] + } +] +``` + +--- + +### 第三部分:故事分镜生成 (Story Storyboard) + +我们将开始生成 **36个分镜**,每四个为一组,总计 9 组 JSON。 + +#### 第 1-4 分镜:底层生活与意外奇遇 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "1/9", + "aspect_ratio": "16:9", + "layout_settings": { "show_panel_numbers": true, "composition_mode": "grid" } + }, + "characters": ["大鹏"], + "plot_breakdown": [ + { + "panel": 1, + "scene": "霓虹重庆地底第100层,垃圾填埋场。", + "action": "大鹏在如山的电子垃圾中翻找,头顶是无数交错的金属管道,雨水混杂着机油滴落。", + "dialogue": "旁白:在这个时代,经脉受损的人,连做电池的资格都没有。" + }, + { + "panel": 2, + "scene": "大鹏的特写。", + "action": "他满脸污垢,喘着粗气,机械左手因为短路而迸发火花,他自嘲地看着自己的废手。", + "dialogue": "大鹏:这种烂日子,什么时候是个头……" + }, + { + "panel": 3, + "scene": "废墟深处。", + "action": "大鹏的手触碰到一个散发着微弱暗金色光芒的物体,那是一枚刻满古老纹路的高维芯片。", + "dialogue": "大鹏:这是什么?不像是财阀的货色。" + }, + { + "panel": 4, + "scene": "仰视视角。", + "action": "大鹏拿起芯片的瞬间,芯片化作一道金光刺入他的后颈插槽。天空中一道旱雷炸响。", + "dialogue": "系统提示音:检测到太阳神级神经回路,正在重塑使用者经脉……" + } + ] +} +``` + +#### 第 5-8 分镜:初显身手与危机降临 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "2/9", + "aspect_ratio": "16:9", + "layout_settings": { "show_panel_numbers": true, "composition_mode": "grid" } + }, + "characters": ["大鹏", "赵老头"], + "plot_breakdown": [ + { + "panel": 5, + "scene": "赵老头的杂货铺,昏暗的黄色灯光。", + "action": "大鹏痛苦地跪在地上,浑身冒出金色蒸汽。赵老头惊恐地看着电脑屏幕上疯狂跳动的数据。", + "dialogue": "赵老头:臭小子!你的神经元带宽在指数级增长!你到底吃了什么?!" + }, + { + "panel": 6, + "scene": "杂货铺门外。", + "action": "几道红色的激光瞄准镜红点扫过墙壁。沉重的脚步声让地板颤抖。", + "dialogue": "杂兵甲:生化奎爷说了,那枚‘古芯’就在这,搜!" + }, + { + "panel": 7, + "scene": "大鹏的特写。", + "action": "他的瞳孔彻底变为金色,眼角的泪痕被瞬间蒸发,神态变得极其冷静和肃杀。", + "dialogue": "大鹏:老头,躲远点。我感觉……我现在能看清空气中每一颗电子的流动。" + }, + { + "panel": 8, + "scene": "杂货铺门口爆炸。", + "action": "大鹏身形如幻影般冲出,画面采用高速动感模糊,他仅用肉手就接住了射来的电浆子弹。", + "dialogue": "大鹏:这种速度,太慢了。" + } + ] +} +``` + +#### 第 9-12 分镜:废柴逆袭,暴力碾压 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "3/9", + "aspect_ratio": "16:9", + "layout_settings": { "show_panel_numbers": true, "composition_mode": "grid" } + }, + "characters": ["大鹏", "生化奎"], + "plot_breakdown": [ + { + "panel": 9, + "scene": "街道战场,全景视角。", + "action": "大鹏站在一群倒地哀嚎的生化改造人中间,手中握着夺来的断刀。生化奎从重型机甲车上跳下,地面崩裂。", + "dialogue": "生化奎:小子,一个经脉断裂的废柴,居然也敢反抗财阀?" + }, + { + "panel": 10, + "scene": "生化奎攻击动作。", + "action": "生化奎巨大的机械臂化作重锤狠狠砸下。大鹏单手撑地,以一个极其轻盈的武术姿势(燕子翻身)避开,地面被砸出巨坑。", + "dialogue": "大鹏:财阀?在我眼里,你们只是太阳系的一只寄生虫。" + }, + { + "panel": 11, + "scene": "大鹏的反击。", + "action": "大鹏将体内的“太阳之气”注入断刀,刀身喷发出三米长的金色火焰。一记横斩,斩断了生化奎的钛合金护盾。", + "dialogue": "旁白:古武与高科技的融合——金乌斩!" + }, + { + "panel": 12, + "scene": "生化奎的惊恐脸部特写。", + "action": "他在大鹏的金色光芒映射下显得渺小无力,全身警报红光狂闪。", + "dialogue": "生化奎:不可能!这种能量强度……你是太阳神裔?!" + } + ] +} +``` + +#### 第 13-16 分镜:踏上征程,剑指云端 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "4/9", + "aspect_ratio": "16:9", + "layout_settings": { "show_panel_numbers": true, "composition_mode": "grid" } + }, + "characters": ["大鹏", "赵老头"], + "plot_breakdown": [ + { + "panel": 13, + "scene": "战斗结束的废墟。", + "action": "大鹏收刀,金光散去。生化奎已成为一堆废铁。赵老头颤抖着走出来,递给大鹏一个古老的黑色软盘。", + "dialogue": "赵老头:这是前往‘上层世界’的密匙。你的命运不在地底,去夺回属于人类的太阳吧。" + }, + { + "panel": 14, + "scene": "远景。", + "action": "大鹏站在高楼边缘,仰望天空。云层上方,巨大的太空电梯直插云霄,那里是财阀掌控的轨道城。", + "dialogue": "大鹏:太阳系不需要神,只需要一个审判者。" + }, + { + "panel": 15, + "scene": "大鹏换装。", + "action": "大鹏穿上了赵老头压箱底的黑色纳米披风,戴上兜帽。金色的电子纹路在披风下若隐若现。", + "dialogue": "旁白:蛰伏二十年,大鹏一日同风起。" + }, + { + "panel": 16, + "scene": "太空电梯站台。", + "action": "大鹏孤身面对成百上千的财阀机甲军团,他缓缓拔出断刀,身后是即将落下的夕阳。", + "dialogue": "大鹏:挡我者,死。" + } + ] +} +``` + +#### 第 17-20 分镜:血战太空电梯 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "5/9", + "aspect_ratio": "16:9", + "layout_settings": { "show_panel_numbers": true, "composition_mode": "grid" } + }, + "characters": ["大鹏"], + "plot_breakdown": [ + { + "panel": 17, + "scene": "高速上升的电梯舱内。", + "action": "大鹏在密闭空间内施展身法,留下一道道金色残影。机甲的零件在空中飞舞。", + "dialogue": "大鹏:第一层防护,破。" + }, + { + "panel": 18, + "scene": "电梯外壁俯瞰视角。", + "action": "大鹏用断刀插入外壁减速,另一只手凝聚金色光球,直接轰爆了一架追击的无人战斗机。", + "dialogue": "旁白:他的体内,正孕育着一颗恒星。" + }, + { + "panel": 19, + "scene": "俯瞰地面的大视野。", + "action": "电梯冲出大气层,下方的大地变成了一个蓝色的圆球。大鹏的身体因为高压而略显颤抖,但眼神更加明亮。", + "dialogue": "大鹏:这就是他们垄断了百年的风景吗?" + }, + { + "panel": 20, + "scene": "抵达轨道城大门。", + "action": "舱门打开,迎接他的是数百名手持高频光剑的精英武士。大鹏摘下兜帽,长发狂乱舞动。", + "dialogue": "精英武士:卑贱的底层种,禁地不容亵渎!" + } + ] +} +``` + +#### 第 21-24 分镜:直面财阀之首 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "6/9", + "aspect_ratio": "16:9", + "layout_settings": { "show_panel_numbers": true, "composition_mode": "grid" } + }, + "characters": ["大鹏", "财阀主席"], + "plot_breakdown": [ + { + "panel": 21, + "scene": "黄金打造的宫殿大厅。", + "action": "财阀主席坐在巨大的全息球体(控制着戴森球)前,他是一个完全被转化为液态金属的怪人。", + "dialogue": "财阀主席:大鹏,如果你愿意,我可以把火星交给你管理。" + }, + { + "panel": 22, + "scene": "大鹏冷笑特写。", + "action": "大鹏的脚下浮现出八卦阵图,但这阵图是由纯净的能源链路构成。", + "dialogue": "大鹏:我不想要火星。我想要你,从那个位子上滚下来。" + }, + { + "panel": 23, + "scene": "战斗爆发。", + "action": "液态金属化作漫天箭雨。大鹏将断刀刺向地面,一个半球形的金色力场瞬间撑开。", + "dialogue": "大鹏:金乌领域·极昼!" + }, + { + "panel": 24, + "scene": "爆炸特效。", + "action": "整个宫殿因为能量过载而开始崩塌。大鹏冲破液态金属的包围,直取主席的核心核芯。", + "dialogue": "财阀主席:不!那是太阳系的命脉!你毁了它,所有人都会死!" + } + ] +} +``` + +#### 第 25-28 分镜:灵魂升维,主宰一切 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "7/9", + "aspect_ratio": "16:9", + "layout_settings": { "show_panel_numbers": true, "composition_mode": "grid" } + }, + "characters": ["大鹏"], + "plot_breakdown": [ + { + "panel": 25, + "scene": "核心机房中心。", + "action": "大鹏的手触碰到那颗控制全系能源的“太阳核心”。无数数据流像狂暴的洪水般灌入他的脑海。", + "dialogue": "系统:检测到超神进化序列……使用者意识正在与戴森球同步。" + }, + { + "panel": 26, + "scene": "精神世界。", + "action": "大鹏看到自己化作一只翼展亿万里的金色大鹏鸟,双翼一振,便扫过水星与金星。", + "dialogue": "大鹏(内心):这才是真正的力量……这就是,众生的生死。" + }, + { + "panel": 27, + "scene": "轨道城外景。", + "action": "原本死寂的太空突然亮起万丈金光,戴森球的每一个叶片都开始按特定的频率颤动,向地球输送纯净能源。", + "dialogue": "旁白:这一刻,长夜终结。" + }, + { + "panel": 28, + "scene": "大鹏的全身特写。", + "action": "他缓缓升到半空,身体已经半透明化,充满了恒星般的质感。财阀主席在他脚下哀嚎着融化。", + "dialogue": "大鹏:从今天起,太阳系不再有阶级,只有光明。" + } + ] +} +``` + +#### 第 29-32 分镜:审判与重塑 +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "8/9", + "aspect_ratio": "16:9", + "layout_settings": { "show_panel_numbers": true, "composition_mode": "grid" } + }, + "characters": ["大鹏", "赵老头"], + "plot_breakdown": [ + { + "panel": 29, + "scene": "地底百层,大屏幕前。", + "action": "贫民们走出贫民窟,抬头望天。原本灰暗的酸雨云层消散,久违的真阳光洒在他们脸上。", + "dialogue": "路人甲:是光……是真的太阳光!" + }, + { + "panel": 30, + "scene": "赵老头的特写。", + "action": "他拿着烟斗,欣慰地看着天空中那个若隐若现的金色身影,那是他曾收留过的少年。", + "dialogue": "赵老头:这小子,真的把天给捅破了。" + }, + { + "panel": 31, + "scene": "轨道城宫殿。", + "action": "大鹏坐到了原本属于财阀的主座上,但他面前没有酒杯,只有掌控星系的虚拟星图。", + "dialogue": "大鹏:开启资源平等分配协议。重启火星生态系统。" + }, + { + "panel": 32, + "scene": "大鹏的手部动作。", + "action": "他随手一划,星图上原本灰暗的木星、土星卫星纷纷点亮。", + "dialogue": "大鹏:我要这星系,再无黑暗。" + } + ] +} +``` + +#### 第 33-36 分镜:永恒之主(大结局) +```json +{ + "type": "storyboard", + "meta_info": { + "style": "赛博朋克", + "language": "中文简体", + "volume": "9/9", + "aspect_ratio": "16:9", + "layout_settings": { "show_panel_numbers": true, "composition_mode": "grid" } + }, + "characters": ["大鹏"], + "plot_breakdown": [ + { + "panel": 33, + "scene": "太阳系全景图。", + "action": "八大行星被金色的脉络连接在一起,构成了一幅宏大的星空画卷。那是大鹏的意志延伸。", + "dialogue": "旁白:他不再是一个人,他是这片星空的脊梁。" + }, + { + "panel": 34, + "scene": "大鹏的正面特写。", + "action": "他的面容依然保持着20岁的模样,但神情已经庄严如神祇。他缓缓闭上眼,感受着亿万生灵的呼吸。", + "dialogue": "大鹏:使命尚未结束。星辰大海,才是征途。" + }, + { + "panel": 35, + "scene": "侧面镜头。", + "action": "大鹏站起身,推开宫殿的大门。门外不是走廊,而是直接通往无尽深空的星门。", + "dialogue": "大鹏:下一个目标,半人马座。" + }, + { + "panel": 36, + "scene": "最终震撼画面(Splash Page)。", + "action": "金色的大鹏鸟虚影在整个太阳系背景上展开,双翼遮天蔽日。下方是一行遒劲的大字。", + "dialogue": "大标题:太阳系之主:大鹏。" + } + ] +} +``` + +--- + +### 专家总结 +这部作品成功捕捉了“赛博朋克”的视觉张力与“废柴逆袭”的爽文核心。通过从地底垃圾场到太阳系王座的跨度,完成了史诗级的叙事转换。JSON 结构严谨,每个分镜都包含了光影与动作的精细指引,适合直接进入绘制或AI生成流程。 \ No newline at end of file diff --git a/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_2_e5e103bb.png b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_2_e5e103bb.png new file mode 100644 index 0000000..6699359 Binary files /dev/null and b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_2_e5e103bb.png differ diff --git a/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_3_5c81da7e.png b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_3_5c81da7e.png new file mode 100644 index 0000000..fbebce1 Binary files /dev/null and b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_3_5c81da7e.png differ diff --git a/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_4_ab93129f.png b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_4_ab93129f.png new file mode 100644 index 0000000..e7e014d Binary files /dev/null and b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_4_ab93129f.png differ diff --git a/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_5_f216859d.png b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_5_f216859d.png new file mode 100644 index 0000000..5f53fb2 Binary files /dev/null and b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/characters/character_5_f216859d.png differ diff --git a/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_5_9a37f93e.png b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_5_9a37f93e.png new file mode 100644 index 0000000..be26519 Binary files /dev/null and b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_5_9a37f93e.png differ diff --git a/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_6_3ab53dcd.png b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_6_3ab53dcd.png new file mode 100644 index 0000000..c8ec73c Binary files /dev/null and b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_6_3ab53dcd.png differ diff --git a/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_7_aae2809b.png b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_7_aae2809b.png new file mode 100644 index 0000000..5b8f1ec Binary files /dev/null and b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_7_aae2809b.png differ diff --git a/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_8_a2fbdeb2.png b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_8_a2fbdeb2.png new file mode 100644 index 0000000..7592a06 Binary files /dev/null and b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_8_a2fbdeb2.png differ diff --git a/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_8_d3e5a509.png b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_8_d3e5a509.png new file mode 100644 index 0000000..7d0250b Binary files /dev/null and b/backend/static/fc69a223-e25d-4b2d-bf6d-797fed2cec10/panels/panel_8_d3e5a509.png differ diff --git a/example/story.txt b/example/story.txt new file mode 100644 index 0000000..18e4e5d --- /dev/null +++ b/example/story.txt @@ -0,0 +1,228 @@ +风格为东方玄幻(仙侠)风格游戏原画,苍劲的书法字体旁白对话框,半透明白底,至少60个分镜 + +1章 地狱灵芝 +天空中乌云密布,时不时会冒出一道闪电,伴随着一声声闷雷,一场大雨即将来临。 +卧虎城中,沈翔仰头看着天空,喃喃说道:“不能再拖了,我要快点找到好的灵药,否则我难以有翻身的机会。” +沈翔今年十六岁,有着比同龄人要健壮高大的身躯,这身躯和那张带着稚气的俊俏脸蛋有着鲜明对比,但他那双与年龄不相称的深邃眸子,看起来闲得要比同龄人成熟一些。 +沈翔此时要去采药,他虽然是沈家族长的孙子,但他却因为没有灵脉,不能成为一个厉害的武者,因此,他从小就非常勤奋的锻炼自己的身体,经常外出去进行各种秘密训练,甚至还和虎兽进行过身上搏斗,他虽然年纪轻轻,但却有过几次生死经历,心境和意志都远胜同龄人。 +“这不是沈翔吗?就要下大雨了,你还要去锻炼?”一个老管家走过来说道,看见沈翔如此发奋,他不由得钦佩,但眼神中更多的是惋惜。 +沈翔每天都勤学苦练,至今六年,但还是停留在凡武境三重,和他同龄的大多数沈家子弟都进入了凡武境四重,厉害的更是进入了五重。 +这一切都是因为他没有灵脉的缘故,所以才不被家族重视,而如今他只是沈家中一个很普通的人。 +虽然没有灵脉,但沈翔却从来不气馁,一直都在努力锻炼自己,至少努力的过程让他感觉自己很充实。 +“老马,我是去采药。”沈翔跑到老管家身后,嘻笑着扯住他那光头上的一条鞭子。 +“没用的,你没有灵脉,不管怎么努力都是无济于事!” 那老管家摇头叹道。 +对于这样的话,沈翔听过无数遍了,但他却依然得坚持,无论如何他都不会放弃。 +“翔儿,天气这样就别去了!”这时,一个中年男子走来。 +沈翔撇撇嘴,说道:“老爹,下雨天采药可是一个好时机,至少不用和别人抢得头破血流。” +中年男子名叫沈天虎,是沈翔的父亲,是个名动一方的强大武者,也是最有希望继承下任沈家族长的人,虽然他儿子没有灵脉,但他却一直鼓励沈翔,还时不时给一些珍贵的丹药他,只不过还是无济于事。 +“拿着。”沈天虎无奈一笑,抛给沈翔一个小盒子。 +沈翔接过盒子,看也不看里面的东西,他知道里面放的丹药,嘻笑道:“多谢老爹,这样我就不用去偷马老头养的那些鸡来补身子了。” +这让那马管家满脸苦涩,他没想到自己竟然会被盯上。 +看着沈翔的背影消失,沈天虎只能叹气,他虽然在沈家有着很高的地位,但沈家的长老对丹药这些稀有珍贵的修炼资源却管理得非常严,他只能省出自己的一份来给沈翔,但那却起不到什么作用,因为丹药太少。 +做父亲的,哪个不想望子成龙?只不过沈天虎也没有办法,他只能尽力而为,替沈翔争取丹药。 +…… +仙魔崖,这是个非常荒凉的地方,此刻悬崖上却攀爬着一个赤着上身的少年。 +此时下着倾盆大雨,沈翔却在这个地方攀崖,这是一件非常危险的事情,要知道这仙魔崖下面可是深不见底的,而且下面常年弥漫着一种带着死亡气息的黑气,所以很多人都不想靠近这个地方。 +但沈翔却来这里采药,还攀爬在崖壁上,慢慢向下着,如果让别人知道,一定会笑话他是个不要命的疯子,谁都知道这种鸟不拉屎,死气浓重的地方是绝不会有什么好的灵药。 +沈翔不但不傻,还很聪明,他知道这仙魔崖存在了许多年,特别是下面的那些死气,更是没人知道存在多少年。 +在寻常人的认知里面,毫无生气的地方是没有灵药的,而沈翔却不这么认为,物极必反道理他是知道的,他十分肯定这崖壁上一定有一种传说中的珍贵灵药。 +“地狱灵芝”这种灵药听起来很可怕,但却是一种有起死人肉白骨之效的灵药,一般生长在古战场,坟场这些死气重的地方,是一味奇药。 +雨天能让一些黑气下沉,这样沈翔就能看清深一些的崖壁,他就去到较深的地方,这样他就能寻找到那“地狱灵芝”。 +虽然他不需要地狱灵芝,但他得到这圣药之后,却绝对能换到许多珍贵的丹药,能让他摆脱窘境,拥有强大的实力。 +雨点打在沈翔的身上,让他感到很不舒服,同时也让峭壁上的岩石变得更滑。这让他更加谨慎,小心翼翼的从峭壁上攀爬下去,否则一不小心他会摔下去。 +没人知道仙魔崖下面有着什么,虽然下去过的人也有不少,但能上来的人却一个都没有,掉下去就意味着死! +两个时辰过去,大雨还在下着,沈翔凭借着他多年锻炼出来的强壮身体,下到好几十丈深的崖壁中。 +沈翔找到了一个比较好的落脚处,这时候他仔细观察下面,突然,他看见了一些什么,这让他激动得心脏剧烈跳动起来。 +“地狱灵芝!”沈翔兴奋的喊了一声,目光激动地凝视下方,在他脚下十来丈的地方有着一块如同白色大饼的东西紧贴着崖壁,他非常肯定这就是传说在地狱灵芝。 +这里常年都被黑色死气覆盖着,而地狱灵芝的颜色和崖壁非常相似,很难发现。 +沈翔兴奋不已,他让自己镇定下来,休息了片刻,才缓慢的向下攀爬着。 +不用多久,沈翔就来到那一株地狱灵芝的旁边,他吞了吞口水,看着那如同脸盆般大的白色地狱灵芝,他现在还能感受到那地狱灵芝散发出的旺盛的生命之力。 +沈翔只能用一只手去采摘这株地狱灵芝,他估计这是千年以上的地狱灵芝,拿去拍卖的话,可是一个天文数字。 +沈翔费了很大劲才把灵芝采下,放入那珍贵的储物袋里面,他咧嘴笑着:“哈哈,老子咸鱼翻身的时候到了!” +他只要把这地狱灵芝卖掉,就能购买许多品阶不错的丹药,到时候他就能突飞猛进! +雨渐渐小了,沈翔是个很知足的人,所以他没有继续搜寻着偌大的崖壁,而是选择攀爬上去,毕竟他体力有限,爬上去也是非常艰苦和危险的。 +就在他爬了半个多时辰的时候,突然感觉到崖壁正在微微的颤抖起来! +沈翔心中一惊,那颗激动而兴奋的心立即一沉,他有不好的预感。他看向上方,只见许多小石块从崖壁上掉落下来,跌入深不见底的深渊下面,而原本微微颤抖的崖壁也抖动得越来越剧烈。 +“他娘的,好不容易得到地狱灵芝,老天你可别和我开玩笑呀!”突如其来的山摇地动,让沈翔不由得低骂起来。 +他要保持镇定,让自己紧抓住凹凸不平的崖壁,否则他就会被震得掉下去。 +不断加剧的震颤让沈翔渐渐绝望,这时候他看见上面不断掉落更大的石块,而他感觉到他双手抓着的岩石也产生了裂缝。 +“老天爷,我刚得到地狱灵芝,你就让我下地狱,耍我的吧!”沈翔不由得破口大骂起来,也在这时,下面的黑气升腾起来,沈翔所抓的岩石突然裂开…… +“啊——”沈翔的身体坠入了黑气弥漫的深渊之中,他那充满不甘的声音在下面回**着…… +不知道过了多久,沈翔睁开了眼睛,他竟然能看到光亮,这可是深渊底下,最让他觉得不可思议的是他竟然在水中,而且他还能呼吸! +沈翔浮到水面,他所在的地方是一个水潭,而水潭却冒着白色圣洁的光霞。 +让沈翔目瞪口呆的是,在水池不远处竟然盘坐着两名乱发披肩,容貌极美的女子。 +最让他感到震惊的是,眼前这两个宛若天仙的女子竟然都没有穿衣服!两具完美无瑕的玉体就这样毫无保留的展现在他面前! +那两名赤.裸的女子就好像是用羊脂玉精心雕磨成的一样,没有丝毫杂色。她们双峰都丰盈坚挺,腰肢都一样娇细,她们都美得让人窒息……这是沈翔见过最美的女人。 +如此具有冲击力的**画面让沈翔整个人瞬间石化,面红耳赤,心跳和呼吸都仿佛停止了! +两名女子盘坐在地,她们看着对方,完全没有发现沈翔,这让沈翔有种被藐视的感觉,他竟然被两个大美人无视了。 +一阵失神之后,沈翔才看见这这深渊底下满目疮痍,有着许多裂缝和凹坑,碎石满地,碎石中还有许多很碎的白色丝绸,看起来像是发生过战斗,他很猜测是那两名女子战斗造成的,也因此导致衣服碎烂。 +沈翔虽然不知道这两名倾城绝色的女子为什么会在这深渊下面战斗,但他却看得这两女很强,而且强大得超出他的认知范畴,竟然能施展出地动山摇的力量来。 +“真是红颜祸水,竟然把我给震下来了,幸好命大没有摔死!”沈翔心中低骂,不过他很好奇这两名神秘的女子。 +沈翔直勾勾的看着眼前这两具毫无瑕疵的玉体,同时朝两女轻轻走了过去。 +仙魔崖旁边的深渊被称之为地狱,而此时呆在这地狱下面的沈翔却如同在仙境一般,这里有着一潭散发圣洁白光的水,最重要的是水潭边还有两个没穿衣服的绝美女子。 +那两名女子这时候才意识到不远处有一双火热的眼睛扫视着她们,这让她们羞怒不已。 +两名绝美的女子并没有动,只是俏脸上布满滔天的杀意,那两双美眸都饱含怒意斜视着他,她们竟然连头都无法扭动。 +“两位大姐,你们……你们不冷吗?为什么不穿衣服,我感觉很冷。”沈翔也不知道该说些什么,就胡乱地问了一句。 +这时,那冷艳高贵,满面寒霜的女子冷冷喝道,“你再往前一步,我定然会让受尽蚀骨之痛,生不如死。” +这女子的声音虽然空灵清脆,但却毫无感情,让人有觉得有一种美中不足之意。这女子和她的声音一样,无论是神情还是气质,都有着一种拒人于千里之外的冰冷感觉,一双寒芒闪烁的美眸更是透着浓浓的戾气。 +“小子,你若敢靠近,我会让你后悔来到这个世界!”另一个女子低吟道,这女子的声音如同银铃般娇媚,流盼间媚态横生,勾人夺魄,这是一个艳丽妖挠,媚到骨子里的绝世尤物。 +眼前的**的参加,对于沈翔这个未经人事的雏鸟来说有很大的**力,虽然他自认为不是什么正人君子,但他也不是那种奸诈小人,眼前两名女子无法动弹,他更不会趁人之危。 +沈翔暗暗定下神来,礼貌地说道:“两位姑娘,这个……我不是故意的,我在上面悬崖采药,然后我就被震下来了,我没有死已经算是命大了。” +说话间,沈翔拿出了两件大袍子,朝那冷艳女子走过去,他看得出来这两个女子都不能动,为了不让她们感到害羞,他只能先掩盖住她们的那**的身体。 +被沈翔如此近距离的看着她的玉体,冷艳女子只能闭上眼眸,忍受着沈翔那火辣的眼神灼烧她的玉体!她浑身微微颤抖着,散发出一种透人骨髓的阴冷寒气杀气,让沈翔不由得打了个寒颤。 +沈翔浑身冒着冷汗,有些不舍地把一件大袍子盖在那冷艳女子的身体上,这让她微微哼了一声,而脸色也变得缓和许多,没有先前那般杀气腾腾。 +沈翔又来到那妖媚女子的身边,只见那妖媚女子朝他微微一笑,媚态万千,这让沈翔老脸微微一红,他深吸了一口气,才把那大袍子盖在她身上。 +沈翔此举,让两个女子心中都暗暗感激着,她们心中也有些愧疚,她们之前还那番威胁人家,而且还把人家给震下来,如果不是沈翔命大,恐怕就摔死了。 +两个女子都松了一口气,沈翔没有对她们做出龌龊的事情来,这番定力让她们赞赏不已,她们都很清楚此时的自己对男人的**力是最强的。 +“两位姑娘,你们是不是在这下面呆很久了?能告诉我怎么上去吗?我不能呆在这里一辈子,我还有很重要的事情要做!”沈翔有些沮丧地说道。 +妖媚女子柔声浅笑道:“小弟弟,我看你没有灵脉,此生是无法踏入武道强者的境界!不过嘛……我可以赠你一条至阳神脉,传授你强大的神功,教你炼丹制药,让你成为一名强大的武者,但我有一个条件。” +妖媚女子朝沈翔抛了一个媚眼,那媚意浓浓的神态,让沈翔不由得心神一**,这女子的话也让他微微吃惊。不过他却有些疑惑,他看得出这两个女子很强,只不过现在受伤而不能动弹,他能帮助她们什么? +冷艳女子眼眸一亮,她冷冷说道:“小子,我赠你一条至阴神脉!同时把我所修炼的魔功传授给你,我的魔功绝不比我师妹的神功差!我们绝不食言。” +沈翔浑身一震,上品灵脉就是天才了,而灵脉之上还有更稀有的玄脉,玄脉之上还有天脉,而天脉之上就是传说中的神脉! +拥有一条神脉的话,那可是非常逆天的! +“你们是不是从上面摔下来摔坏脑袋了?别拿我寻开心了,我可没功夫和你们在这里傻。”沈翔刚才虽然震惊了一下, 但他还是无法相信。 +“如果你得到这些的话,要成为一个强大的武者一点都不难!不过你到时候可要帮助我们恢复实力。”妖媚女子娇滴滴地说道,声音让沈翔感到骨头一阵酥软。 +冷艳女子说道:“我们是被一个仇家困在这里,身受重伤,无法动弹,修为尽失,而这下面会有强大的妖兽出没,我们得赶紧离开这里。” +她们现在都不能动,她们伤得非常严重,体内的经脉、骨骼、丹田,五脏六腑都受到重创,可以说是完全废掉了,这都是她们刚才和仇家大战而造成的,也是在那时导致地动山摇把沈翔震下来。 +而沈翔这个善良的少年出现,对她们来说无疑是一个翻身的机会,要知道这下面经常有妖兽出没,她们毫无反抗之力,最后只会成为妖兽的食物。 +“你现在必须相信我们,否则你这辈子就别想上去。”那妖媚女子认真说道。 +沈翔很难接受这两个女子可以随意赐予别人神脉,而且还身怀魔功和神功。但现在他只能选择相信。 +沈翔苦叹了一声,说道:“小子名叫沈翔,两位姐姐芳名?但愿你们没有耍我玩。” +冷艳女子冷冷说道:“白幽幽。” +娇媚女子浅笑道:“苏媚瑶。” +沈翔微笑道:“真是人如其名呀!那接下来我要做什么?” +苏媚瑶说道:“我们会和你结下一个血契,保证我们双方都互不背叛,因为我们要共处一段很长的时间!把神脉转移到你身上也是很简单的事情,我和师姐都是双神脉,给你一条也没什么。” +双神脉!两人都是,沈翔嘴角抽搐着,这让他更加难以相信。不过他心中有些激动,因为这两个厉害女子要和他在一起很长的时间,这对于男人来说可是妙不可言的事情,而且她们在还要依靠他来恢复实力。 +白幽幽冷冷说道:“我们都不了解对方,为了防止过河拆桥,结血契是必须的。” +苏媚瑶简单的把那血契的事情讲解了一下,然后订血契的步骤详细的说了一遍。 +血契很简单,就是先用三人的血液浸泡一张兽皮,然后在上面画出血契纹路,在血契上面写下契约内容,最后三人滴血在血契上面,血契形成之后会形成一种灵魂与灵魂只见的联系,能让人清晰感觉到契约的内容。 +沈翔完成了血契之后,对这种玄奥之术感到震惊不已,此时他已经相信这两个女子说的是真的!心中有着说不出的激动,他知道以后将会和这两个美人儿共处一段很长的时间,而且他这条烂咸鱼不但能翻身,还很翻到天山去。 +就这样,两个貌若天仙的女子和沈翔搭成了协议。 +“两位姐姐,你们不能动是因为体内的骨骼经脉受到重伤了吗?”沈翔问道,他刚刚采到地狱灵芝,这地狱灵芝有肉白骨之效,对于这种伤势帮助很大。 +白幽幽点头道:“我们的仇家很强,但那贱人却因为血契的缘故,不能亲手把我们杀死,所以就把我们废掉,然后让我们坐在这里等死。” +沈翔的出,无疑是救了她们,所以她们也愿意把自己认为多余的神脉赠给沈翔。 +沈翔心惊不已,这两个有双神脉的神秘女子,都如此强悍了,而她们的仇家更是强悍,能让她们落到如此田地。 +“我在崖壁上采到了地狱灵芝,这对你们的伤势有帮助吗?”沈翔问道,他很快就能得到两条神脉,所以也不吝啬那地狱灵芝。 +苏媚瑶脸上一喜,说道:“当然有用,这能然我们能很快可以动弹。” +沈翔咧嘴憨厚地笑着,拿出一大半“地狱灵芝”将之分成两份,喂入两女的口中,给美女喂食,也让沈翔颇为享受…… +地狱灵芝虽然不能让两女完全恢复伤势,但让却能让她们修复体内碎裂的骨骼,能让她们可以走动,不过她们现在却毫无实力。 +“两位姐姐,你们到底什么来头?仇敌还那么厉害,你们的仇敌又是谁?”这是沈翔一直都非常好奇的。 +白幽幽冷冷的瞥了他一眼:“契约上面没有提到我要告诉你这些,我可以不回答你!” +苏媚瑶整理着秀发,轻声说道:“这些事你就不要问了,牵涉太广,你一个小喽啰为了满足好奇心知道这些而引来杀身之祸那可是不好的。下面开始把神脉挪移到你身上。” +沈翔盘坐在地上,集中精神力,等待神脉挪到他的身上。 +白幽幽和苏媚瑶把纤手按在沈翔的腹部上,只见她们的玉手分别冒出了一百一黑的雾气,黑色是白幽幽的至阴神脉,白色是苏媚瑶的至阳神脉,看起来非常神异。 +沈翔身体中出现了一白一黑好像气流的东西,在他体内的肌肉、骨骼、经脉中游走,循环流动起来,他的经脉正在慢慢变大,骨骼和肌肉得到了强化,无数次循环之后,最后那一白一黑的气流汇聚在他的丹田之中形成一副太极阴阳图,这就是阴阳神脉! +苏媚瑶和白幽幽看见沈翔成功融合至阴至阳两条神脉,都不由得动容,心中兴奋不已,她们原本还以为至阴至阳两条神脉难以相融,而她们也抱着尝试的形态在沈翔身上实验,但没想到却成功了。 +苏媚瑶和白幽幽慢慢后退,看着身上冒着一百一黑气雾的沈翔,她们不约而同的用一种复杂而惊讶的眼神对视着。在她们师姐妹的认知中,拥有阴阳两条神脉的人从未有过,此时她们能知道眼前这个毛头小子只要得到栽培,说不定能成为一个举世无双的强者。 +沈翔睁开了眼睛,此时他有一种从未有过的舒爽,而且他觉得自己好像变强了一点。 +“这就是神脉吗!感觉真好,我以后能进入真武境吗?”沈翔有些激动地说道,脸上闪现出一抹邪异的笑容,看起来坏坏的,根本没有之前那副憨厚老实的模样,这让那两个美人儿心中有些担忧。 +这武道世界中,凡武境是最初的境界,分为炼气、淬体、武体、通脉、真气、神识、真罡、神力、真形、大圆满十个境界。 +而在凡武境之上,还有真武之境,那是许多武者梦寐以求的境界,到达那个境界,能拥有翻江倒海之力,更是有千年的寿元。 +沈翔就在就被卡在第三重的武体境,一直无法迈入四重的通脉境。 +“才真武境?这简直就是辱没了神脉。”白幽幽不屑地冷哼道。 +苏媚瑶脸色严肃,说道:“你拥有了阴阳神脉,眼界就要更加开阔一些!这个世界中力量是无止尽的,而且有着众多不同的世界,你现在所在的这个辰武大陆只是凡俗世界中一片小陆地而已!” +“你可要记得契约,你得帮助我们恢复到巅峰的实力!这可不是简单是事情!” +沈翔重重地点头道:“只要我还活着,我就一定会履行契约,替两位姐姐恢复巅峰的实力。” +白幽幽满意地说道:“帮助我们恢复实力的最快途径就是依靠丹药,当然,那是非常高等的丹药!等我们恢复实力之后,我们的契约也算搭成,到时候我们会去找我们仇敌复仇。” +沈翔觉得有些失落,如果这两个绝世美人能一直呆在他身边,那才是天大的享受。 +苏媚瑶从她那头美丽的秀发中取出一枚戒指,她抛给沈翔,说道:“滴血认主,就和使用那种储物袋一样。把我和师姐装进去,然后你自己爬上去!这下面不能久留,这里是巨型妖兽经常出没的地方。” +鲜血滴入,沈翔立即和那戒指建立了联系,戒指里面的空间很小,只有一个房间大,在沈翔的认知中,传说中的储物戒指应该有辽阔如海的空间才对。 +能装活物的储物法宝!这让沈翔惊叹不已,普通的储物袋都十分难得了,那都是从仙山中那些门派流传出来的,更别说装活物的储物法宝。 +他照苏媚瑶的话去做,把两女收入储物戒指。他带上戒指之后,戒指竟然还能隐形在他的手指上,让沈翔暗暗称奇。 +随后拿出他父亲给的丹药吃下,有了充足的体力,便开始攀爬悬崖峭壁,离开这死气充斥的深渊,这对他来说是一个巨大的挑战。 +攀爬悬崖的过程让沈翔十分郁闷,因为他在黑色的死气中什么都看不到,加大了难度。 +艰苦攀爬了一天一夜之后,沈翔终于爬了上来,原本他还以为这是难以完成的事情,但却因为有神脉的缘故,在攀爬上来时,摄取了许多灵气入体,清除他体内的疲劳,让他每时每刻都龙精虎猛。 +沈翔爬上去之后,踏上了回家的路。 +他无法看见那戒指里面的白幽幽和苏媚瑶,不过他却可以感应得到她们。 +“两位姐姐,你们什么时候能传授我神功和魔功?”沈翔心急着问道,对于那些神功魔功他都很好奇。 +“你的身体太弱,还不能修炼我的魔功。”白幽幽冰冷的声音传来。 +苏媚瑶说道:“我的神功随时都可以修炼,你回到你的住所再开始修炼吧,到时候我还会教你炼丹制药。” +沈翔心中一喜,便飞跑起来。 +南武国,南方的卧虎城,人口百万,这是一座繁荣而巨大的城市,里面还坐落着一个南武国有名的武道世家,沈家! +沈家有着数千年的历史,至今的实力依然强大,底蕴丰厚,能屹立不倒数千年,就证明沈家的强大。 +沈家富可敌国,是卧虎城里面最强大的势力,光沈家山庄就占地数千亩,山庄里面庭院无数,花园众多,有山有水,即便有人想潜入沈家,也会在里面迷路的。 +天虎园,从名字来看,就知道这是沈天虎的府邸,作为沈家地位显赫的人,有一座巨大的宅院也是很正常的事情。 +“老爹,我回来了!”沈翔一回来,就急忙奔向书房,他知道他父亲在那里。 +沈天虎呵呵笑道:“你这臭小子终于回来了,你可知道有个小美人在等你?你还记得薛家的小丫头吗?就是你的那个小妻子。” +沈翔眉头一皱,脑海中顿时浮现出一个粉雕玉琢的漂亮小丫头来,那是他五六岁时候的事情了。 +“老爹……你是说薛仙仙?”沈翔问道,这是薛家的一个天之娇女,在她小时候曾经来沈家住过一段时间,当时沈翔整天和她玩耍,两人很玩得来,然后就订下了婚约。 +沈天虎点了点头:“没错,那小丫头就在山庄里面,你刚刚走她就来了,她可是吵着要见你。” +沈天虎说完,微笑着看向窗外,沈翔也扭过头看去,只见院子外面正有一个长身玉立的女子走来。 +女子长发披肩,全身白衣,头上带着金光发饰。见这女子一身装束宛如仙女一般,沈翔不禁看得发愣。那女子不过十四五岁年纪,肌肤胜雪,容貌秀丽,让人不可逼视。 +少女透过窗子看见沈翔,娇美无比的脸庞满是欢喜,她娇喊道:“小翔哥!” +声音轻灵婉转,动人心魄,沈翔承认,这个少女虽是豆蔻年华,但无论是气质还是容貌,都能比肩在他戒指里面的那两个美人儿,更何况这少女还没发育完全。 +沈翔咽着口水,干笑道:“真是女大十八变呀!想当年的小丫头竟然变成了小仙女。” +这少女就是薛家的天之娇女,薛仙仙。 +“这丫头是我的未婚妻!”沈翔内心激动地喊着。 +如果是以前的话,沈翔心中多少会有些疙瘩,因为他没有灵脉。但现在他有着阴阳神脉!只要给他时间,踏足武道巅峰绝不是空想。 +薛仙仙浅浅一笑,脸颊一个可爱的小酒窝伴随着迷人的红晕浮现,看得沈翔又是一阵失神。 +沈天虎大笑一声,拍了下沈翔的肩膀,说道:“你们好好叙叙旧。” +沈翔嘿笑一声,便匆匆跑出书房。 +来到院子中,沈翔二话不说,挽着薛仙仙那娇柔无骨的玉手,就快步离开他父亲的视线,就好像他们小时候偷偷做坏事一样。 +沈翔带着一个小仙女来到自己的宅院,一路上让羡煞许多沈家子弟。 +“仙仙,你要在沈家呆多久?”沈翔嘻笑着抚摸薛仙仙脸上那可爱的小酒窝。 +薛仙仙只是面露娇羞,轻声说道:“小翔哥,我和我爹爹路过卧虎城才来看看你的,所以不会呆多久。” +在小时候,薛仙仙是因为得了怪病才来卧虎城求医的,那时候薛仙仙的身体很虚弱,长得很瘦很小,在沈家和薛家都经常被人欺负,当时只有沈翔很照顾她,经常和她嬉闹。那段时间让她很开心,而且沈翔经常鼓励她,让她能坚强的与病痛争斗,还给了她几粒珍贵的丹药。 +这些都被薛仙仙铭记在心,即便她知道沈翔没有灵脉,没有任何前途,她都决定要嫁给沈翔。 +沈翔有些失落,他也十分喜欢薛仙仙,在小时候,家族确定沈翔没有灵脉之后,沈翔就没有什么玩伴,所以他和薛仙仙在一起也很开心。 +“小翔哥,家族原本要让我和你解除婚约,但我不同意,所以他们让我和爹爹去药家和那个天才炼丹师见见面。”薛仙仙看见沈翔的脸色变了,便轻笑道:“放心吧,我死也不会嫁给药家那个家伙的,听说他很坏。” +薛仙仙十四五岁就有凡武境六重的实力,在整个南武国中确实天才中的天才了,而药家里面也有一个十六岁就能炼制出丹药的天才,在许多人眼中,他们才是最配的一对。 +沈翔很淡定,他有阴阳神脉,还有两个来历不明的厉害女人指导他修炼,他有信心在短时间赶超那个最年轻的天才炼丹师。 +沈翔咧嘴一笑,伸手摸了摸薛仙仙张绝俗淡雅的脸颊,笑道:“仙仙,你去了药家就说你有未婚夫了,他不服就让他来挑战我。” +看见沈翔如此自信,薛仙仙也非常欢喜,她踮起脚尖,亲吻了一下沈翔的嘴唇,然后垂着头,红着脸说道:“小翔哥,你要加油,我担心我的家族会不惜一切的拆散我们。” +说完,薛仙仙轻轻一掠,如燕一般掠出了院子,身法飘逸,而沈翔还傻愣愣的站在那里,脑海中不断重复着薛仙仙亲吻他嘴唇的那一瞬间。 +沈翔舔了舔嘴唇,笑道:“这小丫头还真的长大了很多。” + +第4章 四象神功 +“小家伙,你说你的小仙女漂亮,还是我们师姐妹漂亮?”沈翔的脑海中突然出现一道娇媚的声音,这是苏媚瑶的声音。 +沈翔干咳两声,笑道:“只要是我的妻子都漂亮,嘿嘿,你们当然也很漂亮……对了,媚瑶姐你得快点传授我那些神功,还有炼丹什么的。” +“混小子,你这是什么意思?”苏媚瑶娇啐道。 +“没啥……”沈翔挠头傻笑着,眼眸中闪过一抹异样的光芒。 +为了不让薛仙仙失望,沈翔打算日夜不眠,加倍努力去修炼! +沈翔那带着隐形戒指的手指突然闪烁起一丝微弱的光芒,只见白幽幽和苏媚瑶出现在他的面前,她们身上都穿着紫色的衣裙,闲得更加高贵端庄,沈翔猜测这是存放在那戒指里面的。 +此时是下午,太阳十分毒辣,但两个绝色女子却好像十分享受这种让人讨厌的毒辣阳光。 +“这里的灵气真差,不依靠丹药的话,难以进入真武境!”苏媚瑶仰头望着烈阳,低喃着。 +沈翔有些头大,这两个女人竟然可以自由的初入那枚戒指,而且她们还能听到外面的声音。 +沈翔跟着她们进入了那小宅子,里面不是很大,只有一个厅四间房,在偌大的沈家来说算是很小的了。 +小厅中,苏媚瑶和白幽幽喝着沈翔泡的灵茶,这可是沈翔珍藏的东西,平时他自己都舍不得喝。 +“在这种灵气稀薄的地方,你要快速提升的话只有通过丹药了,不过在此之前,你得先修炼出真气之火来,你有阴阳神脉,这对你来说并不是很难。”苏媚瑶说道。 +沈翔点了点头,说道:“还请媚瑶姐指教。” +“你过来,我先传授你太极神功和四象神功。”苏媚瑶娇声说道,她抚着秀发,煞是风情万种,因为沈翔有着无限的潜力,苏媚瑶也想好好把他培养起来,这样以后她也能有一个厉害的帮手。 +苏媚瑶把纤手捂在沈翔的额头上,然后闭上淹没,而沈翔的脑海中也出现了许多文字,每一句都晦涩难懂,但后来他又不知不觉地融会贯通了。 +苏媚瑶说道:“这是我用神识给你传功,我不仅仅把神功传给你,还把我对神功的理解传给你,所以你只要照着修炼就行了!” +沈翔心中微微惊讶着,他没想到这苏媚瑶竟然还能使用“神识”,神识是凡武境六重才能修炼出来的,是精神力和真力融合在一起诞生的,是一种无形无态的精神力量,十分神异。 +“对于我们的事情你一定要保密,绝不能告诉人任何人,包括你那小妻子!”白幽幽神情严肃,冷冷说道。 +苏媚瑶说道:“我可以告诉你,我和师姐的仇人很多,我们那些仇人随便一个都能用手指头捏死你,所以你必须要保密!而我们也不会随便从戒指里面出来。” +沈翔心中惊骇,他没想到自己竟然收容了两个这么危险的女人,他急忙点了点头,而白幽幽和苏媚瑶也突然消失,进入了那戒指里面。 +太极神功,四象神功!虽然只是一小部分而已,但沈翔却激动无比,毕竟不是什么人都能修炼神功的! +在这个武道世界中,武功分为凡级,灵级、玄级、地级、天级、圣级、神级,又分上下乘,丹药也是如此划分,只不过是分为上中下三品。 +天级的武功都是非常难得了,神级武功就更加稀有! +神功,那可是传说中神明修炼的神功,强大无比,修炼之后,能拥有翻江倒海,召唤雷电的力量,甚至还能逆天改命,破碎虚空,登天成神! +太极神功是主功法,是专门练气练体用的,是一门非常深奥而且强大的武功心法。 +而四象神功分为四部分,分别是青龙神功,白虎神功,朱雀神功,玄武神功,包罗万象,这四门神功是攻击或者防御的辅助型武功,修炼之后,能施展强大的武技。 +沈翔来到后院,那些青翠欲滴的树木,呼吸着清新的空气,他此时能非常清晰的感应到天地间的五行灵气。 +普通的灵脉是分属性的,而沈翔的阴阳神脉却代表着天地五行,所以他能修炼五行真气! +太极神功虽然是玄奥的练功心法,但经过苏媚瑶的神识传功之后,沈翔能融会贯通。他运转太极神功,他丹田中的太极阴阳图就立即旋转起来,天地灵气顿时狂涌入他的身体之中。 +上品灵脉吸收灵气的速度都非常之快了,神脉就更加不用说!此时沈翔盘腿坐在洞口运转着太极神功,灵气进入他的身体,立即被他提纯,化成一股纯白色的暖流,在他身体的骨骼、肌肉、经脉中游走,蕴养着他的身体内部。 +想要成为一个强大的武者,就要内外兼修。沈翔现在就是在强化身体内部,使他身体内部诞生出一种蕴含强大力量的气流,也就是真气! +沈翔不知不觉进入了修炼状态之中,灵气疯狂涌入沈翔的身体中,如果有人看见,一定会认为被吓得一大跳。 +拥有阴阳神脉和太极神功,能让他快速吸收天地灵气,而太极神功能快速炼化灵气为真气,因此他才能突飞猛进。 +而沈翔如今拥有的太极神功和四象神功都是神级武功,他要发挥出神功的威力,就得拥有更加浑厚的真气。 +青龙神功和白虎神功是攻击力最强的,朱雀神功主要是火焰方面,玄武神功就是防御。 +修炼真气到大乘,能在丹田中凝聚出形态,如果沈翔修炼四象神功的话,到时候他会在丹田内凝聚出青龙、朱雀、白虎、玄武,这四尊神兽之象! +这意味着沈翔一个人却要修炼四个人的份量,虽然会让他很辛苦,但他掌握的力量却要超乎常人。 +大量的灵气涌入他的身体之中,被他运转太极神功简直炼化成精纯的真气!真气流动在他身体内的大小脉络之中,让他身体内的经脉变得更加粗壮。 +次日清晨,温暖宜人的阳光射入在小宅子的后院,照在沈翔的身上。沈翔睁开眼睛,双目如电,缭绕在他身体外面的白色雾气顿时散开,只见他的身上满是黑色的污渍,他此时已经进入了凡武境第四重! +凡武境四到六重又是一个阶段,如今沈翔到达了第四重,通脉境!迈过了一道困扰着他多年的坎。 +沈翔双目微微闭上,凝神内视着他丹田里面的那副太极阴阳图,发现太极阴阳图里面出现了五个真气漩涡,分别在四方和中心!而平常人只有一个,他现在却有五个!他真气的浑厚程度可想而知。 +凭借他的真气浑厚程度,他的实力能直逼凡武境五重。 +“终于迈入凡武境四重了,老子以后不用去采药给那些老家伙了!”沈翔心中狂喜,只是一夜之间他就突破了。 +而当他想到薛仙仙十五岁就进入了凡武境六重,他心情又平静下来,想到药家那年轻的天才炼药师打他未婚妻的主意,沈翔心中一狠,他要更加发奋修炼,然后前往药家,用挑战的方式把那天才炼丹师打败。 +“现在就开始修炼青龙神功!”苏媚瑶说道,她的神识恢复了不少,能洞察到外面的变化。 +四象神功总的来说只是一门神功而已,但其中却包罗万象,里面就包涵着拳法,掌法、腿法、轻功、等等,融会贯通之后,想怎么施展就怎么施展,玄妙无穷。 +青龙是木属性的,木属性中又有风和雷属,所以修炼青龙神功能施展出具有风雷力量的武功来。 +沈翔清洗身体,饱餐一顿之后,就来到后院中继续修炼青龙神功! +太极神功和四象神功已融入了他的灵魂之中,不管是多么高深玄奥,他都能理解! +半天过去,沈翔按照青龙神功去运转体内的真气,只见他的身体溢出一阵青色柔光,青光之中闪烁着丝丝雷电,同时还有一阵轻风从沈翔的身体中轻飘出来。 +沈翔现在已经掌握了从木属性的真气之中释放出雷电和风的力量来,只不过这种程度还不够,要能将木属性真气转化为强大的雷电和狂风才能施展青龙神功中的武技。 +沈翔控制着体内的木属性真气以一种奇异的方式流动在身体的经脉之中,循环流动着,真气流动的方式飞诡异,时快时慢,有时候如同大江奔流一般的冲刺,有时候又如溪水潺潺…… \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..a9df62d --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + AI Comic Generator + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..73c1470 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "ai-comic-generator", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.6.7", + "element-plus": "^2.5.6", + "pinia": "^2.1.7", + "vue": "^3.4.19", + "vue-router": "^4.2.5", + "@element-plus/icons-vue": "^2.3.1" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "vite": "^5.1.4" + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..ff22fd1 --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,1162 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.1 + version: 2.3.2(vue@3.5.26) + axios: + specifier: ^1.6.7 + version: 1.13.2 + element-plus: + specifier: ^2.5.6 + version: 2.13.1(vue@3.5.26) + pinia: + specifier: ^2.1.7 + version: 2.3.1(vue@3.5.26) + vue: + specifier: ^3.4.19 + version: 3.5.26 + vue-router: + specifier: ^4.2.5 + version: 4.6.4(vue@3.5.26) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^5.0.4 + version: 5.2.4(vite@5.4.21)(vue@3.5.26) + vite: + specifier: ^5.1.4 + version: 5.4.21 + +packages: + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@element-plus/icons-vue@2.3.2': + resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==} + peerDependencies: + vue: ^3.2.0 + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + cpu: [x64] + os: [win32] + + '@sxzz/popperjs-es@2.11.7': + resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.23': + resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==} + + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vue/compiler-core@3.5.26': + resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} + + '@vue/compiler-dom@3.5.26': + resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==} + + '@vue/compiler-sfc@3.5.26': + resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==} + + '@vue/compiler-ssr@3.5.26': + resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/reactivity@3.5.26': + resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==} + + '@vue/runtime-core@3.5.26': + resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==} + + '@vue/runtime-dom@3.5.26': + resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==} + + '@vue/server-renderer@3.5.26': + resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==} + peerDependencies: + vue: 3.5.26 + + '@vue/shared@3.5.26': + resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} + + '@vueuse/core@10.11.1': + resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} + + '@vueuse/metadata@10.11.1': + resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} + + '@vueuse/shared@10.11.1': + resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + element-plus@2.13.1: + resolution: {integrity: sha512-eG4BDBGdAsUGN6URH1PixzZb0ngdapLivIk1meghS1uEueLvQ3aljSKrCt5x6sYb6mUk8eGtzTQFgsPmLavQcA==} + peerDependencies: + vue: ^3.3.0 + + entities@7.0.0: + resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + lodash-es@4.17.22: + resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + pinia@2.3.1: + resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue@3.5.26: + resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + +snapshots: + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@ctrl/tinycolor@3.6.1': {} + + '@element-plus/icons-vue@2.3.2(vue@3.5.26)': + dependencies: + vue: 3.5.26 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@rollup/rollup-android-arm-eabi@4.55.1': + optional: true + + '@rollup/rollup-android-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-x64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.55.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.55.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.55.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.55.1': + optional: true + + '@sxzz/popperjs-es@2.11.7': {} + + '@types/estree@1.0.8': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.23 + + '@types/lodash@4.17.23': {} + + '@types/web-bluetooth@0.0.20': {} + + '@vitejs/plugin-vue@5.2.4(vite@5.4.21)(vue@3.5.26)': + dependencies: + vite: 5.4.21 + vue: 3.5.26 + + '@vue/compiler-core@3.5.26': + dependencies: + '@babel/parser': 7.28.6 + '@vue/shared': 3.5.26 + entities: 7.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.26': + dependencies: + '@vue/compiler-core': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/compiler-sfc@3.5.26': + dependencies: + '@babel/parser': 7.28.6 + '@vue/compiler-core': 3.5.26 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.26': + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/devtools-api@6.6.4': {} + + '@vue/reactivity@3.5.26': + dependencies: + '@vue/shared': 3.5.26 + + '@vue/runtime-core@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/runtime-dom@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/runtime-core': 3.5.26 + '@vue/shared': 3.5.26 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.26(vue@3.5.26)': + dependencies: + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + vue: 3.5.26 + + '@vue/shared@3.5.26': {} + + '@vueuse/core@10.11.1(vue@3.5.26)': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.1 + '@vueuse/shared': 10.11.1(vue@3.5.26) + vue-demi: 0.14.10(vue@3.5.26) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@10.11.1': {} + + '@vueuse/shared@10.11.1(vue@3.5.26)': + dependencies: + vue-demi: 0.14.10(vue@3.5.26) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + csstype@3.2.3: {} + + dayjs@1.11.19: {} + + delayed-stream@1.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + element-plus@2.13.1(vue@3.5.26): + dependencies: + '@ctrl/tinycolor': 3.6.1 + '@element-plus/icons-vue': 2.3.2(vue@3.5.26) + '@floating-ui/dom': 1.7.4 + '@popperjs/core': '@sxzz/popperjs-es@2.11.7' + '@types/lodash': 4.17.23 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 10.11.1(vue@3.5.26) + async-validator: 4.2.5 + dayjs: 1.11.19 + lodash: 4.17.21 + lodash-es: 4.17.22 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.22)(lodash@4.17.21) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.26 + transitivePeerDependencies: + - '@vue/composition-api' + + entities@7.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + estree-walker@2.0.2: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + lodash-es@4.17.22: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.22)(lodash@4.17.21): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.17.21 + lodash-es: 4.17.22 + + lodash@4.17.21: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + memoize-one@6.0.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + nanoid@3.3.11: {} + + normalize-wheel-es@1.2.0: {} + + picocolors@1.1.1: {} + + pinia@2.3.1(vue@3.5.26): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.26 + vue-demi: 0.14.10(vue@3.5.26) + transitivePeerDependencies: + - '@vue/composition-api' + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proxy-from-env@1.1.0: {} + + rollup@4.55.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 + fsevents: 2.3.3 + + source-map-js@1.2.1: {} + + vite@5.4.21: + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.55.1 + optionalDependencies: + fsevents: 2.3.3 + + vue-demi@0.14.10(vue@3.5.26): + dependencies: + vue: 3.5.26 + + vue-router@4.6.4(vue@3.5.26): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.26 + + vue@3.5.26: + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 + '@vue/runtime-dom': 3.5.26 + '@vue/server-renderer': 3.5.26(vue@3.5.26) + '@vue/shared': 3.5.26 diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..93e5582 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..0d769c6 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,21 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/dark/css-vars.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import App from './App.vue' +import router from './router' +import './style.css' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) +app.use(ElementPlus) + +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..3e42014 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,27 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomeView from '../views/HomeView.vue' +import ConfigView from '../views/ConfigView.vue' +import ProjectView from '../views/ProjectView.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: HomeView + }, + { + path: '/config', + name: 'config', + component: ConfigView + }, + { + path: '/project/:id', + name: 'project', + component: ProjectView + } + ] +}) + +export default router diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..558114a --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,33 @@ +html, body { + margin: 0; + padding: 0; + height: 100%; + width: 100%; + background-color: #121212; + color: #e0e0e0; + font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; +} + +#app { + height: 100%; +} + +.el-container { + height: 100%; +} + +/* Scrollbar styling for tech look */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} +::-webkit-scrollbar-track { + background: #1e1e1e; +} +::-webkit-scrollbar-thumb { + background: #333; + border-radius: 4px; +} +::-webkit-scrollbar-thumb:hover { + background: #555; +} diff --git a/frontend/src/views/ConfigView.vue b/frontend/src/views/ConfigView.vue new file mode 100644 index 0000000..d1e555e --- /dev/null +++ b/frontend/src/views/ConfigView.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue new file mode 100644 index 0000000..6a97d01 --- /dev/null +++ b/frontend/src/views/HomeView.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/frontend/src/views/ProjectView.vue b/frontend/src/views/ProjectView.vue new file mode 100644 index 0000000..279f4a8 --- /dev/null +++ b/frontend/src/views/ProjectView.vue @@ -0,0 +1,1021 @@ + + + + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..0d8b12e --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + server: { + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + '/static': { + target: 'http://localhost:8000', + changeOrigin: true, + } + } + } +}) diff --git a/start_backend.bat b/start_backend.bat new file mode 100644 index 0000000..575df96 --- /dev/null +++ b/start_backend.bat @@ -0,0 +1,4 @@ +@echo off +cd backend +echo Starting Backend... +python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 diff --git a/start_frontend.bat b/start_frontend.bat new file mode 100644 index 0000000..b4d396b --- /dev/null +++ b/start_frontend.bat @@ -0,0 +1,6 @@ +@echo off +cd frontend +echo Installing dependencies (if needed)... +call pnpm install +echo Starting Frontend... +call pnpm dev diff --git a/story.txt b/story.txt new file mode 100644 index 0000000..18e4e5d --- /dev/null +++ b/story.txt @@ -0,0 +1,228 @@ +风格为东方玄幻(仙侠)风格游戏原画,苍劲的书法字体旁白对话框,半透明白底,至少60个分镜 + +1章 地狱灵芝 +天空中乌云密布,时不时会冒出一道闪电,伴随着一声声闷雷,一场大雨即将来临。 +卧虎城中,沈翔仰头看着天空,喃喃说道:“不能再拖了,我要快点找到好的灵药,否则我难以有翻身的机会。” +沈翔今年十六岁,有着比同龄人要健壮高大的身躯,这身躯和那张带着稚气的俊俏脸蛋有着鲜明对比,但他那双与年龄不相称的深邃眸子,看起来闲得要比同龄人成熟一些。 +沈翔此时要去采药,他虽然是沈家族长的孙子,但他却因为没有灵脉,不能成为一个厉害的武者,因此,他从小就非常勤奋的锻炼自己的身体,经常外出去进行各种秘密训练,甚至还和虎兽进行过身上搏斗,他虽然年纪轻轻,但却有过几次生死经历,心境和意志都远胜同龄人。 +“这不是沈翔吗?就要下大雨了,你还要去锻炼?”一个老管家走过来说道,看见沈翔如此发奋,他不由得钦佩,但眼神中更多的是惋惜。 +沈翔每天都勤学苦练,至今六年,但还是停留在凡武境三重,和他同龄的大多数沈家子弟都进入了凡武境四重,厉害的更是进入了五重。 +这一切都是因为他没有灵脉的缘故,所以才不被家族重视,而如今他只是沈家中一个很普通的人。 +虽然没有灵脉,但沈翔却从来不气馁,一直都在努力锻炼自己,至少努力的过程让他感觉自己很充实。 +“老马,我是去采药。”沈翔跑到老管家身后,嘻笑着扯住他那光头上的一条鞭子。 +“没用的,你没有灵脉,不管怎么努力都是无济于事!” 那老管家摇头叹道。 +对于这样的话,沈翔听过无数遍了,但他却依然得坚持,无论如何他都不会放弃。 +“翔儿,天气这样就别去了!”这时,一个中年男子走来。 +沈翔撇撇嘴,说道:“老爹,下雨天采药可是一个好时机,至少不用和别人抢得头破血流。” +中年男子名叫沈天虎,是沈翔的父亲,是个名动一方的强大武者,也是最有希望继承下任沈家族长的人,虽然他儿子没有灵脉,但他却一直鼓励沈翔,还时不时给一些珍贵的丹药他,只不过还是无济于事。 +“拿着。”沈天虎无奈一笑,抛给沈翔一个小盒子。 +沈翔接过盒子,看也不看里面的东西,他知道里面放的丹药,嘻笑道:“多谢老爹,这样我就不用去偷马老头养的那些鸡来补身子了。” +这让那马管家满脸苦涩,他没想到自己竟然会被盯上。 +看着沈翔的背影消失,沈天虎只能叹气,他虽然在沈家有着很高的地位,但沈家的长老对丹药这些稀有珍贵的修炼资源却管理得非常严,他只能省出自己的一份来给沈翔,但那却起不到什么作用,因为丹药太少。 +做父亲的,哪个不想望子成龙?只不过沈天虎也没有办法,他只能尽力而为,替沈翔争取丹药。 +…… +仙魔崖,这是个非常荒凉的地方,此刻悬崖上却攀爬着一个赤着上身的少年。 +此时下着倾盆大雨,沈翔却在这个地方攀崖,这是一件非常危险的事情,要知道这仙魔崖下面可是深不见底的,而且下面常年弥漫着一种带着死亡气息的黑气,所以很多人都不想靠近这个地方。 +但沈翔却来这里采药,还攀爬在崖壁上,慢慢向下着,如果让别人知道,一定会笑话他是个不要命的疯子,谁都知道这种鸟不拉屎,死气浓重的地方是绝不会有什么好的灵药。 +沈翔不但不傻,还很聪明,他知道这仙魔崖存在了许多年,特别是下面的那些死气,更是没人知道存在多少年。 +在寻常人的认知里面,毫无生气的地方是没有灵药的,而沈翔却不这么认为,物极必反道理他是知道的,他十分肯定这崖壁上一定有一种传说中的珍贵灵药。 +“地狱灵芝”这种灵药听起来很可怕,但却是一种有起死人肉白骨之效的灵药,一般生长在古战场,坟场这些死气重的地方,是一味奇药。 +雨天能让一些黑气下沉,这样沈翔就能看清深一些的崖壁,他就去到较深的地方,这样他就能寻找到那“地狱灵芝”。 +虽然他不需要地狱灵芝,但他得到这圣药之后,却绝对能换到许多珍贵的丹药,能让他摆脱窘境,拥有强大的实力。 +雨点打在沈翔的身上,让他感到很不舒服,同时也让峭壁上的岩石变得更滑。这让他更加谨慎,小心翼翼的从峭壁上攀爬下去,否则一不小心他会摔下去。 +没人知道仙魔崖下面有着什么,虽然下去过的人也有不少,但能上来的人却一个都没有,掉下去就意味着死! +两个时辰过去,大雨还在下着,沈翔凭借着他多年锻炼出来的强壮身体,下到好几十丈深的崖壁中。 +沈翔找到了一个比较好的落脚处,这时候他仔细观察下面,突然,他看见了一些什么,这让他激动得心脏剧烈跳动起来。 +“地狱灵芝!”沈翔兴奋的喊了一声,目光激动地凝视下方,在他脚下十来丈的地方有着一块如同白色大饼的东西紧贴着崖壁,他非常肯定这就是传说在地狱灵芝。 +这里常年都被黑色死气覆盖着,而地狱灵芝的颜色和崖壁非常相似,很难发现。 +沈翔兴奋不已,他让自己镇定下来,休息了片刻,才缓慢的向下攀爬着。 +不用多久,沈翔就来到那一株地狱灵芝的旁边,他吞了吞口水,看着那如同脸盆般大的白色地狱灵芝,他现在还能感受到那地狱灵芝散发出的旺盛的生命之力。 +沈翔只能用一只手去采摘这株地狱灵芝,他估计这是千年以上的地狱灵芝,拿去拍卖的话,可是一个天文数字。 +沈翔费了很大劲才把灵芝采下,放入那珍贵的储物袋里面,他咧嘴笑着:“哈哈,老子咸鱼翻身的时候到了!” +他只要把这地狱灵芝卖掉,就能购买许多品阶不错的丹药,到时候他就能突飞猛进! +雨渐渐小了,沈翔是个很知足的人,所以他没有继续搜寻着偌大的崖壁,而是选择攀爬上去,毕竟他体力有限,爬上去也是非常艰苦和危险的。 +就在他爬了半个多时辰的时候,突然感觉到崖壁正在微微的颤抖起来! +沈翔心中一惊,那颗激动而兴奋的心立即一沉,他有不好的预感。他看向上方,只见许多小石块从崖壁上掉落下来,跌入深不见底的深渊下面,而原本微微颤抖的崖壁也抖动得越来越剧烈。 +“他娘的,好不容易得到地狱灵芝,老天你可别和我开玩笑呀!”突如其来的山摇地动,让沈翔不由得低骂起来。 +他要保持镇定,让自己紧抓住凹凸不平的崖壁,否则他就会被震得掉下去。 +不断加剧的震颤让沈翔渐渐绝望,这时候他看见上面不断掉落更大的石块,而他感觉到他双手抓着的岩石也产生了裂缝。 +“老天爷,我刚得到地狱灵芝,你就让我下地狱,耍我的吧!”沈翔不由得破口大骂起来,也在这时,下面的黑气升腾起来,沈翔所抓的岩石突然裂开…… +“啊——”沈翔的身体坠入了黑气弥漫的深渊之中,他那充满不甘的声音在下面回**着…… +不知道过了多久,沈翔睁开了眼睛,他竟然能看到光亮,这可是深渊底下,最让他觉得不可思议的是他竟然在水中,而且他还能呼吸! +沈翔浮到水面,他所在的地方是一个水潭,而水潭却冒着白色圣洁的光霞。 +让沈翔目瞪口呆的是,在水池不远处竟然盘坐着两名乱发披肩,容貌极美的女子。 +最让他感到震惊的是,眼前这两个宛若天仙的女子竟然都没有穿衣服!两具完美无瑕的玉体就这样毫无保留的展现在他面前! +那两名赤.裸的女子就好像是用羊脂玉精心雕磨成的一样,没有丝毫杂色。她们双峰都丰盈坚挺,腰肢都一样娇细,她们都美得让人窒息……这是沈翔见过最美的女人。 +如此具有冲击力的**画面让沈翔整个人瞬间石化,面红耳赤,心跳和呼吸都仿佛停止了! +两名女子盘坐在地,她们看着对方,完全没有发现沈翔,这让沈翔有种被藐视的感觉,他竟然被两个大美人无视了。 +一阵失神之后,沈翔才看见这这深渊底下满目疮痍,有着许多裂缝和凹坑,碎石满地,碎石中还有许多很碎的白色丝绸,看起来像是发生过战斗,他很猜测是那两名女子战斗造成的,也因此导致衣服碎烂。 +沈翔虽然不知道这两名倾城绝色的女子为什么会在这深渊下面战斗,但他却看得这两女很强,而且强大得超出他的认知范畴,竟然能施展出地动山摇的力量来。 +“真是红颜祸水,竟然把我给震下来了,幸好命大没有摔死!”沈翔心中低骂,不过他很好奇这两名神秘的女子。 +沈翔直勾勾的看着眼前这两具毫无瑕疵的玉体,同时朝两女轻轻走了过去。 +仙魔崖旁边的深渊被称之为地狱,而此时呆在这地狱下面的沈翔却如同在仙境一般,这里有着一潭散发圣洁白光的水,最重要的是水潭边还有两个没穿衣服的绝美女子。 +那两名女子这时候才意识到不远处有一双火热的眼睛扫视着她们,这让她们羞怒不已。 +两名绝美的女子并没有动,只是俏脸上布满滔天的杀意,那两双美眸都饱含怒意斜视着他,她们竟然连头都无法扭动。 +“两位大姐,你们……你们不冷吗?为什么不穿衣服,我感觉很冷。”沈翔也不知道该说些什么,就胡乱地问了一句。 +这时,那冷艳高贵,满面寒霜的女子冷冷喝道,“你再往前一步,我定然会让受尽蚀骨之痛,生不如死。” +这女子的声音虽然空灵清脆,但却毫无感情,让人有觉得有一种美中不足之意。这女子和她的声音一样,无论是神情还是气质,都有着一种拒人于千里之外的冰冷感觉,一双寒芒闪烁的美眸更是透着浓浓的戾气。 +“小子,你若敢靠近,我会让你后悔来到这个世界!”另一个女子低吟道,这女子的声音如同银铃般娇媚,流盼间媚态横生,勾人夺魄,这是一个艳丽妖挠,媚到骨子里的绝世尤物。 +眼前的**的参加,对于沈翔这个未经人事的雏鸟来说有很大的**力,虽然他自认为不是什么正人君子,但他也不是那种奸诈小人,眼前两名女子无法动弹,他更不会趁人之危。 +沈翔暗暗定下神来,礼貌地说道:“两位姑娘,这个……我不是故意的,我在上面悬崖采药,然后我就被震下来了,我没有死已经算是命大了。” +说话间,沈翔拿出了两件大袍子,朝那冷艳女子走过去,他看得出来这两个女子都不能动,为了不让她们感到害羞,他只能先掩盖住她们的那**的身体。 +被沈翔如此近距离的看着她的玉体,冷艳女子只能闭上眼眸,忍受着沈翔那火辣的眼神灼烧她的玉体!她浑身微微颤抖着,散发出一种透人骨髓的阴冷寒气杀气,让沈翔不由得打了个寒颤。 +沈翔浑身冒着冷汗,有些不舍地把一件大袍子盖在那冷艳女子的身体上,这让她微微哼了一声,而脸色也变得缓和许多,没有先前那般杀气腾腾。 +沈翔又来到那妖媚女子的身边,只见那妖媚女子朝他微微一笑,媚态万千,这让沈翔老脸微微一红,他深吸了一口气,才把那大袍子盖在她身上。 +沈翔此举,让两个女子心中都暗暗感激着,她们心中也有些愧疚,她们之前还那番威胁人家,而且还把人家给震下来,如果不是沈翔命大,恐怕就摔死了。 +两个女子都松了一口气,沈翔没有对她们做出龌龊的事情来,这番定力让她们赞赏不已,她们都很清楚此时的自己对男人的**力是最强的。 +“两位姑娘,你们是不是在这下面呆很久了?能告诉我怎么上去吗?我不能呆在这里一辈子,我还有很重要的事情要做!”沈翔有些沮丧地说道。 +妖媚女子柔声浅笑道:“小弟弟,我看你没有灵脉,此生是无法踏入武道强者的境界!不过嘛……我可以赠你一条至阳神脉,传授你强大的神功,教你炼丹制药,让你成为一名强大的武者,但我有一个条件。” +妖媚女子朝沈翔抛了一个媚眼,那媚意浓浓的神态,让沈翔不由得心神一**,这女子的话也让他微微吃惊。不过他却有些疑惑,他看得出这两个女子很强,只不过现在受伤而不能动弹,他能帮助她们什么? +冷艳女子眼眸一亮,她冷冷说道:“小子,我赠你一条至阴神脉!同时把我所修炼的魔功传授给你,我的魔功绝不比我师妹的神功差!我们绝不食言。” +沈翔浑身一震,上品灵脉就是天才了,而灵脉之上还有更稀有的玄脉,玄脉之上还有天脉,而天脉之上就是传说中的神脉! +拥有一条神脉的话,那可是非常逆天的! +“你们是不是从上面摔下来摔坏脑袋了?别拿我寻开心了,我可没功夫和你们在这里傻。”沈翔刚才虽然震惊了一下, 但他还是无法相信。 +“如果你得到这些的话,要成为一个强大的武者一点都不难!不过你到时候可要帮助我们恢复实力。”妖媚女子娇滴滴地说道,声音让沈翔感到骨头一阵酥软。 +冷艳女子说道:“我们是被一个仇家困在这里,身受重伤,无法动弹,修为尽失,而这下面会有强大的妖兽出没,我们得赶紧离开这里。” +她们现在都不能动,她们伤得非常严重,体内的经脉、骨骼、丹田,五脏六腑都受到重创,可以说是完全废掉了,这都是她们刚才和仇家大战而造成的,也是在那时导致地动山摇把沈翔震下来。 +而沈翔这个善良的少年出现,对她们来说无疑是一个翻身的机会,要知道这下面经常有妖兽出没,她们毫无反抗之力,最后只会成为妖兽的食物。 +“你现在必须相信我们,否则你这辈子就别想上去。”那妖媚女子认真说道。 +沈翔很难接受这两个女子可以随意赐予别人神脉,而且还身怀魔功和神功。但现在他只能选择相信。 +沈翔苦叹了一声,说道:“小子名叫沈翔,两位姐姐芳名?但愿你们没有耍我玩。” +冷艳女子冷冷说道:“白幽幽。” +娇媚女子浅笑道:“苏媚瑶。” +沈翔微笑道:“真是人如其名呀!那接下来我要做什么?” +苏媚瑶说道:“我们会和你结下一个血契,保证我们双方都互不背叛,因为我们要共处一段很长的时间!把神脉转移到你身上也是很简单的事情,我和师姐都是双神脉,给你一条也没什么。” +双神脉!两人都是,沈翔嘴角抽搐着,这让他更加难以相信。不过他心中有些激动,因为这两个厉害女子要和他在一起很长的时间,这对于男人来说可是妙不可言的事情,而且她们在还要依靠他来恢复实力。 +白幽幽冷冷说道:“我们都不了解对方,为了防止过河拆桥,结血契是必须的。” +苏媚瑶简单的把那血契的事情讲解了一下,然后订血契的步骤详细的说了一遍。 +血契很简单,就是先用三人的血液浸泡一张兽皮,然后在上面画出血契纹路,在血契上面写下契约内容,最后三人滴血在血契上面,血契形成之后会形成一种灵魂与灵魂只见的联系,能让人清晰感觉到契约的内容。 +沈翔完成了血契之后,对这种玄奥之术感到震惊不已,此时他已经相信这两个女子说的是真的!心中有着说不出的激动,他知道以后将会和这两个美人儿共处一段很长的时间,而且他这条烂咸鱼不但能翻身,还很翻到天山去。 +就这样,两个貌若天仙的女子和沈翔搭成了协议。 +“两位姐姐,你们不能动是因为体内的骨骼经脉受到重伤了吗?”沈翔问道,他刚刚采到地狱灵芝,这地狱灵芝有肉白骨之效,对于这种伤势帮助很大。 +白幽幽点头道:“我们的仇家很强,但那贱人却因为血契的缘故,不能亲手把我们杀死,所以就把我们废掉,然后让我们坐在这里等死。” +沈翔的出,无疑是救了她们,所以她们也愿意把自己认为多余的神脉赠给沈翔。 +沈翔心惊不已,这两个有双神脉的神秘女子,都如此强悍了,而她们的仇家更是强悍,能让她们落到如此田地。 +“我在崖壁上采到了地狱灵芝,这对你们的伤势有帮助吗?”沈翔问道,他很快就能得到两条神脉,所以也不吝啬那地狱灵芝。 +苏媚瑶脸上一喜,说道:“当然有用,这能然我们能很快可以动弹。” +沈翔咧嘴憨厚地笑着,拿出一大半“地狱灵芝”将之分成两份,喂入两女的口中,给美女喂食,也让沈翔颇为享受…… +地狱灵芝虽然不能让两女完全恢复伤势,但让却能让她们修复体内碎裂的骨骼,能让她们可以走动,不过她们现在却毫无实力。 +“两位姐姐,你们到底什么来头?仇敌还那么厉害,你们的仇敌又是谁?”这是沈翔一直都非常好奇的。 +白幽幽冷冷的瞥了他一眼:“契约上面没有提到我要告诉你这些,我可以不回答你!” +苏媚瑶整理着秀发,轻声说道:“这些事你就不要问了,牵涉太广,你一个小喽啰为了满足好奇心知道这些而引来杀身之祸那可是不好的。下面开始把神脉挪移到你身上。” +沈翔盘坐在地上,集中精神力,等待神脉挪到他的身上。 +白幽幽和苏媚瑶把纤手按在沈翔的腹部上,只见她们的玉手分别冒出了一百一黑的雾气,黑色是白幽幽的至阴神脉,白色是苏媚瑶的至阳神脉,看起来非常神异。 +沈翔身体中出现了一白一黑好像气流的东西,在他体内的肌肉、骨骼、经脉中游走,循环流动起来,他的经脉正在慢慢变大,骨骼和肌肉得到了强化,无数次循环之后,最后那一白一黑的气流汇聚在他的丹田之中形成一副太极阴阳图,这就是阴阳神脉! +苏媚瑶和白幽幽看见沈翔成功融合至阴至阳两条神脉,都不由得动容,心中兴奋不已,她们原本还以为至阴至阳两条神脉难以相融,而她们也抱着尝试的形态在沈翔身上实验,但没想到却成功了。 +苏媚瑶和白幽幽慢慢后退,看着身上冒着一百一黑气雾的沈翔,她们不约而同的用一种复杂而惊讶的眼神对视着。在她们师姐妹的认知中,拥有阴阳两条神脉的人从未有过,此时她们能知道眼前这个毛头小子只要得到栽培,说不定能成为一个举世无双的强者。 +沈翔睁开了眼睛,此时他有一种从未有过的舒爽,而且他觉得自己好像变强了一点。 +“这就是神脉吗!感觉真好,我以后能进入真武境吗?”沈翔有些激动地说道,脸上闪现出一抹邪异的笑容,看起来坏坏的,根本没有之前那副憨厚老实的模样,这让那两个美人儿心中有些担忧。 +这武道世界中,凡武境是最初的境界,分为炼气、淬体、武体、通脉、真气、神识、真罡、神力、真形、大圆满十个境界。 +而在凡武境之上,还有真武之境,那是许多武者梦寐以求的境界,到达那个境界,能拥有翻江倒海之力,更是有千年的寿元。 +沈翔就在就被卡在第三重的武体境,一直无法迈入四重的通脉境。 +“才真武境?这简直就是辱没了神脉。”白幽幽不屑地冷哼道。 +苏媚瑶脸色严肃,说道:“你拥有了阴阳神脉,眼界就要更加开阔一些!这个世界中力量是无止尽的,而且有着众多不同的世界,你现在所在的这个辰武大陆只是凡俗世界中一片小陆地而已!” +“你可要记得契约,你得帮助我们恢复到巅峰的实力!这可不是简单是事情!” +沈翔重重地点头道:“只要我还活着,我就一定会履行契约,替两位姐姐恢复巅峰的实力。” +白幽幽满意地说道:“帮助我们恢复实力的最快途径就是依靠丹药,当然,那是非常高等的丹药!等我们恢复实力之后,我们的契约也算搭成,到时候我们会去找我们仇敌复仇。” +沈翔觉得有些失落,如果这两个绝世美人能一直呆在他身边,那才是天大的享受。 +苏媚瑶从她那头美丽的秀发中取出一枚戒指,她抛给沈翔,说道:“滴血认主,就和使用那种储物袋一样。把我和师姐装进去,然后你自己爬上去!这下面不能久留,这里是巨型妖兽经常出没的地方。” +鲜血滴入,沈翔立即和那戒指建立了联系,戒指里面的空间很小,只有一个房间大,在沈翔的认知中,传说中的储物戒指应该有辽阔如海的空间才对。 +能装活物的储物法宝!这让沈翔惊叹不已,普通的储物袋都十分难得了,那都是从仙山中那些门派流传出来的,更别说装活物的储物法宝。 +他照苏媚瑶的话去做,把两女收入储物戒指。他带上戒指之后,戒指竟然还能隐形在他的手指上,让沈翔暗暗称奇。 +随后拿出他父亲给的丹药吃下,有了充足的体力,便开始攀爬悬崖峭壁,离开这死气充斥的深渊,这对他来说是一个巨大的挑战。 +攀爬悬崖的过程让沈翔十分郁闷,因为他在黑色的死气中什么都看不到,加大了难度。 +艰苦攀爬了一天一夜之后,沈翔终于爬了上来,原本他还以为这是难以完成的事情,但却因为有神脉的缘故,在攀爬上来时,摄取了许多灵气入体,清除他体内的疲劳,让他每时每刻都龙精虎猛。 +沈翔爬上去之后,踏上了回家的路。 +他无法看见那戒指里面的白幽幽和苏媚瑶,不过他却可以感应得到她们。 +“两位姐姐,你们什么时候能传授我神功和魔功?”沈翔心急着问道,对于那些神功魔功他都很好奇。 +“你的身体太弱,还不能修炼我的魔功。”白幽幽冰冷的声音传来。 +苏媚瑶说道:“我的神功随时都可以修炼,你回到你的住所再开始修炼吧,到时候我还会教你炼丹制药。” +沈翔心中一喜,便飞跑起来。 +南武国,南方的卧虎城,人口百万,这是一座繁荣而巨大的城市,里面还坐落着一个南武国有名的武道世家,沈家! +沈家有着数千年的历史,至今的实力依然强大,底蕴丰厚,能屹立不倒数千年,就证明沈家的强大。 +沈家富可敌国,是卧虎城里面最强大的势力,光沈家山庄就占地数千亩,山庄里面庭院无数,花园众多,有山有水,即便有人想潜入沈家,也会在里面迷路的。 +天虎园,从名字来看,就知道这是沈天虎的府邸,作为沈家地位显赫的人,有一座巨大的宅院也是很正常的事情。 +“老爹,我回来了!”沈翔一回来,就急忙奔向书房,他知道他父亲在那里。 +沈天虎呵呵笑道:“你这臭小子终于回来了,你可知道有个小美人在等你?你还记得薛家的小丫头吗?就是你的那个小妻子。” +沈翔眉头一皱,脑海中顿时浮现出一个粉雕玉琢的漂亮小丫头来,那是他五六岁时候的事情了。 +“老爹……你是说薛仙仙?”沈翔问道,这是薛家的一个天之娇女,在她小时候曾经来沈家住过一段时间,当时沈翔整天和她玩耍,两人很玩得来,然后就订下了婚约。 +沈天虎点了点头:“没错,那小丫头就在山庄里面,你刚刚走她就来了,她可是吵着要见你。” +沈天虎说完,微笑着看向窗外,沈翔也扭过头看去,只见院子外面正有一个长身玉立的女子走来。 +女子长发披肩,全身白衣,头上带着金光发饰。见这女子一身装束宛如仙女一般,沈翔不禁看得发愣。那女子不过十四五岁年纪,肌肤胜雪,容貌秀丽,让人不可逼视。 +少女透过窗子看见沈翔,娇美无比的脸庞满是欢喜,她娇喊道:“小翔哥!” +声音轻灵婉转,动人心魄,沈翔承认,这个少女虽是豆蔻年华,但无论是气质还是容貌,都能比肩在他戒指里面的那两个美人儿,更何况这少女还没发育完全。 +沈翔咽着口水,干笑道:“真是女大十八变呀!想当年的小丫头竟然变成了小仙女。” +这少女就是薛家的天之娇女,薛仙仙。 +“这丫头是我的未婚妻!”沈翔内心激动地喊着。 +如果是以前的话,沈翔心中多少会有些疙瘩,因为他没有灵脉。但现在他有着阴阳神脉!只要给他时间,踏足武道巅峰绝不是空想。 +薛仙仙浅浅一笑,脸颊一个可爱的小酒窝伴随着迷人的红晕浮现,看得沈翔又是一阵失神。 +沈天虎大笑一声,拍了下沈翔的肩膀,说道:“你们好好叙叙旧。” +沈翔嘿笑一声,便匆匆跑出书房。 +来到院子中,沈翔二话不说,挽着薛仙仙那娇柔无骨的玉手,就快步离开他父亲的视线,就好像他们小时候偷偷做坏事一样。 +沈翔带着一个小仙女来到自己的宅院,一路上让羡煞许多沈家子弟。 +“仙仙,你要在沈家呆多久?”沈翔嘻笑着抚摸薛仙仙脸上那可爱的小酒窝。 +薛仙仙只是面露娇羞,轻声说道:“小翔哥,我和我爹爹路过卧虎城才来看看你的,所以不会呆多久。” +在小时候,薛仙仙是因为得了怪病才来卧虎城求医的,那时候薛仙仙的身体很虚弱,长得很瘦很小,在沈家和薛家都经常被人欺负,当时只有沈翔很照顾她,经常和她嬉闹。那段时间让她很开心,而且沈翔经常鼓励她,让她能坚强的与病痛争斗,还给了她几粒珍贵的丹药。 +这些都被薛仙仙铭记在心,即便她知道沈翔没有灵脉,没有任何前途,她都决定要嫁给沈翔。 +沈翔有些失落,他也十分喜欢薛仙仙,在小时候,家族确定沈翔没有灵脉之后,沈翔就没有什么玩伴,所以他和薛仙仙在一起也很开心。 +“小翔哥,家族原本要让我和你解除婚约,但我不同意,所以他们让我和爹爹去药家和那个天才炼丹师见见面。”薛仙仙看见沈翔的脸色变了,便轻笑道:“放心吧,我死也不会嫁给药家那个家伙的,听说他很坏。” +薛仙仙十四五岁就有凡武境六重的实力,在整个南武国中确实天才中的天才了,而药家里面也有一个十六岁就能炼制出丹药的天才,在许多人眼中,他们才是最配的一对。 +沈翔很淡定,他有阴阳神脉,还有两个来历不明的厉害女人指导他修炼,他有信心在短时间赶超那个最年轻的天才炼丹师。 +沈翔咧嘴一笑,伸手摸了摸薛仙仙张绝俗淡雅的脸颊,笑道:“仙仙,你去了药家就说你有未婚夫了,他不服就让他来挑战我。” +看见沈翔如此自信,薛仙仙也非常欢喜,她踮起脚尖,亲吻了一下沈翔的嘴唇,然后垂着头,红着脸说道:“小翔哥,你要加油,我担心我的家族会不惜一切的拆散我们。” +说完,薛仙仙轻轻一掠,如燕一般掠出了院子,身法飘逸,而沈翔还傻愣愣的站在那里,脑海中不断重复着薛仙仙亲吻他嘴唇的那一瞬间。 +沈翔舔了舔嘴唇,笑道:“这小丫头还真的长大了很多。” + +第4章 四象神功 +“小家伙,你说你的小仙女漂亮,还是我们师姐妹漂亮?”沈翔的脑海中突然出现一道娇媚的声音,这是苏媚瑶的声音。 +沈翔干咳两声,笑道:“只要是我的妻子都漂亮,嘿嘿,你们当然也很漂亮……对了,媚瑶姐你得快点传授我那些神功,还有炼丹什么的。” +“混小子,你这是什么意思?”苏媚瑶娇啐道。 +“没啥……”沈翔挠头傻笑着,眼眸中闪过一抹异样的光芒。 +为了不让薛仙仙失望,沈翔打算日夜不眠,加倍努力去修炼! +沈翔那带着隐形戒指的手指突然闪烁起一丝微弱的光芒,只见白幽幽和苏媚瑶出现在他的面前,她们身上都穿着紫色的衣裙,闲得更加高贵端庄,沈翔猜测这是存放在那戒指里面的。 +此时是下午,太阳十分毒辣,但两个绝色女子却好像十分享受这种让人讨厌的毒辣阳光。 +“这里的灵气真差,不依靠丹药的话,难以进入真武境!”苏媚瑶仰头望着烈阳,低喃着。 +沈翔有些头大,这两个女人竟然可以自由的初入那枚戒指,而且她们还能听到外面的声音。 +沈翔跟着她们进入了那小宅子,里面不是很大,只有一个厅四间房,在偌大的沈家来说算是很小的了。 +小厅中,苏媚瑶和白幽幽喝着沈翔泡的灵茶,这可是沈翔珍藏的东西,平时他自己都舍不得喝。 +“在这种灵气稀薄的地方,你要快速提升的话只有通过丹药了,不过在此之前,你得先修炼出真气之火来,你有阴阳神脉,这对你来说并不是很难。”苏媚瑶说道。 +沈翔点了点头,说道:“还请媚瑶姐指教。” +“你过来,我先传授你太极神功和四象神功。”苏媚瑶娇声说道,她抚着秀发,煞是风情万种,因为沈翔有着无限的潜力,苏媚瑶也想好好把他培养起来,这样以后她也能有一个厉害的帮手。 +苏媚瑶把纤手捂在沈翔的额头上,然后闭上淹没,而沈翔的脑海中也出现了许多文字,每一句都晦涩难懂,但后来他又不知不觉地融会贯通了。 +苏媚瑶说道:“这是我用神识给你传功,我不仅仅把神功传给你,还把我对神功的理解传给你,所以你只要照着修炼就行了!” +沈翔心中微微惊讶着,他没想到这苏媚瑶竟然还能使用“神识”,神识是凡武境六重才能修炼出来的,是精神力和真力融合在一起诞生的,是一种无形无态的精神力量,十分神异。 +“对于我们的事情你一定要保密,绝不能告诉人任何人,包括你那小妻子!”白幽幽神情严肃,冷冷说道。 +苏媚瑶说道:“我可以告诉你,我和师姐的仇人很多,我们那些仇人随便一个都能用手指头捏死你,所以你必须要保密!而我们也不会随便从戒指里面出来。” +沈翔心中惊骇,他没想到自己竟然收容了两个这么危险的女人,他急忙点了点头,而白幽幽和苏媚瑶也突然消失,进入了那戒指里面。 +太极神功,四象神功!虽然只是一小部分而已,但沈翔却激动无比,毕竟不是什么人都能修炼神功的! +在这个武道世界中,武功分为凡级,灵级、玄级、地级、天级、圣级、神级,又分上下乘,丹药也是如此划分,只不过是分为上中下三品。 +天级的武功都是非常难得了,神级武功就更加稀有! +神功,那可是传说中神明修炼的神功,强大无比,修炼之后,能拥有翻江倒海,召唤雷电的力量,甚至还能逆天改命,破碎虚空,登天成神! +太极神功是主功法,是专门练气练体用的,是一门非常深奥而且强大的武功心法。 +而四象神功分为四部分,分别是青龙神功,白虎神功,朱雀神功,玄武神功,包罗万象,这四门神功是攻击或者防御的辅助型武功,修炼之后,能施展强大的武技。 +沈翔来到后院,那些青翠欲滴的树木,呼吸着清新的空气,他此时能非常清晰的感应到天地间的五行灵气。 +普通的灵脉是分属性的,而沈翔的阴阳神脉却代表着天地五行,所以他能修炼五行真气! +太极神功虽然是玄奥的练功心法,但经过苏媚瑶的神识传功之后,沈翔能融会贯通。他运转太极神功,他丹田中的太极阴阳图就立即旋转起来,天地灵气顿时狂涌入他的身体之中。 +上品灵脉吸收灵气的速度都非常之快了,神脉就更加不用说!此时沈翔盘腿坐在洞口运转着太极神功,灵气进入他的身体,立即被他提纯,化成一股纯白色的暖流,在他身体的骨骼、肌肉、经脉中游走,蕴养着他的身体内部。 +想要成为一个强大的武者,就要内外兼修。沈翔现在就是在强化身体内部,使他身体内部诞生出一种蕴含强大力量的气流,也就是真气! +沈翔不知不觉进入了修炼状态之中,灵气疯狂涌入沈翔的身体中,如果有人看见,一定会认为被吓得一大跳。 +拥有阴阳神脉和太极神功,能让他快速吸收天地灵气,而太极神功能快速炼化灵气为真气,因此他才能突飞猛进。 +而沈翔如今拥有的太极神功和四象神功都是神级武功,他要发挥出神功的威力,就得拥有更加浑厚的真气。 +青龙神功和白虎神功是攻击力最强的,朱雀神功主要是火焰方面,玄武神功就是防御。 +修炼真气到大乘,能在丹田中凝聚出形态,如果沈翔修炼四象神功的话,到时候他会在丹田内凝聚出青龙、朱雀、白虎、玄武,这四尊神兽之象! +这意味着沈翔一个人却要修炼四个人的份量,虽然会让他很辛苦,但他掌握的力量却要超乎常人。 +大量的灵气涌入他的身体之中,被他运转太极神功简直炼化成精纯的真气!真气流动在他身体内的大小脉络之中,让他身体内的经脉变得更加粗壮。 +次日清晨,温暖宜人的阳光射入在小宅子的后院,照在沈翔的身上。沈翔睁开眼睛,双目如电,缭绕在他身体外面的白色雾气顿时散开,只见他的身上满是黑色的污渍,他此时已经进入了凡武境第四重! +凡武境四到六重又是一个阶段,如今沈翔到达了第四重,通脉境!迈过了一道困扰着他多年的坎。 +沈翔双目微微闭上,凝神内视着他丹田里面的那副太极阴阳图,发现太极阴阳图里面出现了五个真气漩涡,分别在四方和中心!而平常人只有一个,他现在却有五个!他真气的浑厚程度可想而知。 +凭借他的真气浑厚程度,他的实力能直逼凡武境五重。 +“终于迈入凡武境四重了,老子以后不用去采药给那些老家伙了!”沈翔心中狂喜,只是一夜之间他就突破了。 +而当他想到薛仙仙十五岁就进入了凡武境六重,他心情又平静下来,想到药家那年轻的天才炼药师打他未婚妻的主意,沈翔心中一狠,他要更加发奋修炼,然后前往药家,用挑战的方式把那天才炼丹师打败。 +“现在就开始修炼青龙神功!”苏媚瑶说道,她的神识恢复了不少,能洞察到外面的变化。 +四象神功总的来说只是一门神功而已,但其中却包罗万象,里面就包涵着拳法,掌法、腿法、轻功、等等,融会贯通之后,想怎么施展就怎么施展,玄妙无穷。 +青龙是木属性的,木属性中又有风和雷属,所以修炼青龙神功能施展出具有风雷力量的武功来。 +沈翔清洗身体,饱餐一顿之后,就来到后院中继续修炼青龙神功! +太极神功和四象神功已融入了他的灵魂之中,不管是多么高深玄奥,他都能理解! +半天过去,沈翔按照青龙神功去运转体内的真气,只见他的身体溢出一阵青色柔光,青光之中闪烁着丝丝雷电,同时还有一阵轻风从沈翔的身体中轻飘出来。 +沈翔现在已经掌握了从木属性的真气之中释放出雷电和风的力量来,只不过这种程度还不够,要能将木属性真气转化为强大的雷电和狂风才能施展青龙神功中的武技。 +沈翔控制着体内的木属性真气以一种奇异的方式流动在身体的经脉之中,循环流动着,真气流动的方式飞诡异,时快时慢,有时候如同大江奔流一般的冲刺,有时候又如溪水潺潺…… \ No newline at end of file