fix: 优化布局
This commit is contained in:
parent
f02e962b22
commit
cf64f5a71f
|
|
@ -0,0 +1,598 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>全能AI绘画提示词工厂 公众号:AI星河</title>
|
||||
<!-- 基础样式 -->
|
||||
<style>
|
||||
body { font-family: "Microsoft YaHei", sans-serif; max-width: 1000px; margin: 20px auto; padding: 20px; }
|
||||
.section, .category-group { margin-bottom: 25px; padding: 15px; border: 1px solid #eee; border-radius: 8px; }
|
||||
h2, h3 { color: #2c3e50; margin: 10px 0; }
|
||||
input[type="text"], select { width: 98%; padding: 8px; margin: 5px 0; }
|
||||
button { background: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; }
|
||||
#output { background: #f9f9f9; padding: 15px; margin-top: 20px; white-space: pre-wrap; }
|
||||
.columns { display: grid; grid-template-columns: repeat(2,1fr); gap: 20px; }
|
||||
.color-preview { width: 20px; height: 20px; display: inline-block; margin-right: 5px; }
|
||||
/* 折叠模块样式 */
|
||||
.category-group { margin:15px 0; padding:10px; border-left:4px solid #3498db; }
|
||||
.collapsible { background:#f8f9fa; padding:10px; cursor:pointer; }
|
||||
.content { display:none; padding:10px; }
|
||||
.multi-column { display: flex; flex-wrap: wrap; gap: 20px; }
|
||||
.tag-item { display:inline-block; padding:5px 10px; margin:2px; background:#eef; border-radius:3px; }
|
||||
input[type="checkbox"] { margin-right:5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎨 AI绘画提示词超级工厂</h1>
|
||||
|
||||
<!-- 主体描述 -->
|
||||
<div class="section">
|
||||
<h3>1. 主体描述 (自由输入)</h3>
|
||||
<input type="text" id="mainSubject" placeholder="示例:戴VR眼镜的上班族/手握光剑的柴犬/发光水母群..." style="width:100%">
|
||||
</div>
|
||||
|
||||
<!-- 折叠式分类模块 -->
|
||||
<div class="category-group">
|
||||
<h2>基础参数</h2>
|
||||
<div class="collapsible" onclick="toggleCollapse(this)">🔦 光影与照明(点击展开)</div>
|
||||
<div class="content multi-column">
|
||||
<label><input type="checkbox" value="戏剧灯光"> 戏剧灯光</label>
|
||||
<label><input type="checkbox" value="体积光"> 体积光</label>
|
||||
<label><input type="checkbox" value="温暖光辉"> 温暖光辉</label>
|
||||
<label><input type="checkbox" value="微光"> 微光</label>
|
||||
<label><input type="checkbox" value="外太空观"> 外太空观</label>
|
||||
<label><input type="checkbox" value="太阳光"> 太阳光</label>
|
||||
<label><input type="checkbox" value="手电筒照明"> 手电筒照明</label>
|
||||
<label><input type="checkbox" value="闪耀星空"> 闪耀星空</label>
|
||||
<label><input type="checkbox" value="色光"> 色光</label>
|
||||
<label><input type="checkbox" value="赛博朋克光"> 赛博朋克光</label>
|
||||
<label><input type="checkbox" value="柔软的光线"> 柔软的光线</label>
|
||||
<label><input type="checkbox" value="柔和烛光"> 柔和烛光</label>
|
||||
<label><input type="checkbox" value="柔和月光"> 柔和月光</label>
|
||||
<label><input type="checkbox" value="柔和的照明/柔光"> 柔和的照明/柔光</label>
|
||||
<label><input type="checkbox" value="熔岩光芒"> 熔岩光芒</label>
|
||||
<label><input type="checkbox" value="全局照明"> 全局照明</label>
|
||||
<label><input type="checkbox" value="情绪照明"> 情绪照明</label>
|
||||
<label><input type="checkbox" value="强光"> 强光</label>
|
||||
<label><input type="checkbox" value="前灯"> 前灯</label>
|
||||
<label><input type="checkbox" value="氛围照明"> 氛围照明</label>
|
||||
<label><input type="checkbox" value="暖昧光晕"> 暖昧光晕</label>
|
||||
<label><input type="checkbox" value="暖光"> 暖光</label>
|
||||
<label><input type="checkbox" value="逆光"> 逆光</label>
|
||||
<label><input type="checkbox" value="霓虹灯冷光"> 霓虹灯冷光</label>
|
||||
<label><input type="checkbox" value="内部发光"> 内部发光</label>
|
||||
<label><input type="checkbox" value="明亮高光"> 明亮高光</label>
|
||||
<label><input type="checkbox" value="明亮的光线"> 明亮的光线</label>
|
||||
<label><input type="checkbox" value="明暗分明"> 明暗分明</label>
|
||||
<label><input type="checkbox" value="梦幻雾气"> 梦幻雾气</label>
|
||||
<label><input type="checkbox" value="梦幻光芒"> 梦幻光芒</label>
|
||||
<label><input type="checkbox" value="轮廓光"> 轮廓光</label>
|
||||
<label><input type="checkbox" value="伦勃朗照明"> 伦勃朗照明</label>
|
||||
<label><input type="checkbox" value="冷光"> 冷光</label>
|
||||
<label><input type="checkbox" value="蓝色时间"> 蓝色时间</label>
|
||||
<label><input type="checkbox" value="激光"> 激光</label>
|
||||
<label><input type="checkbox" value="火光"> 火光</label>
|
||||
<label><input type="checkbox" value="黄昏射线"> 黄昏射线</label>
|
||||
<label><input type="checkbox" value="化学发光"> 化学发光</label>
|
||||
<label><input type="checkbox" value="暗黑氛围"> 暗黑氛围</label>
|
||||
<label><input type="checkbox" value="好看的灯光"> 好看的灯光</label>
|
||||
<label><input type="checkbox" value="光线追踪"> 光线追踪</label>
|
||||
<label><input type="checkbox" value="光粒子效果"> 光粒子效果</label>
|
||||
<label><input type="checkbox" value="光斑"> 光斑</label>
|
||||
<label><input type="checkbox" value="高对比度"> 高对比度</label>
|
||||
<label><input type="checkbox" value="干净的背景趋势"> 干净的背景趋势</label>
|
||||
<label><input type="checkbox" value="分体照明"> 分体照明</label>
|
||||
<label><input type="checkbox" value="放射发光"> 放射发光</label>
|
||||
<label><input type="checkbox" value="泛光灯"> 泛光灯</label>
|
||||
<label><input type="checkbox" value="反光"> 反光</label>
|
||||
<label><input type="checkbox" value="顶光"> 顶光</label>
|
||||
<label><input type="checkbox" value="丁达尔效应"> 丁达尔效应</label>
|
||||
<label><input type="checkbox" value="电致发光线"> 电致发光线</label>
|
||||
<label><input type="checkbox" value="电影光"> 电影光</label>
|
||||
<label><input type="checkbox" value="迪斯科灯光"> 迪斯科灯光</label>
|
||||
<label><input type="checkbox" value="晨光"> 晨光</label>
|
||||
<label><input type="checkbox" value="侧光"> 侧光</label>
|
||||
<label><input type="checkbox" value="彩虹火花"> 彩虹火花</label>
|
||||
<label><input type="checkbox" value="边缘光"> 边缘光</label>
|
||||
<label><input type="checkbox" value="背光照明"> 背光照明</label>
|
||||
<label><input type="checkbox" value="斑驳光线"> 斑驳光线</label>
|
||||
<label><input type="checkbox" value="暗黑的"> 暗黑的</label>
|
||||
<label><input type="checkbox" value="安静恬淡"> 安静恬淡</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="category-group">
|
||||
<div class="collapsible" onclick="toggleCollapse(this)">🎬 镜头与构图(点击展开)</div>
|
||||
<div class="content multi-column">
|
||||
<label><input type="checkbox" value="电影镜头"> 电影镜头</label>
|
||||
<label><input type="checkbox" value="荷兰角"> 荷兰角(倾斜构图)</label>
|
||||
<label><input type="checkbox" value="广角镜头"> 广角镜头</label>
|
||||
<label><input type="checkbox" value="对称构图"> 对称构图</label>
|
||||
<label><input type="checkbox" value="三分法构图"> 三分法构图</label>
|
||||
<label><input type="checkbox" value="鸟瞰图"> 鸟瞰图(俯视图)</label>
|
||||
<label><input type="checkbox" value="低角度镜头"> 低角度镜头</label>
|
||||
<label><input type="checkbox" value="微距拍摄"> 微距拍摄</label>
|
||||
<label><input type="checkbox" value="长焦镜头"> 长焦镜头</label>
|
||||
<label><input type="checkbox" value="远景镜头"> 远景镜头</label>
|
||||
<label><input type="checkbox" value="中景镜头"> 中景镜头</label>
|
||||
<label><input type="checkbox" value="特写镜头"> 特写镜头</label>
|
||||
<label><input type="checkbox" value="过肩镜头"> 过肩镜头</label>
|
||||
<label><input type="checkbox" value="环绕镜头"> 环绕镜头</label>
|
||||
<label><input type="checkbox" value="推轨镜头"> 推轨镜头(Dolly Shot)</label>
|
||||
<label><input type="checkbox" value="拉镜头"> 拉镜头(Zoom Out)</label>
|
||||
<label><input type="checkbox" value="推镜头"> 推镜头(Zoom In)</label>
|
||||
<label><input type="checkbox" value="手持拍摄"> 手持拍摄</label>
|
||||
<label><input type="checkbox" value="稳定器拍摄"> 稳定器拍摄</label>
|
||||
<label><input type="checkbox" value="鱼眼镜头"> 鱼眼镜头</label>
|
||||
<label><input type="checkbox" value="高角度镜头"> 高角度镜头</label>
|
||||
<label><input type="checkbox" value="第一人称视角"> 第一人称视角</label>
|
||||
<label><input type="checkbox" value="反射镜头"> 反射镜头(通过镜子或水面等反射物体拍摄)</label>
|
||||
<label><input type="checkbox" value="光影对比构图"> 光影对比构图</label>
|
||||
<label><input type="checkbox" value="引导线构图"> 引导线构图</label>
|
||||
<label><input type="checkbox" value="框架构图"> 框架构图(使用框架元素引导观众视线)</label>
|
||||
<label><input type="checkbox" value="对角线构图"> 对角线构图</label>
|
||||
<label><input type="checkbox" value="黄金分割构图"> 黄金分割构图</label>
|
||||
<label><input type="checkbox" value="散点构图"> 散点构图</label>
|
||||
<label><input type="checkbox" value="重复构图"> 重复构图</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="category-group">
|
||||
<div class="collapsible" onclick="toggleCollapse(this)">🌍 环境场景(点击展开)</div>
|
||||
<div class="content multi-column">
|
||||
<label><input type="checkbox" value="哥特式建筑"> 哥特式建筑</label>
|
||||
<label><input type="checkbox" value="赛博朋克城市"> 赛博朋克城市</label>
|
||||
<label><input type="checkbox" value="魔法森林"> 魔法森林</label>
|
||||
<label><input type="checkbox" value="未来都市"> 未来都市</label>
|
||||
<label><input type="checkbox" value="水晶洞穴"> 水晶洞穴</label>
|
||||
<label><input type="checkbox" value="水下世界"> 水下世界</label>
|
||||
<label><input type="checkbox" value="末日城市"> 末日城市</label>
|
||||
<label><input type="checkbox" value="魔法花园"> 魔法花园</label>
|
||||
<label><input type="checkbox" value="古典庄园"> 古典庄园</label>
|
||||
<label><input type="checkbox" value="荒野沙漠"> 荒野沙漠</label>
|
||||
<label><input type="checkbox" value="繁华市集"> 繁华市集</label>
|
||||
<label><input type="checkbox" value="冰雪王国"> 冰雪王国</label>
|
||||
<label><input type="checkbox" value="熔岩地带"> 熔岩地带</label>
|
||||
<label><input type="checkbox" value="风车草原"> 风车草原</label>
|
||||
<label><input type="checkbox" value="海盗港湾"> 海盗港湾</label>
|
||||
<label><input type="checkbox" value="空中浮岛"> 空中浮岛</label>
|
||||
<label><input type="checkbox" value="废弃工厂"> 废弃工厂</label>
|
||||
<label><input type="checkbox" value="幻想城堡"> 幻想城堡</label>
|
||||
<label><input type="checkbox" value="神秘遗迹"> 神秘遗迹</label>
|
||||
<label><input type="checkbox" value="星际空间站"> 星际空间站</label>
|
||||
<label><input type="checkbox" value="热带雨林"> 热带雨林</label>
|
||||
<label><input type="checkbox" value="高山村落"> 高山村落</label>
|
||||
<label><input type="checkbox" value="科技实验室"> 科技实验室</label>
|
||||
<label><input type="checkbox" value="古老神庙"> 古老神庙</label>
|
||||
<label><input type="checkbox" value="幽灵船"> 幽灵船</label>
|
||||
<label><input type="checkbox" value="蒸汽朋克飞艇"> 蒸汽朋克飞艇</label>
|
||||
<label><input type="checkbox" value="魔法图书馆"> 魔法图书馆</label>
|
||||
<label><input type="checkbox" value="未来农场"> 未来农场</label>
|
||||
<label><input type="checkbox" value="古代战场"> 古代战场</label>
|
||||
<label><input type="checkbox" value="神秘沼泽"> 神秘沼泽</label>
|
||||
<label><input type="checkbox" value="高科技实验室"> 高科技实验室</label>
|
||||
<label><input type="checkbox" value="梦幻岛屿"> 梦幻岛屿</label>
|
||||
<label><input type="checkbox" value="宇宙飞船内部"> 宇宙飞船内部</label>
|
||||
<label><input type="checkbox" value="废墟城市"> 废墟城市</label>
|
||||
<label><input type="checkbox" value="繁花山谷"> 繁花山谷</label>
|
||||
<label><input type="checkbox" value="幽静竹林"> 幽静竹林</label>
|
||||
<label><input type="checkbox" value="火山口"> 火山口</label>
|
||||
<label><input type="checkbox" value="沙漠绿洲"> 沙漠绿洲</label>
|
||||
<label><input type="checkbox" value="失落的文明"> 失落的文明</label>
|
||||
<label><input type="checkbox" value="风暴海岸"> 风暴海岸</label>
|
||||
<label><input type="checkbox" value="雪山之巅"> 雪山之巅</label>
|
||||
<label><input type="checkbox" value="巨人遗迹"> 巨人遗迹</label>
|
||||
<label><input type="checkbox" value="神秘洞窟"> 神秘洞窟</label>
|
||||
<label><input type="checkbox" value="星际港口"> 星际港口</label>
|
||||
<label><input type="checkbox" value="海底古城"> 海底古城</label>
|
||||
<label><input type="checkbox" value="空中花园"> 空中花园</label>
|
||||
<label><input type="checkbox" value="神秘迷宫"> 神秘迷宫</label>
|
||||
<label><input type="checkbox" value="魔法学院"> 魔法学院</label>
|
||||
<label><input type="checkbox" value="时光隧道"> 时光隧道</label>
|
||||
<label><input type="checkbox" value="荒芜星球"> 荒芜星球</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="category-group">
|
||||
<div class="collapsible" onclick="toggleCollapse(this)">🎨 艺术风格(点击展开)</div>
|
||||
<div class="content multi-column">
|
||||
<label><input type="checkbox" value="赛博朋克"> 赛博朋克</label>
|
||||
<label><input type="checkbox" value="梵高风格"> 梵高风格</label>
|
||||
<label><input type="checkbox" value="水彩画"> 水彩画</label>
|
||||
<label><input type="checkbox" value="概念艺术"> 概念艺术</label>
|
||||
<label><input type="checkbox" value="日本动画"> 日本动画</label>
|
||||
<label><input type="checkbox" value="蒸汽朋克"> 蒸汽朋克</label>
|
||||
<label><input type="checkbox" value="低多边形"> 低多边形</label>
|
||||
<label><input type="checkbox" value="超现实主义"> 超现实主义</label>
|
||||
<label><input type="checkbox" value="新艺术"> 新艺术</label>
|
||||
<label><input type="checkbox" value="童话故事书插图"> 童话故事书插图</label>
|
||||
<label><input type="checkbox" value="角色概念艺术"> 角色概念艺术</label>
|
||||
<label><input type="checkbox" value="野兽派"> 野兽派</label>
|
||||
<label><input type="checkbox" value="皮克斯"> 皮克斯</label>
|
||||
<label><input type="checkbox" value="空间主义"> 空间主义</label>
|
||||
<label><input type="checkbox" value="彩墨纸本"> 彩墨纸本</label>
|
||||
<label><input type="checkbox" value="电影摄影"> 电影摄影</label>
|
||||
<label><input type="checkbox" value="国风"> 国风</label>
|
||||
<label><input type="checkbox" value="幻想现实主义"> 幻想现实主义</label>
|
||||
<label><input type="checkbox" value="光与空间"> 光与空间</label>
|
||||
<label><input type="checkbox" value="次表面散射"> 次表面散射</label>
|
||||
<label><input type="checkbox" value="拟人化"> 拟人化</label>
|
||||
<label><input type="checkbox" value="向量图"> 向量图</label>
|
||||
<label><input type="checkbox" value="抽象艺术"> 抽象艺术</label>
|
||||
<label><input type="checkbox" value="日本海报"> 日本海报</label>
|
||||
<label><input type="checkbox" value="武器设计"> 武器设计</label>
|
||||
<label><input type="checkbox" value="CGSociety"> CGSociety</label>
|
||||
<label><input type="checkbox" value="光艺"> 光艺</label>
|
||||
<label><input type="checkbox" value="电影般的"> 电影般的</label>
|
||||
<label><input type="checkbox" value="设计风"> 设计风</label>
|
||||
<label><input type="checkbox" value="数码艺术"> 数码艺术</label>
|
||||
<label><input type="checkbox" value="传统感"> 传统感</label>
|
||||
<label><input type="checkbox" value="超强的时尚设计纹理"> 超强的时尚设计纹理</label>
|
||||
<label><input type="checkbox" value="相交的平面"> 相交的平面</label>
|
||||
<label><input type="checkbox" value="时尚潮流"> 时尚潮流</label>
|
||||
<label><input type="checkbox" value="宫崎骏风格"> 宫崎骏风格</label>
|
||||
<label><input type="checkbox" value="线条"> 线条</label>
|
||||
<label><input type="checkbox" value="波普艺术"> 波普艺术</label>
|
||||
<label><input type="checkbox" value="矢量插画"> 矢量插画</label>
|
||||
<label><input type="checkbox" value="重叠形状"> 重叠形状</label>
|
||||
<label><input type="checkbox" value="几何形状"> 几何形状</label>
|
||||
<label><input type="checkbox" value="平面插画"> 平面插画</label>
|
||||
<label><input type="checkbox" value="手稿"> 手稿</label>
|
||||
<label><input type="checkbox" value="利诺剪裁"> 利诺剪裁</label>
|
||||
<label><input type="checkbox" value="复古 黑暗"> 复古 黑暗</label>
|
||||
<label><input type="checkbox" value="水墨画雕塑"> 水墨画雕塑</label>
|
||||
<label><input type="checkbox" value="梵高"> 梵高</label>
|
||||
<label><input type="checkbox" value="民族艺术"> 民族艺术</label>
|
||||
<label><input type="checkbox" value="未来主义"> 未来主义</label>
|
||||
<label><input type="checkbox" value="抽象风"> 抽象风</label>
|
||||
<label><input type="checkbox" value="日本动画片"> 日本动画片</label>
|
||||
<label><input type="checkbox" value="巴洛克时期"> 巴洛克时期</label>
|
||||
<label><input type="checkbox" value="像素画"> 像素画</label>
|
||||
<label><input type="checkbox" value="绗缝艺术"> 绗缝艺术</label>
|
||||
<label><input type="checkbox" value="古典风,18-19世纪"> 古典风,18-19世纪</label>
|
||||
<label><input type="checkbox" value="黑白电影时期"> 黑白电影时期</label>
|
||||
<label><input type="checkbox" value="乡村"> 乡村</label>
|
||||
<label><input type="checkbox" value="哥特式黑暗"> 哥特式黑暗</label>
|
||||
<label><input type="checkbox" value="室内设计"> 室内设计</label>
|
||||
<label><input type="checkbox" value="低保真艺术(洛菲艺术)"> 低保真艺术(洛菲艺术)</label>
|
||||
<label><input type="checkbox" value="涂鸦"> 涂鸦</label>
|
||||
<label><input type="checkbox" value="riso印刷风"> riso印刷风</label>
|
||||
<label><input type="checkbox" value="梦工厂影业"> 梦工厂影业</label>
|
||||
<label><input type="checkbox" value="水墨插图"> 水墨插图</label>
|
||||
<label><input type="checkbox" value="彩色玻璃窗"> 彩色玻璃窗</label>
|
||||
<label><input type="checkbox" value="立体派"> 立体派</label>
|
||||
<label><input type="checkbox" value="伏尼契手稿"> 伏尼契手稿</label>
|
||||
<label><input type="checkbox" value="吉卜力"> 吉卜力</label>
|
||||
<label><input type="checkbox" value="六七质"> 六七质</label>
|
||||
<label><input type="checkbox" value="山田章博"> 山田章博</label>
|
||||
<label><input type="checkbox" value="超写实主义"> 超写实主义</label>
|
||||
<label><input type="checkbox" value="蒸汽朋克"> 蒸汽朋克</label>
|
||||
<label><input type="checkbox" value="副岛成记"> 副岛成记</label>
|
||||
<label><input type="checkbox" value="插画"> 插画</label>
|
||||
<label><input type="checkbox" value="建构主义"> 建构主义</label>
|
||||
<label><input type="checkbox" value="超现实主义"> 超现实主义</label>
|
||||
<label><input type="checkbox" value="极简主义"> 极简主义</label>
|
||||
<label><input type="checkbox" value="抽象表现主义"> 抽象表现主义</label>
|
||||
<label><input type="checkbox" value="包豪斯"> 包豪斯</label>
|
||||
<label><input type="checkbox" value="粗犷主义"> 粗犷主义</label>
|
||||
<label><input type="checkbox" value="对称肖像"> 对称肖像</label>
|
||||
<label><input type="checkbox" value="扁平设计"> 扁平设计</label>
|
||||
<label><input type="checkbox" value="东方山水画"> 东方山水画</label>
|
||||
<label><input type="checkbox" value="魔幻现实主义"> 魔幻现实主义</label>
|
||||
<label><input type="checkbox" value="水彩"> 水彩</label>
|
||||
<label><input type="checkbox" value="达芬奇"> 达芬奇</label>
|
||||
<label><input type="checkbox" value="浮世绘"> 浮世绘</label>
|
||||
<label><input type="checkbox" value="拼贴艺术"> 拼贴艺术</label>
|
||||
<label><input type="checkbox" value="新海诚"> 新海诚</label>
|
||||
<label><input type="checkbox" value="点彩派"> 点彩派</label>
|
||||
<label><input type="checkbox" value="漫画"> 漫画</label>
|
||||
<label><input type="checkbox" value="时尚"> 时尚</label>
|
||||
<label><input type="checkbox" value="概念艺术"> 概念艺术</label>
|
||||
<label><input type="checkbox" value="充满细节"> 充满细节</label>
|
||||
<label><input type="checkbox" value="欧普艺术/光效应艺术"> 欧普艺术/光效应艺术</label>
|
||||
<label><input type="checkbox" value="水彩儿童插画"> 水彩儿童插画</label>
|
||||
<label><input type="checkbox" value="写实主义"> 写实主义</label>
|
||||
<label><input type="checkbox" value="油画"> 油画</label>
|
||||
<label><input type="checkbox" value="文艺复兴"> 文艺复兴</label>
|
||||
<label><input type="checkbox" value="90年代电视游戏"> 90年代电视游戏</label>
|
||||
<label><input type="checkbox" value="摄影"> 摄影</label>
|
||||
<label><input type="checkbox" value="法国艺术"> 法国艺术</label>
|
||||
<label><input type="checkbox" value="印象派"> 印象派</label>
|
||||
<label><input type="checkbox" value="克劳德莫奈"> 克劳德莫奈</label>
|
||||
<label><input type="checkbox" value="印刷版画"> 印刷版画</label>
|
||||
<label><input type="checkbox" value="清晰的面部特征"> 清晰的面部特征</label>
|
||||
<label><input type="checkbox" value="建筑素描"> 建筑素描</label>
|
||||
<label><input type="checkbox" value="黑白"> 黑白</label>
|
||||
<label><input type="checkbox" value="统一创作"> 统一创作</label>
|
||||
<label><input type="checkbox" value="局部解剖"> 局部解剖</label>
|
||||
<label><input type="checkbox" value="日本漫画4"> 日本漫画4</label>
|
||||
<label><input type="checkbox" value="素描"> 素描</label>
|
||||
<label><input type="checkbox" value="超前卫"> 超前卫</label>
|
||||
<label><input type="checkbox" value="剪辑"> 剪辑</label>
|
||||
<label><input type="checkbox" value="维多利亚时代"> 维多利亚时代</label>
|
||||
<label><input type="checkbox" value="新艺术 Rococo"> 新艺术 Rococo</label>
|
||||
<label><input type="checkbox" value="墨水渲染"> 墨水渲染</label>
|
||||
<label><input type="checkbox" value="动态姿势"> 动态姿势</label>
|
||||
<label><input type="checkbox" value="游戏场景图"> 游戏场景图</label>
|
||||
<label><input type="checkbox" value="雕塑 ink painting"> 雕塑 ink painting</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="category-group">
|
||||
<div class="collapsible" onclick="toggleCollapse(this)">🌈 色彩与材质(点击展开)</div>
|
||||
<div class="content multi-column">
|
||||
<label><input type="checkbox" value="鲜艳色彩"> 鲜艳色彩</label>
|
||||
<label><input type="checkbox" value="黄金时段光"> 黄金时段光</label>
|
||||
<label><input type="checkbox" value="霓虹灯光"> 霓虹灯光</label>
|
||||
<label><input type="checkbox" value="哑光质感"> 哑光质感</label>
|
||||
<label><input type="checkbox" value="金属质感"> 金属质感</label>
|
||||
<label><input type="checkbox" value="发光粒子"> 发光粒子</label>
|
||||
<label><input type="checkbox" value="透明材质"> 透明材质</label>
|
||||
<label><input type="checkbox" value="全息效果"> 全息效果</label>
|
||||
<label><input type="checkbox" value="雕刻质感"> 雕刻质感</label>
|
||||
<label><input type="checkbox" value="鱼骨图案"> 鱼骨图案</label>
|
||||
<label><input type="checkbox" value="黏土质地"> 黏土质地</label>
|
||||
<label><input type="checkbox" value="玻璃"> 玻璃</label>
|
||||
<label><input type="checkbox" value="环氧树脂"> 环氧树脂</label>
|
||||
<label><input type="checkbox" value="流体"> 流体</label>
|
||||
<label><input type="checkbox" value="毛茸茸的"> 毛茸茸的</label>
|
||||
<label><input type="checkbox" value="蛇纹图案"> 蛇纹图案</label>
|
||||
<label><input type="checkbox" value="骨骼状"> 骨骼状</label>
|
||||
<label><input type="checkbox" value="掐丝珐琅"> 掐丝珐琅</label>
|
||||
<label><input type="checkbox" value="蓬松的"> 蓬松的</label>
|
||||
<label><input type="checkbox" value="阳极氧化钛"> 阳极氧化钛</label>
|
||||
<label><input type="checkbox" value="波斯花纹"> 波斯花纹</label>
|
||||
<label><input type="checkbox" value="蕾丝"> 蕾丝</label>
|
||||
<label><input type="checkbox" value="光粒子"> 光粒子</label>
|
||||
<label><input type="checkbox" value="岩浆"> 岩浆</label>
|
||||
<label><input type="checkbox" value="发霉的"> 发霉的</label>
|
||||
<label><input type="checkbox" value="熔化的"> 熔化的</label>
|
||||
<label><input type="checkbox" value="3D分形"> 3D分形</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Midjourney特化模块 -->
|
||||
<div class="columns">
|
||||
<div>
|
||||
</div>
|
||||
|
||||
<!-- 右列 -->
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="category-group">
|
||||
<div class="collapsible" onclick="toggleCollapse(this)">🎨 艺术媒介 (Pick A Medium)</div>
|
||||
<div class="content multi-column">
|
||||
<label><input type="checkbox" value="木版画"> 木版画</label>
|
||||
<label><input type="checkbox" value="圆珠笔素描"> 圆珠笔素描</label>
|
||||
<label><input type="checkbox" value="蓝晒法"> 蓝晒法</label>
|
||||
<label><input type="checkbox" value="浮世绘"> 浮世绘</label>
|
||||
<label><input type="checkbox" value="水彩晕染"> 水彩晕染</label>
|
||||
<label><input type="checkbox" value="像素艺术"> 像素艺术</label>
|
||||
<label><input type="checkbox" value="Risograph印刷"> Risograph印刷</label>
|
||||
<label><input type="checkbox" value="黑光绘画"> 黑光绘画</label>
|
||||
<label><input type="checkbox" value="丙烯倒流"> 丙烯倒流</label>
|
||||
<label><input type="checkbox" value="3D浮雕"> 3D浮雕</label>
|
||||
<label><input type="checkbox" value="油画"> 油画</label>
|
||||
<label><input type="checkbox" value="炭笔素描"> 炭笔素描</label>
|
||||
<label><input type="checkbox" value="铅笔素描"> 铅笔素描</label>
|
||||
<label><input type="checkbox" value="粉彩画"> 粉彩画</label>
|
||||
<label><input type="checkbox" value="墨水画"> 墨水画</label>
|
||||
<label><input type="checkbox" value="马克笔绘画"> 马克笔绘画</label>
|
||||
<label><input type="checkbox" value="刮画"> 刮画</label>
|
||||
<label><input type="checkbox" value="丝网印刷"> 丝网印刷</label>
|
||||
<label><input type="checkbox" value="铜版画"> 铜版画</label>
|
||||
<label><input type="checkbox" value="石版画"> 石版画</label>
|
||||
<label><input type="checkbox" value="拼贴画"> 拼贴画</label>
|
||||
<label><input type="checkbox" value="数字绘画"> 数字绘画</label>
|
||||
<label><input type="checkbox" value="摄影"> 摄影</label>
|
||||
<label><input type="checkbox" value="雕塑"> 雕塑(包括石雕、木雕等)</label>
|
||||
<label><input type="checkbox" value="陶艺"> 陶艺</label>
|
||||
<label><input type="checkbox" value="玻璃艺术"> 玻璃艺术</label>
|
||||
<label><input type="checkbox" value="纤维艺术"> 纤维艺术(如编织、刺绣)</label>
|
||||
<label><input type="checkbox" value="装置艺术"> 装置艺术</label>
|
||||
<label><input type="checkbox" value="行为艺术"> 行为艺术</label>
|
||||
<label><input type="checkbox" value="视频艺术"> 视频艺术</label>
|
||||
<label><input type="checkbox" value="水粉画"> 水粉画</label>
|
||||
<label><input type="checkbox" value="手工艺品"> 手工艺品(如剪纸、折纸)</label>
|
||||
<label><input type="checkbox" value="热蜡画"> 热蜡画</label>
|
||||
<label><input type="checkbox" value="铁艺"> 铁艺</label>
|
||||
<label><input type="checkbox" value="漫画与插画"> 漫画与插画</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="category-group">
|
||||
<div class="collapsible" onclick="toggleCollapse(this)">⏳ 年代风格 (Time Travel)</div>
|
||||
<div class="content multi-column">
|
||||
<label><input type="checkbox" value="15世纪手抄本风格"> 15世纪手抄本风格</label>
|
||||
<label><input type="checkbox" value="1920装饰艺术风"> 1920装饰艺术风</label>
|
||||
<label><input type="checkbox" value="1950复古海报"> 1950复古海报</label>
|
||||
<label><input type="checkbox" value="1980赛博科幻"> 1980赛博科幻</label>
|
||||
<label><input type="checkbox" value="2020元宇宙美学"> 2020元宇宙美学</label>
|
||||
<label><input type="checkbox" value="古希腊与罗马风格"> 古希腊与罗马风格</label>
|
||||
<label><input type="checkbox" value="中世纪风格"> 中世纪风格</label>
|
||||
<label><input type="checkbox" value="文艺复兴风格"> 文艺复兴风格</label>
|
||||
<label><input type="checkbox" value="巴洛克风格"> 巴洛克风格</label>
|
||||
<label><input type="checkbox" value="洛可可风格"> 洛可可风格</label>
|
||||
<label><input type="checkbox" value="新古典主义"> 新古典主义</label>
|
||||
<label><input type="checkbox" value="维多利亚时代风格"> 维多利亚时代风格</label>
|
||||
<label><input type="checkbox" value="工艺美术运动"> 工艺美术运动</label>
|
||||
<label><input type="checkbox" value="新艺术运动"> 新艺术运动</label>
|
||||
<label><input type="checkbox" value="未来主义"> 未来主义</label>
|
||||
<label><input type="checkbox" value="包豪斯风格"> 包豪斯风格</label>
|
||||
<label><input type="checkbox" value="超现实主义"> 超现实主义</label>
|
||||
<label><input type="checkbox" value="波普艺术"> 波普艺术</label>
|
||||
<label><input type="checkbox" value="后现代主义"> 后现代主义</label>
|
||||
<label><input type="checkbox" value="冷战时期科幻风格"> 冷战时期科幻风格</label>
|
||||
<label><input type="checkbox" value="蒸汽波(Vaporwave)"> 蒸汽波(Vaporwave)</label>
|
||||
<label><input type="checkbox" value="极简主义"> 极简主义</label>
|
||||
<label><input type="checkbox" value="哥特式风格"> 哥特式风格</label>
|
||||
<label><input type="checkbox" value="文艺复兴晚期及样式主义"> 文艺复兴晚期及样式主义</label>
|
||||
<label><input type="checkbox" value="工业革命时期风格"> 工业革命时期风格</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="category-group">
|
||||
<div class="collapsible" onclick="toggleCollapse(this)">😄 情感表达 (Emote)</div>
|
||||
<div class="content multi-column">
|
||||
<label><input type="checkbox" value="坚毅的"> 坚毅的</label>
|
||||
<label><input type="checkbox" value="慵懒的"> 悠懒的</label>
|
||||
<label><input type="checkbox" value="愤怒的"> 愤怒的</label>
|
||||
<label><input type="checkbox" value="梦幻的"> 梦幻的</label>
|
||||
<label><input type="checkbox" value="忧郁的"> 忧郁的</label>
|
||||
<label><input type="checkbox" value="欢快的"> 欢快的</label>
|
||||
<label><input type="checkbox" value="悲伤的"> 悲伤的</label>
|
||||
<label><input type="checkbox" value="惊讶的"> 惊讶的</label>
|
||||
<label><input type="checkbox" value="恐惧的"> 恐惧的</label>
|
||||
<label><input type="checkbox" value="平静的"> 平静的</label>
|
||||
<label><input type="checkbox" value="焦虑的"> 焦虑的</label>
|
||||
<label><input type="checkbox" value="兴奋的"> 兴奋的</label>
|
||||
<label><input type="checkbox" value="温柔的"> 温柔的</label>
|
||||
<label><input type="checkbox" value="困惑的"> 困惑的</label>
|
||||
<label><input type="checkbox" value="羞涩的"> 羞涩的</label>
|
||||
<label><input type="checkbox" value="自信的"> 自信的</label>
|
||||
<label><input type="checkbox" value="嫉妒的"> 嫉妒的</label>
|
||||
<label><input type="checkbox" value="感激的"> 感激的</label>
|
||||
<label><input type="checkbox" value="好奇的"> 好奇的</label>
|
||||
<label><input type="checkbox" value="失望的"> 失望的</label>
|
||||
<label><input type="checkbox" value="满足的"> 满足的</label>
|
||||
<label><input type="checkbox" value="孤独的"> 孤独的</label>
|
||||
<label><input type="checkbox" value="浪漫的"> 浪漫的</label>
|
||||
<label><input type="checkbox" value="紧张的"> 紧张的</label>
|
||||
<label><input type="checkbox" value="放松的"> 放松的</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="category-group">
|
||||
<div class="collapsible" onclick="toggleCollapse(this)">🌈 色彩方案 (Get Colorful)</div>
|
||||
<div class="content multi-column">
|
||||
<label><input type="checkbox" value="千禧粉"> 千禧粉</label>
|
||||
<label><input type="checkbox" value="酸性绿"> 酸性绿</label>
|
||||
<label><input type="checkbox" value="香草奶油"> 香草奶油</label>
|
||||
<label><input type="checkbox" value="数码蓝"> 数码蓝</label>
|
||||
<label><input type="checkbox" value="大地棕"> 大地棕</label>
|
||||
<label><input type="checkbox" value="森林绿"> 森林绿</label>
|
||||
<label><input type="checkbox" value="天空蓝"> 天空蓝</label>
|
||||
<label><input type="checkbox" value="沙色"> 沙色</label>
|
||||
<label><input type="checkbox" value="复古红"> 复古红</label>
|
||||
<label><input type="checkbox" value="芥末黄"> 芥末黄</label>
|
||||
<label><input type="checkbox" value="橄榄绿"> 橄榄绿</label>
|
||||
<label><input type="checkbox" value="深蓝"> 深蓝</label>
|
||||
<label><input type="checkbox" value="极简白"> 极简白</label>
|
||||
<label><input type="checkbox" value="高级灰"> 高级灰</label>
|
||||
<label><input type="checkbox" value="炭黑"> 炭黑</label>
|
||||
<label><input type="checkbox" value="金属银"> 金属银</label>
|
||||
<label><input type="checkbox" value="亮橙"> 亮橙</label>
|
||||
<label><input type="checkbox" value="柠檬黄"> 柠檬黄</label>
|
||||
<label><input type="checkbox" value="电光紫"> 电光紫</label>
|
||||
<label><input type="checkbox" value="荧光绿"> 荧光绿</label>
|
||||
<label><input type="checkbox" value="薄荷绿"> 薄荷绿</label>
|
||||
<label><input type="checkbox" value="淡紫"> 淡紫</label>
|
||||
<label><input type="checkbox" value="浅粉"> 浅粉</label>
|
||||
<label><input type="checkbox" value="奶油黄"> 奶油黄</label>
|
||||
<label><input type="checkbox" value="深红"> 深红</label>
|
||||
<label><input type="checkbox" value="墨绿"> 墨绿</label>
|
||||
<label><input type="checkbox" value="海军蓝"> 海军蓝</label>
|
||||
<label><input type="checkbox" value="深紫"> 深紫</label>
|
||||
<label><input type="checkbox" value="日落渐变"> 日落渐变</label>
|
||||
<label><input type="checkbox" value="海洋渐变"> 海洋渐变</label>
|
||||
<label><input type="checkbox" value="霓虹渐变"> 霓虹渐变</label>
|
||||
<label><input type="checkbox" value="森林渐变"> 森林渐变</label>
|
||||
<label><input type="checkbox" value="全白"> 全白</label>
|
||||
<label><input type="checkbox" value="全黑"> 全黑</label>
|
||||
<label><input type="checkbox" value="全灰"> 全灰</label>
|
||||
<label><input type="checkbox" value="全蓝"> 全蓝</label>
|
||||
<label><input type="checkbox" value="黑白"> 黑白</label>
|
||||
<label><input type="checkbox" value="红绿"> 红绿</label>
|
||||
<label><input type="checkbox" value="蓝橙"> 蓝橙</label>
|
||||
<label><input type="checkbox" value="黄紫"> 黄紫</label>
|
||||
<label><input type="checkbox" value="中国风"> 中国风</label>
|
||||
<label><input type="checkbox" value="日式风"> 日式风</label>
|
||||
<label><input type="checkbox" value="北欧风"> 北欧风</label>
|
||||
<label><input type="checkbox" value="地中海风"> 地中海风</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示词生成按钮 -->
|
||||
<button onclick="generateSmartPrompt()">🚀 生成高级提示词</button>
|
||||
<button onclick="copyPrompt()">一键复制</button>
|
||||
|
||||
<div id="output"></div>
|
||||
|
||||
<script>
|
||||
function toggleCollapse(element) {
|
||||
const content = element.nextElementSibling;
|
||||
content.style.display = content.style.display === 'block' ? 'none' : 'block';
|
||||
}
|
||||
|
||||
function generateSmartPrompt() {
|
||||
const mainSubject = document.getElementById('mainSubject').value;
|
||||
const selectedParams = [];
|
||||
document.querySelectorAll('input[type="checkbox"]:checked').forEach(checkbox => {
|
||||
selectedParams.push(checkbox.value);
|
||||
});
|
||||
|
||||
const finalPrompt = `${mainSubject}, ${selectedParams.join(', ')}, 8K超高清画质`;
|
||||
document.getElementById('output').textContent = finalPrompt;
|
||||
}
|
||||
|
||||
function copyPrompt() {
|
||||
const text = document.getElementById('output').textContent;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('提示词已复制到剪贴板!');
|
||||
});
|
||||
}
|
||||
|
||||
function addCustomOption(inputId, button) {
|
||||
const inputValue = document.getElementById(inputId).value;
|
||||
if (inputValue.trim() === '') return;
|
||||
|
||||
const label = document.createElement('label');
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.value = inputValue;
|
||||
label.appendChild(checkbox);
|
||||
label.appendChild(document.createTextNode(' ' + inputValue));
|
||||
|
||||
button.parentElement.parentElement.insertBefore(label, button.parentElement);
|
||||
|
||||
// 保存到本地存储
|
||||
let savedOptions = JSON.parse(localStorage.getItem('customOptions')) || {};
|
||||
if (!savedOptions[inputId]) savedOptions[inputId] = [];
|
||||
savedOptions[inputId].push(inputValue);
|
||||
localStorage.setItem('customOptions', JSON.stringify(savedOptions));
|
||||
|
||||
// 清空输入框
|
||||
document.getElementById(inputId).value = '';
|
||||
}
|
||||
|
||||
// 加载自定义选项
|
||||
window.onload = function() {
|
||||
const savedOptions = JSON.parse(localStorage.getItem('customOptions')) || {};
|
||||
for (const key in savedOptions) {
|
||||
savedOptions[key].forEach(value => {
|
||||
const inputId = key.replace('custom', '');
|
||||
const container = document.querySelector(`#${key}`).parentElement;
|
||||
const label = document.createElement('label');
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.value = value;
|
||||
label.appendChild(checkbox);
|
||||
label.appendChild(document.createTextNode(' ' + value));
|
||||
container.insertBefore(label, container.lastElementChild);
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { Sparkles, Send, User, Bot, Wand2, Play, Download, Loader } from 'lucide-react'
|
||||
import { Project } from '../services/projectService'
|
||||
import { Model } from '../services/modelService'
|
||||
|
||||
interface AICreationChatProps {
|
||||
project: Project
|
||||
models: Model[]
|
||||
onMaterialCreated: () => void
|
||||
}
|
||||
|
||||
interface ChatMessage {
|
||||
id: string
|
||||
type: 'user' | 'assistant' | 'system'
|
||||
content: string
|
||||
timestamp: Date
|
||||
creationTask?: CreationTask
|
||||
}
|
||||
|
||||
interface CreationTask {
|
||||
id: string
|
||||
model: Model
|
||||
prompt: string
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
progress: number
|
||||
result?: {
|
||||
videoPath: string
|
||||
thumbnailPath: string
|
||||
duration: number
|
||||
}
|
||||
error?: string
|
||||
}
|
||||
|
||||
const AICreationChat: React.FC<AICreationChatProps> = ({
|
||||
project,
|
||||
models,
|
||||
onMaterialCreated
|
||||
}) => {
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([
|
||||
{
|
||||
id: '1',
|
||||
type: 'system',
|
||||
content: `欢迎使用AI创作助手!我可以帮您为项目"${project.product_name}"创作视频素材。请告诉我您想要什么样的内容。`,
|
||||
timestamp: new Date()
|
||||
}
|
||||
])
|
||||
const [inputText, setInputText] = useState('')
|
||||
const [selectedModel, setSelectedModel] = useState<Model | null>(models[0] || null)
|
||||
const [isCreating, setIsCreating] = useState(false)
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom()
|
||||
}, [messages])
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
if (!inputText.trim() || !selectedModel) return
|
||||
|
||||
const userMessage: ChatMessage = {
|
||||
id: Date.now().toString(),
|
||||
type: 'user',
|
||||
content: inputText.trim(),
|
||||
timestamp: new Date()
|
||||
}
|
||||
|
||||
setMessages(prev => [...prev, userMessage])
|
||||
setInputText('')
|
||||
setIsCreating(true)
|
||||
|
||||
// 模拟AI响应
|
||||
setTimeout(() => {
|
||||
const assistantMessage: ChatMessage = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
type: 'assistant',
|
||||
content: `好的!我将使用模特"${selectedModel.model_number}"为您创作关于"${project.product_name}"的视频素材。正在开始创作...`,
|
||||
timestamp: new Date()
|
||||
}
|
||||
setMessages(prev => [...prev, assistantMessage])
|
||||
|
||||
// 开始创作任务
|
||||
startCreationTask(inputText.trim(), selectedModel)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const startCreationTask = async (prompt: string, model: Model) => {
|
||||
const taskId = Date.now().toString()
|
||||
const creationTask: CreationTask = {
|
||||
id: taskId,
|
||||
model,
|
||||
prompt,
|
||||
status: 'pending',
|
||||
progress: 0
|
||||
}
|
||||
|
||||
const taskMessage: ChatMessage = {
|
||||
id: taskId + '_task',
|
||||
type: 'assistant',
|
||||
content: '正在创作中...',
|
||||
timestamp: new Date(),
|
||||
creationTask
|
||||
}
|
||||
|
||||
setMessages(prev => [...prev, taskMessage])
|
||||
|
||||
// 模拟创作过程
|
||||
const steps = [
|
||||
{ progress: 10, status: '初始化AI模型...' },
|
||||
{ progress: 30, status: '分析模特特征...' },
|
||||
{ progress: 50, status: '生成视频内容...' },
|
||||
{ progress: 70, status: '渲染视频帧...' },
|
||||
{ progress: 90, status: '后处理优化...' },
|
||||
{ progress: 100, status: '创作完成!' }
|
||||
]
|
||||
|
||||
for (const step of steps) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||
|
||||
const updatedTask = {
|
||||
...creationTask,
|
||||
status: step.progress === 100 ? 'completed' as const : 'processing' as const,
|
||||
progress: step.progress,
|
||||
result: step.progress === 100 ? {
|
||||
videoPath: `${project.local_directory}/ai_generated_${taskId}.mp4`,
|
||||
thumbnailPath: `${project.local_directory}/ai_generated_${taskId}_thumb.jpg`,
|
||||
duration: 15
|
||||
} : undefined
|
||||
}
|
||||
|
||||
setMessages(prev => prev.map(msg =>
|
||||
msg.id === taskId + '_task'
|
||||
? { ...msg, creationTask: updatedTask }
|
||||
: msg
|
||||
))
|
||||
}
|
||||
|
||||
// 添加完成消息
|
||||
setTimeout(() => {
|
||||
const completionMessage: ChatMessage = {
|
||||
id: (Date.now() + 2).toString(),
|
||||
type: 'assistant',
|
||||
content: '✨ 视频创作完成!您可以预览或下载生成的素材。需要调整什么吗?',
|
||||
timestamp: new Date()
|
||||
}
|
||||
setMessages(prev => [...prev, completionMessage])
|
||||
setIsCreating(false)
|
||||
onMaterialCreated()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
handleSendMessage()
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusColor = (status: CreationTask['status']) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'text-yellow-600'
|
||||
case 'processing':
|
||||
return 'text-blue-600'
|
||||
case 'completed':
|
||||
return 'text-green-600'
|
||||
case 'failed':
|
||||
return 'text-red-600'
|
||||
default:
|
||||
return 'text-gray-600'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
{/* 头部 */}
|
||||
<div className="p-4 border-b border-gray-200 bg-gradient-to-r from-purple-50 to-blue-50">
|
||||
<div className="flex items-center mb-3">
|
||||
<Sparkles className="text-purple-600 mr-2" size={20} />
|
||||
<h3 className="font-semibold text-gray-900">AI创作助手</h3>
|
||||
</div>
|
||||
|
||||
{/* 模特选择 */}
|
||||
<div className="mb-2">
|
||||
<label className="text-xs text-gray-600 mb-1 block">选择模特:</label>
|
||||
<select
|
||||
value={selectedModel?.id || ''}
|
||||
onChange={(e) => {
|
||||
const model = models.find(m => m.id === e.target.value)
|
||||
setSelectedModel(model || null)
|
||||
}}
|
||||
className="w-full text-sm border border-gray-300 rounded-lg px-2 py-1 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
{models.length === 0 ? (
|
||||
<option value="">暂无可用模特</option>
|
||||
) : (
|
||||
models.map((model) => (
|
||||
<option key={model.id} value={model.id}>
|
||||
{model.model_number}
|
||||
</option>
|
||||
))
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-600">
|
||||
为项目"{project.product_name}"创作专属视频素材
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 消息列表 */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
{messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`flex ${message.type === 'user' ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div className={`max-w-[80%] ${message.type === 'user' ? 'order-2' : 'order-1'}`}>
|
||||
{/* 消息头部 */}
|
||||
<div className={`flex items-center mb-1 ${message.type === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||
<div className={`flex items-center ${message.type === 'user' ? 'flex-row-reverse' : 'flex-row'}`}>
|
||||
<div className={`w-6 h-6 rounded-full flex items-center justify-center ${
|
||||
message.type === 'user'
|
||||
? 'bg-blue-600 text-white ml-2'
|
||||
: message.type === 'system'
|
||||
? 'bg-purple-600 text-white mr-2'
|
||||
: 'bg-gray-600 text-white mr-2'
|
||||
}`}>
|
||||
{message.type === 'user' ? (
|
||||
<User size={12} />
|
||||
) : message.type === 'system' ? (
|
||||
<Sparkles size={12} />
|
||||
) : (
|
||||
<Bot size={12} />
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">
|
||||
{message.timestamp.toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 消息内容 */}
|
||||
<div className={`rounded-lg px-3 py-2 ${
|
||||
message.type === 'user'
|
||||
? 'bg-blue-600 text-white'
|
||||
: message.type === 'system'
|
||||
? 'bg-purple-100 text-purple-800 border border-purple-200'
|
||||
: 'bg-gray-100 text-gray-800'
|
||||
}`}>
|
||||
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
|
||||
|
||||
{/* 创作任务卡片 */}
|
||||
{message.creationTask && (
|
||||
<div className="mt-3 p-3 bg-white rounded-lg border border-gray-200">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center">
|
||||
<Wand2 size={14} className="text-purple-600 mr-1" />
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
{message.creationTask.model.model_number}
|
||||
</span>
|
||||
</div>
|
||||
<span className={`text-xs ${getStatusColor(message.creationTask.status)}`}>
|
||||
{message.creationTask.status === 'pending' && '等待中'}
|
||||
{message.creationTask.status === 'processing' && '创作中'}
|
||||
{message.creationTask.status === 'completed' && '已完成'}
|
||||
{message.creationTask.status === 'failed' && '失败'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-600 mb-2">
|
||||
{message.creationTask.prompt}
|
||||
</p>
|
||||
|
||||
{/* 进度条 */}
|
||||
{message.creationTask.status === 'processing' && (
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center justify-between text-xs text-gray-600 mb-1">
|
||||
<span>进度</span>
|
||||
<span>{message.creationTask.progress}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-1">
|
||||
<div
|
||||
className="bg-purple-600 h-1 rounded-full transition-all duration-300"
|
||||
style={{ width: `${message.creationTask.progress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 结果操作 */}
|
||||
{message.creationTask.status === 'completed' && message.creationTask.result && (
|
||||
<div className="flex items-center space-x-2 mt-2">
|
||||
<button className="flex items-center px-2 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition-colors">
|
||||
<Play size={12} className="mr-1" />
|
||||
预览
|
||||
</button>
|
||||
<button className="flex items-center px-2 py-1 bg-green-600 text-white text-xs rounded hover:bg-green-700 transition-colors">
|
||||
<Download size={12} className="mr-1" />
|
||||
下载
|
||||
</button>
|
||||
<span className="text-xs text-gray-500">
|
||||
{message.creationTask.result.duration}s
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{isCreating && (
|
||||
<div className="flex justify-start">
|
||||
<div className="flex items-center space-x-2 text-gray-500">
|
||||
<Loader className="animate-spin" size={16} />
|
||||
<span className="text-sm">AI正在思考...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* 输入区域 */}
|
||||
<div className="p-4 border-t border-gray-200 bg-gray-50">
|
||||
<div className="flex space-x-2">
|
||||
<textarea
|
||||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
onKeyDown={handleKeyPress}
|
||||
placeholder={selectedModel ? `告诉我您想要什么样的${project.product_name}视频...` : '请先选择一个模特'}
|
||||
disabled={!selectedModel || isCreating}
|
||||
rows={2}
|
||||
className="flex-1 resize-none border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-purple-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed text-sm"
|
||||
/>
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
disabled={!inputText.trim() || !selectedModel || isCreating}
|
||||
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center"
|
||||
>
|
||||
<Send size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
按 Enter 发送,Shift + Enter 换行
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AICreationChat
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
import React, { useState } from 'react'
|
||||
import { Video, Play, Search, Filter, Tag, Clock, HardDrive, Eye, User } from 'lucide-react'
|
||||
import { Project } from '../services/projectService'
|
||||
import { VideoSegment, MediaService } from '../services/mediaService'
|
||||
import { Model } from '../services/modelService'
|
||||
import VideoPlayer from './VideoPlayer'
|
||||
|
||||
interface ProjectMaterialsCenterProps {
|
||||
project: Project
|
||||
materials: VideoSegment[]
|
||||
models: Model[]
|
||||
onMaterialsChange: (materials: VideoSegment[]) => void
|
||||
}
|
||||
|
||||
const ProjectMaterialsCenter: React.FC<ProjectMaterialsCenterProps> = ({
|
||||
project,
|
||||
materials,
|
||||
models,
|
||||
onMaterialsChange
|
||||
}) => {
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selectedModelIds, setSelectedModelIds] = useState<string[]>([])
|
||||
const [filterType, setFilterType] = useState<'all' | 'original' | 'segmented'>('all')
|
||||
const [selectedMaterial, setSelectedMaterial] = useState<VideoSegment | null>(null)
|
||||
const [showVideoPlayer, setShowVideoPlayer] = useState(false)
|
||||
|
||||
// 过滤素材
|
||||
const filteredMaterials = materials.filter(material => {
|
||||
// 搜索过滤
|
||||
const matchesSearch = material.filename.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(material.tags || []).some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||
|
||||
// 模特过滤(如果选择了模特)
|
||||
const matchesModel = selectedModelIds.length === 0 ||
|
||||
selectedModelIds.some(modelId => {
|
||||
const model = models.find(m => m.id === modelId)
|
||||
return model && (material.tags || []).includes(model.model_number)
|
||||
})
|
||||
|
||||
// 类型过滤
|
||||
let matchesType = true
|
||||
if (filterType === 'original') {
|
||||
matchesType = material.segment_index === 0
|
||||
} else if (filterType === 'segmented') {
|
||||
matchesType = material.segment_index > 0
|
||||
}
|
||||
|
||||
return matchesSearch && matchesModel && matchesType
|
||||
})
|
||||
|
||||
const handleModelToggle = (modelId: string) => {
|
||||
setSelectedModelIds(prev =>
|
||||
prev.includes(modelId)
|
||||
? prev.filter(id => id !== modelId)
|
||||
: [...prev, modelId]
|
||||
)
|
||||
}
|
||||
|
||||
const handleUseMaterial = async (material: VideoSegment) => {
|
||||
try {
|
||||
await MediaService.incrementSegmentUsage(material.id)
|
||||
const updatedMaterials = materials.map(m =>
|
||||
m.id === material.id ? { ...m, use_count: m.use_count + 1 } : m
|
||||
)
|
||||
onMaterialsChange(updatedMaterials)
|
||||
} catch (error) {
|
||||
console.error('Failed to use material:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePlayMaterial = (material: VideoSegment) => {
|
||||
setSelectedMaterial(material)
|
||||
setShowVideoPlayer(true)
|
||||
}
|
||||
|
||||
const formatDuration = (seconds: number) => {
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const remainingSeconds = Math.floor(seconds % 60)
|
||||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
if (bytes === 0) return '0 B'
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024))
|
||||
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col bg-white rounded-lg shadow-sm border border-gray-200">
|
||||
{/* 头部 */}
|
||||
<div className="p-6 border-b border-gray-200">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-bold text-gray-900">项目素材</h2>
|
||||
<div className="text-sm text-gray-600">
|
||||
{filteredMaterials.length} / {materials.length} 个素材
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 搜索栏 */}
|
||||
<div className="relative mb-4">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索素材名称或标签..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 模特筛选标签 */}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center mb-2">
|
||||
<User size={16} className="text-gray-400 mr-2" />
|
||||
<span className="text-sm font-medium text-gray-700">按模特筛选:</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{models.map((model) => (
|
||||
<button
|
||||
key={model.id}
|
||||
onClick={() => handleModelToggle(model.id)}
|
||||
className={`px-3 py-1 text-sm rounded-full border transition-colors ${
|
||||
selectedModelIds.includes(model.id)
|
||||
? 'bg-blue-600 text-white border-blue-600'
|
||||
: 'bg-white text-gray-700 border-gray-300 hover:border-blue-400'
|
||||
}`}
|
||||
>
|
||||
{model.model_number}
|
||||
</button>
|
||||
))}
|
||||
{selectedModelIds.length > 0 && (
|
||||
<button
|
||||
onClick={() => setSelectedModelIds([])}
|
||||
className="px-3 py-1 text-sm bg-red-100 text-red-700 rounded-full hover:bg-red-200 transition-colors"
|
||||
>
|
||||
清除筛选
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 类型过滤 */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center">
|
||||
<Filter size={16} className="text-gray-400 mr-2" />
|
||||
<span className="text-sm font-medium text-gray-700">类型:</span>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
{[
|
||||
{ value: 'all', label: '全部' },
|
||||
{ value: 'original', label: '原始素材' },
|
||||
{ value: 'segmented', label: '已分镜头' }
|
||||
].map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={() => setFilterType(option.value as any)}
|
||||
className={`px-3 py-1 text-sm rounded-lg transition-colors ${
|
||||
filterType === option.value
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 素材网格 */}
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
{filteredMaterials.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<Video className="mx-auto text-gray-400 mb-4" size={64} />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
{searchTerm || selectedModelIds.length > 0 || filterType !== 'all'
|
||||
? '没有找到匹配的素材'
|
||||
: '暂无项目素材'
|
||||
}
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
{searchTerm || selectedModelIds.length > 0 || filterType !== 'all'
|
||||
? '尝试调整搜索条件或筛选器'
|
||||
: `上传包含"${project.product_name}"标签的视频素材`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
{filteredMaterials.map((material) => (
|
||||
<div key={material.id} className="bg-gray-50 rounded-lg overflow-hidden hover:shadow-md transition-shadow border border-gray-200">
|
||||
{/* 视频缩略图 */}
|
||||
<div className="relative h-40 bg-gray-100">
|
||||
{material.thumbnail_path ? (
|
||||
<img
|
||||
src={material.thumbnail_path}
|
||||
alt={material.filename}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<Video size={32} className="text-gray-400" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 播放按钮 */}
|
||||
<div className="absolute inset-0 bg-black bg-opacity-0 hover:bg-opacity-30 transition-all flex items-center justify-center">
|
||||
<button
|
||||
onClick={() => handlePlayMaterial(material)}
|
||||
className="p-3 bg-white/80 rounded-full hover:bg-white transition-colors opacity-0 hover:opacity-100"
|
||||
>
|
||||
<Play size={24} className="text-gray-800" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 类型标识 */}
|
||||
<div className="absolute top-2 left-2">
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${
|
||||
material.segment_index === 0
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-purple-100 text-purple-800'
|
||||
}`}>
|
||||
{material.segment_index === 0 ? '原始' : `片段${material.segment_index}`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 使用次数 */}
|
||||
<div className="absolute top-2 right-2">
|
||||
<span className="px-2 py-1 text-xs bg-black/50 text-white rounded-full">
|
||||
使用 {material.use_count} 次
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 素材信息 */}
|
||||
<div className="p-4">
|
||||
<h4 className="font-medium text-gray-900 mb-2 truncate" title={material.filename}>
|
||||
{material.filename}
|
||||
</h4>
|
||||
|
||||
{/* 基本信息 */}
|
||||
<div className="flex items-center text-sm text-gray-600 mb-3">
|
||||
<Clock size={14} className="mr-1" />
|
||||
<span>{formatDuration(material.duration)}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<HardDrive size={14} className="mr-1" />
|
||||
<span>{formatFileSize(material.file_size)}</span>
|
||||
</div>
|
||||
|
||||
{/* 标签 */}
|
||||
<div className="flex flex-wrap gap-1 mb-3">
|
||||
{(material.tags || []).slice(0, 3).map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={`inline-flex items-center px-2 py-1 text-xs rounded-full ${
|
||||
tag === project.product_name
|
||||
? 'bg-blue-100 text-blue-800'
|
||||
: 'bg-gray-100 text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<Tag size={10} className="mr-1" />
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{(material.tags || []).length > 3 && (
|
||||
<span className="text-xs text-gray-500">+{(material.tags || []).length - 3}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
onClick={() => handlePlayMaterial(material)}
|
||||
className="flex items-center px-3 py-1 text-sm text-blue-600 hover:text-blue-700 transition-colors"
|
||||
>
|
||||
<Eye size={14} className="mr-1" />
|
||||
预览
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleUseMaterial(material)}
|
||||
className="px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
使用素材
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 视频播放器 */}
|
||||
{selectedMaterial && (
|
||||
<VideoPlayer
|
||||
videoPath={selectedMaterial.file_path}
|
||||
isOpen={showVideoPlayer}
|
||||
onClose={() => {
|
||||
setShowVideoPlayer(false)
|
||||
setSelectedMaterial(null)
|
||||
}}
|
||||
title={`${selectedMaterial.filename} - ${
|
||||
selectedMaterial.segment_index === 0 ? '原始素材' : `片段 ${selectedMaterial.segment_index}`
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectMaterialsCenter
|
||||
|
|
@ -1,14 +1,11 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { ArrowLeft, User, Layout, Video, Sparkles, FolderOpen, Plus } from 'lucide-react'
|
||||
import { ArrowLeft, User, Video } from 'lucide-react'
|
||||
import { Project, ProjectService } from '../services/projectService'
|
||||
import { VideoSegment, MediaService } from '../services/mediaService'
|
||||
import { Model, ModelService } from '../services/modelService'
|
||||
import ProjectModels from '../components/ProjectModels'
|
||||
import ProjectTemplates from '../components/ProjectTemplates'
|
||||
import ProjectMaterials from '../components/ProjectMaterials'
|
||||
import ProjectFiles from '../components/ProjectFiles'
|
||||
import AICreationPanel from '../components/AICreationPanel'
|
||||
import ProjectMaterialsCenter from '../components/ProjectMaterialsCenter'
|
||||
import AICreationChat from '../components/AICreationChat'
|
||||
|
||||
const ProjectDetailPage: React.FC = () => {
|
||||
const { projectId } = useParams<{ projectId: string }>()
|
||||
|
|
@ -16,13 +13,10 @@ const ProjectDetailPage: React.FC = () => {
|
|||
|
||||
const [project, setProject] = useState<Project | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [activeTab, setActiveTab] = useState<'models' | 'templates' | 'materials' | 'files' | 'ai'>('models')
|
||||
|
||||
|
||||
// 数据状态
|
||||
const [projectModels, setProjectModels] = useState<Model[]>([])
|
||||
const [availableTemplates, setAvailableTemplates] = useState<any[]>([])
|
||||
const [projectMaterials, setProjectMaterials] = useState<VideoSegment[]>([])
|
||||
const [projectFiles, setProjectFiles] = useState<any[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (projectId) {
|
||||
|
|
@ -43,10 +37,8 @@ const ProjectDetailPage: React.FC = () => {
|
|||
|
||||
// 加载项目相关数据
|
||||
await Promise.all([
|
||||
loadProjectModels(projectResponse.data),
|
||||
loadAvailableTemplates(projectResponse.data),
|
||||
loadProjectMaterials(projectResponse.data),
|
||||
loadProjectFiles(projectResponse.data)
|
||||
loadProjectModels(),
|
||||
loadProjectMaterials(projectResponse.data)
|
||||
])
|
||||
} else {
|
||||
console.error('Failed to load project:', projectResponse.msg)
|
||||
|
|
@ -60,7 +52,7 @@ const ProjectDetailPage: React.FC = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const loadProjectModels = async (project: Project) => {
|
||||
const loadProjectModels = async () => {
|
||||
try {
|
||||
// 获取所有模特,后续可以添加项目关联逻辑
|
||||
const response = await ModelService.getAllModels()
|
||||
|
|
@ -72,16 +64,6 @@ const ProjectDetailPage: React.FC = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const loadAvailableTemplates = async (project: Project) => {
|
||||
try {
|
||||
// 获取所有模板,后续可以添加项目关联逻辑
|
||||
// 这里需要调用模板服务
|
||||
setAvailableTemplates([])
|
||||
} catch (error) {
|
||||
console.error('Failed to load available templates:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const loadProjectMaterials = async (project: Project) => {
|
||||
try {
|
||||
// 获取与项目商品名相关的素材
|
||||
|
|
@ -98,23 +80,7 @@ const ProjectDetailPage: React.FC = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const loadProjectFiles = async (project: Project) => {
|
||||
try {
|
||||
// 获取项目文件夹下的所有文件
|
||||
// 这里需要添加文件系统扫描功能
|
||||
setProjectFiles([])
|
||||
} catch (error) {
|
||||
console.error('Failed to load project files:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ id: 'models', label: '使用模特', icon: User, count: projectModels.length },
|
||||
{ id: 'templates', label: '可用模板', icon: Layout, count: availableTemplates.length },
|
||||
{ id: 'materials', label: '项目素材', icon: Video, count: projectMaterials.length },
|
||||
{ id: 'files', label: '项目文件', icon: FolderOpen, count: projectFiles.length },
|
||||
{ id: 'ai', label: 'AI创作', icon: Sparkles, count: 0 }
|
||||
] as const
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
|
@ -146,133 +112,59 @@ const ProjectDetailPage: React.FC = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
{/* 页面头部 */}
|
||||
<div className="flex items-center mb-6">
|
||||
<button
|
||||
onClick={() => navigate('/projects')}
|
||||
className="mr-4 p-2 text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<ArrowLeft size={24} />
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">{project.name}</h1>
|
||||
<p className="text-gray-600">商品:{project.product_name}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 项目信息卡片 */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
|
||||
<div className="flex items-start space-x-6">
|
||||
{/* 商品图片 */}
|
||||
<div className="w-24 h-24 bg-gray-100 rounded-lg overflow-hidden flex-shrink-0">
|
||||
{project.product_image ? (
|
||||
<img
|
||||
src={project.product_image}
|
||||
alt={project.product_name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-gray-400">
|
||||
<Video size={32} />
|
||||
</div>
|
||||
)}
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* 顶部导航栏 */}
|
||||
<div className="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={() => navigate('/projects')}
|
||||
className="mr-4 p-2 text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<ArrowLeft size={24} />
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-gray-900">{project.name}</h1>
|
||||
<p className="text-sm text-gray-600">商品:{project.product_name}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 项目信息 */}
|
||||
<div className="flex-1">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-gray-600">项目名称</label>
|
||||
<p className="font-medium">{project.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-gray-600">商品名称</label>
|
||||
<p className="font-medium">{project.product_name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-gray-600">本地目录</label>
|
||||
<p className="font-medium text-sm text-gray-700">{project.local_directory}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-gray-600">创建时间</label>
|
||||
<p className="font-medium">{new Date(project.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
{/* 项目信息概览 */}
|
||||
<div className="flex items-center space-x-6 text-sm text-gray-600">
|
||||
<div className="flex items-center">
|
||||
<Video size={16} className="mr-1" />
|
||||
<span>{projectMaterials.length} 个素材</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<User size={16} className="mr-1" />
|
||||
<span>{projectModels.length} 个模特</span>
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
创建于 {new Date(project.created_at).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 标签页导航 */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 mb-6">
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="flex space-x-8 px-6">
|
||||
{tabs.map((tab) => {
|
||||
const Icon = tab.icon
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`flex items-center space-x-2 py-4 border-b-2 font-medium text-sm transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<Icon size={16} />
|
||||
<span>{tab.label}</span>
|
||||
{tab.count > 0 && (
|
||||
<span className="bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-xs">
|
||||
{tab.count}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
{/* 主要内容区域 - 三栏布局 */}
|
||||
<div className="flex h-[calc(100vh-120px)]">
|
||||
{/* 中间主要区域 - 项目素材管理 */}
|
||||
<div className="flex-1 px-6 py-4 overflow-hidden">
|
||||
<ProjectMaterialsCenter
|
||||
project={project}
|
||||
materials={projectMaterials}
|
||||
models={projectModels}
|
||||
onMaterialsChange={setProjectMaterials}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 标签页内容 */}
|
||||
<div className="p-6">
|
||||
{activeTab === 'models' && (
|
||||
<ProjectModels
|
||||
project={project}
|
||||
models={projectModels}
|
||||
onModelsChange={setProjectModels}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'templates' && (
|
||||
<ProjectTemplates
|
||||
project={project}
|
||||
templates={availableTemplates}
|
||||
onTemplatesChange={setAvailableTemplates}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'materials' && (
|
||||
<ProjectMaterials
|
||||
project={project}
|
||||
materials={projectMaterials}
|
||||
onMaterialsChange={setProjectMaterials}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'files' && (
|
||||
<ProjectFiles
|
||||
project={project}
|
||||
files={projectFiles}
|
||||
onFilesChange={setProjectFiles}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'ai' && (
|
||||
<AICreationPanel
|
||||
project={project}
|
||||
models={projectModels}
|
||||
onMaterialCreated={() => loadProjectMaterials(project)}
|
||||
/>
|
||||
)}
|
||||
{/* 右侧 AI 创作面板 */}
|
||||
<div className="w-80 bg-white border-l border-gray-200 flex flex-col">
|
||||
<AICreationChat
|
||||
project={project}
|
||||
models={projectModels}
|
||||
onMaterialCreated={() => loadProjectMaterials(project)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue