⚠️ 先读这一条,能省你十分钟设置面板里的第一个选项「默认 (Gemini Flash) — 使用内置的免费试用通道」在线上部署与本地自建环境里都不可用。 源码写的是
process.env.API_KEY,而 Vite 会把process.env替换成空对象,于是传给 SDK 的 apiKey 是undefined, Google SDK 会当场抛错:An API Key must be set when running in a browser。要用它,请在「配置 AI」里切到
Google Gemini (自选 Key)或DeepSeek (自选 Key)并填入自己的 Key。 详见 AI 服务商与模型。
灵感剧本规划器是一个纯前端的 AI 写作辅助工作台,专门解决「脑子里有画面、有几句话、有几个角色,但串不成一个故事」这一步。
它不替你写正文,而是替你完成从碎片到骨架的跃迁:
- 你把手里的零散素材丢进去(世界观、角色人设、关键情节、想要的调性);
- 它让模型把同一批素材往三个截然不同的方向各推一遍,产出三个候选方案;
- 你挑一个最顺眼的,它就变成一份可逐字编辑的大纲(标题、一句话梗概、故事简介、核心冲突、幕结构);
- 进入结构编辑器后,你可以针对单个情节点让 AI 细化、拆分,或者让它顺着上下文继续往下写几个节点。
产出物是一份结构化的幕/节点清单,可以导出成 TXT 带走,也可以把改好的 TXT 导回来覆盖。
这个仓库的地址是 github.com/kuroshio4396/-,仓库名就是一个连字符。这是发布时的疏忽,不是刻意设计——
在 GitHub 上它并不会带来功能问题(URL 里 - 是安全字符),但确实不利于检索与分享。项目真正的名字是 灵感剧本规划器。
从代码里能看到它的来历:metadata.json 与 package.json 里的名字分别是
Copy of Copy of 灵感剧本规划器 版本3 与 copy-of-copy-of-灵感剧本规划器-版本3,
页面里也残留着 Google AI Studio 的工程链接——它是从 AI Studio 的
aistudio-repository-template 模板里导出的项目,作者复制、迭代了若干版之后直接推送到了这里。
- 四步线性工作流 —— 灵感输入 → 构思规划 → 剧本大纲 → 结构编辑器,顶部导航可随时回退到上一步。
- 一次给三个方向 —— 同一个素材,模型会刻意分化成三份切入点不同的方案,而不是三个措辞不同的一版。
- 「都不满意?重做」 —— 不满意时点一下,会把已看过的标题全部列为禁用,逼模型换完全不同的方向或反转, 而且这些禁用标题会跨多轮累积。
- 不挑输入形态 —— 可以手填,也可以直接上传
.docx/.txt,由 AI 自动抽取世界观、角色、剧情梗概与主题并回填表单。 - 五档角色定位 —— 主角 / 主要配角 / 反派BOSS / 次要配角 / 群演,角色卡片可增删,取舍会自动进入提示词。
- 大纲逐字可编辑 —— 标题、一句话梗概、故事简介、核心冲突都是输入框,不是只读文本。
- 单节点 AI 操作 —— 结构编辑器里每个节点都带「细化」(扩写细节、加画面感与冲突)与「拆分」(拆成 2–4 个连续节点)两个按钮。
- AI 续写 —— 带上全部已有节点当上下文,一次接着往下生成 2–3 个新节点。
- TXT 往返 —— 导出的大纲可以直接改完再导回来;导入器认得
第1幕 / 第三章 / Act 1 / Chapter 1 / Scene 1等多种标题写法。 - 两条自带 Key 通道 —— Google Gemini 与 DeepSeek(OpenAI 兼容),配置存在本地浏览器,服务端不需要任何东西。
灵感输入 ──▶ 构思规划 ──▶ 剧本大纲 ──▶ 剧本结构编辑器
① 灵感输入
世界观 / 角色人设 / 剧情片段 / 主题 —— 或直接上传 docx·txt 让 AI 自动抽取并回填
│
│ analyzeFileContent(仅「AI 导入」时调用)
▼
② 构思规划
generateScriptPlans —— 一次产出 3 个方向迥异的候选方案;不满意可「重做」换一批
│ (已看过的标题会累积进禁用名单)
▼
③ 剧本大纲
标题 / 一句话梗概 / 故事简介 / 核心冲突 全部可编辑;下方是幕结构预览
│
│ ← 这一屏与上一屏都不调用 AI,纯本地编辑
▼
④ 剧本结构编辑器
refineScriptAct(逐节点细化) · splitScriptAct(逐节点拆分) · extendScriptStructure(AI 续写)
+ 删除节点 / 添加空白节点 / 导出TXT / 导入TXT(全量覆盖)
四个视图由 AppView 一个状态机驱动:input → selection → outline → structure_editor。
只有「灵感输入 → 构思规划」这一跳必须调用 AI;之后的编辑全部是本地操作,不消耗任何额度。
四个字段,全部选填但有门槛:
- 世界观 / 故事背景 —— 多行文本。占位提示:「例如:赛博朋克风格的古代长安,或是被洪水淹没的未来地球...」
- 角色人设 —— 动态卡片列表。每张卡有「角色名称」「角色定位下拉框」「角色描述」三部分;
默认角色定位是
主角,可选项为主角 / 主要配角 / 反派BOSS / 次要配角 / 群演。 列表为空时显示虚线占位框引导你去点「添加角色」。 - 初步剧情片段 / 关键情节 —— 多行文本。占位提示:「例如:主角在雨夜捡到一把会说话的枪,或者结尾必须是悲剧...」
- 核心主题 / 风格偏好(选填) —— 单行文本。占位提示:「例如:关于复仇,黑色幽默,克苏鲁神话...」
门槛:点「生成剧本规划」时,如果四个字段全部为空,会弹 请输入至少一项灵感内容。 并中止。
只要有一个字段有内容就放行——所以只写一句主题也能开工。
「AI 导入 (Word/Txt)」按钮:选择 .docx 或 .txt 后,前端先抽文本再喂给模型做结构化抽取。
.docx走 mammoth.js(1.6.0,从 cdnjs 加载)取纯文本;.txt直接file.text()读取;- 抽完之后把结果合并进表单:
worldSetting/plotSnippets/theme只在该字段当前为空时写入 (用的是analysis.x || prev.x),而 角色是追加——所以重复导入同一份文件会得到重复角色,需要手动删。 - 文件内容会被截断到 100000 字符再送去分析(超出部分静默丢弃)。
- 导入过程中按钮变成「AI分析中...」并禁用,同时也会禁用「生成剧本规划」。
顶部有一句提示:「AI为您生成了以下几个发展方向。选择一个最吸引你的加入大纲。」
三个方案以卡片网格呈现(宽屏 3 列 / 中屏 2 列 / 手机 1 列),每张卡片展示:
- 标题 + 右上角的风格标签(tone,如「赛博朋克黑色电影」)
- 一句话梗概(logline,左侧带竖线的斜体引用样式)
- 主要冲突(mainConflict)
- 简介(synopsis,最多显示 4 行,超出用
line-clamp-4截断)
卡片底部是「加入剧本大纲」。
右上角两个动作:
- 返回修改灵感 —— 回到第一步,已填内容都还在。
- 都不满意?重做 —— 触发生成。它会把你这次看到的三个标题连同之前所有轮次已经拒过的标题
一起塞进提示词的
avoidTitles,并明确要求「尝试完全不同的方向或反转」。 效果是连续点几次「重做」,方向会越来越偏。重新从第一步生成(非重做)时,禁用列表会被清空。
选定方案后进入。上半部是渐变深蓝的头部,风格标签、标题与一句话梗概都是可直接改的输入框 (悬停时出现下划线提示可编辑);右上角有「删除/重置」——会二次确认,然后把大纲清空并退回第二步。
正文两部分:故事简介(可编辑,约 10 行高的文本域)与核心冲突(可编辑,约 6 行高)。 再下面是剧本结构规划的只读预览:显示节点总数,并列出前 3 个节点的标题,超出时显示「...还有 N 个节点」。 真正改结构要点「进入结构编辑器」。
这是整个应用里最重的一屏。每个节点是一张卡片:
- 左上角是序号徽章 + 可编辑的节点标题;
- 大号文本域是 节点描述,可自由编辑,右下角可拖拽调整高度;
- 工具条平时半透明,鼠标悬停整张卡片时才显现(触屏设备上常驻),包含三个按钮:
- 细化 —— 带着「前情回顾」(当前节点之前的全部节点)让 AI 大幅丰富这一节的细节, 要求增加画面感、动作描述、潜在对话冲突或心理活动。返回的富文本直接覆盖该节点描述。
- 拆分 —— 把当前节点拆成 2–4 个时间上连续的更细节点,拆分结果原地替换该节点。
- 删除 —— 二次确认后移除。
- AI 处理期间,工具条替换为「AI处理中...」的转圈徽章,同时全局只允许一个 AI 操作在跑
(用
processingIndex挡并发)。
底部是固定的操作条(position: fixed,所以页面留了 pb-24 的下边距):
- 添加空白节点 —— 追加一个标题为「新情节节点」的空节点。没有拖拽排序,顺序即数组顺序, 想调整位置只能删掉重建。
- AI 继续生成剧情 —— 用完整上下文(世界观、角色、全部已有节点)续写 2–3 个新节点并追加到末尾。 每点一次都会再往下长一截,适合「滚雪球」式推进。
左上角还有 导出TXT 与 导入TXT (覆盖)。
导出的 TXT 长这样(handleExportTxt,文件名 ${标题}-structure.txt):
=== 剧本大纲: 雨夜拾枪 ===
风格: 冷硬黑色电影
核心冲突: 主角必须在复仇与自保之间选一个
=== 剧情结构详情 ===
第 1 幕: 雨夜
-------------------
主角在天桥下捡到一把会说话的枪,枪声称自己记得所有被杀者的名字。
===================
第 2 幕: 第一次交易
-------------------
...
导入(handleImportTxt)会在覆盖前弹窗警告「此操作将【彻底清空】当前编辑器内的所有节点……建议先导出备份」。
确认后按正则在全文里找节点标题,规则如下:
- 认得的标题写法:
第1幕第 2 节第三章第 4 场(中文数字与阿拉伯数字都行)、Act 1、Chapter 2、Scene 3,且允许前面带###之类的 Markdown 井号; - 标题后可以用
:或:接副标题; - 第一个标题之前若还有正文,会被装成一个标题为「前言/背景信息」的节点,不会丢;
- 每个节点的描述 = 从该标题后到下一个标题之间的全部内容,
---与===分隔线会被自动清掉; - 一个标题都没匹配上时,整份文件会被塞进单个节点,标题写作「导入的内容」;
- 导入完成后弹
已覆盖现有内容,成功导入 N 个剧情节点!。
注意:导入只替换节点列表。导出文件头部的「风格」「核心冲突」两行,以及根本没被导出的 「一句话梗概」「故事简介」,导入时都不会被读回——它们仍以页面上现有的值为准。 也就是说 TXT 往返对节点是无损的,对大纲头部字段不是。
点右上角「配置 AI」打开设置面板,三选一。配置只存在浏览器 localStorage,读取键名 ai_script_settings。
| 通道 | 面板上的说明文案 | 代码实际用的模型 | 需要自己的 Key |
|---|---|---|---|
| 默认 | 默认 (Gemini Flash) —— 使用内置的免费试用通道 | gemini-3-flash-preview |
否(但取不到 Key,见下) |
| Google Gemini | Google Gemini (自选 Key) —— 调用 gemini-2.5-pro 模型 | gemini-2.0-flash |
是 |
| DeepSeek | DeepSeek (自选 Key) —— 调用 deepseek-chat 模型 | deepseek-chat |
是 |
services/geminiService.ts 里默认通道是这么写的:
if (settings.provider === 'default') {
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY });
const response = await ai.models.generateContent({ model: "gemini-3-flash-preview", ... });
}vite.config.ts 里只有一个 react() 插件,没有 define、也没有 loadEnv。
Vite 会把 process.env 替换成一个空对象——在线上产物里可以直接看到
var hR={}; ... new Mh({apiKey:hR.API_KEY}) ...,也就是 apiKey: undefined。
而 Google SDK 的浏览器实现里有一句硬校验:
if (t.apiKey == null) throw new Error("An API Key must be set when running in a browser");结论:走「默认」通道时,请求还没发出去就会抛异常,被 App.tsx 兜住并弹出
生成失败,请检查网络设置、API Key 是否正确,或稍后再试。。
面板上「使用内置的免费试用通道」这句文案与实现不符——在 AI Studio 自己的托管环境里
这个变量是平台注入的,一旦导出到 Vercel / 本地就失效了。
所以要真正跑起来,请选自带 Key 的通道。 想修的话,最小改法是在 vite.config.ts 里补:
export default defineConfig({
plugins: [react()],
define: { 'process.env.API_KEY': JSON.stringify(process.env.API_KEY ?? '') },
});(注意:这样会把 Key 打进前端产物并公开。纯前端应用要藏 Key,就得加一个后端转发, 参见本账号的 Gemini-3.5-trigger 是怎么用 Vercel Serverless 做服务端转发的。)
这是本项目里最值得看的一段设计:结构化输出靠的是两套完全不同的机制。
- Gemini 通道用 SDK 原生的
responseSchema(services/geminiService.ts里定义了 4 个 Schema:RESPONSE_SCHEMA三个方案、ACTS_SCHEMA节点数组、REFINE_SCHEMA单节点细化、ANALYSIS_SCHEMA文档解析), 同时把responseMimeType设成application/json。由模型侧保证 JSON 合法,前端不用做容错解析。 - DeepSeek 通道没有 schema 可用,只能靠两手:
- 在系统提示里追加「请务必以严格的 JSON 格式返回结果,不要包含 Markdown 代码块标记(如
```json)」; - 请求体带上
response_format: { type: "json_object" }; - 前端再兜一层
text.replace(/```json/g,'').replace(/```/g,'').trim()之后才JSON.parse。
- 在系统提示里追加「请务必以严格的 JSON 格式返回结果,不要包含 Markdown 代码块标记(如
这里有个已知冲突:提示词向 DeepSeek 要的是 JSON 数组(
[{...}]), 但response_format的json_object模式只保证返回对象。两者同时下发可能互相干扰, 属于该通道稳定性不如 Gemini 的原因之一。
- React 19.2 —— 全函数组件 + Hooks,没有引入路由(视图切换靠
AppView状态机),也没有全局状态库。 - TypeScript 5.8 ——
tsconfig.json开了isolatedModules、moduleDetection: force、skipLibCheck, 并配了@/*路径别名;noEmit: true,编译交给 Vite。 - Vite 7 —— 构建与开发服务器,仅一个
@vitejs/plugin-react插件。 - Tailwind CSS(Play CDN) —— 通过
https://cdn.tailwindcss.com运行时常量, 不是 PostCSS 构建;自定义配置(字体栈、fadeIn关键帧)写在index.html的内联tailwind.config里。 - @google/genai 1.35 —— 官方新一代 SDK(
GoogleGenAI+Type.Type.*Schema 类型)。 - mammoth.js 1.6.0 —— 仅在浏览器端用于
.docx取纯文本,从 cdnjs 加载,用declare var mammoth: any声明。 - Noto Sans SC —— 从 Google Fonts 加载,字体栈再回落 PingFang SC / 微软雅黑。
- 无后端、无数据库、无账号体系 —— 全部计算在浏览器里完成。
index.html里还有一份 importmap,把react/react-dom/@google/genai/vite指到esm.sh。这是 AI Studio 模板留下的浏览器原生 ESM 兜底方案,与 Vite 打包并存,正常构建时不影响产物。
- Node.js 18 以上(Vite 7 要求)
- 一个 Gemini 或 DeepSeek 的 API Key
# 1. 取代码
git clone https://github.com/kuroshio4396/-.git
cd ./- # 仓库名就是一个连字符,目录名就是它
# 2. 装依赖
npm install
# 3. 起开发服务器
npm run dev打开终端提示的地址(Vite 默认 http://localhost:5173),点右上角「配置 AI」,
选 Google Gemini (自选 Key) 或 DeepSeek (自选 Key) 并粘贴 Key,保存即可。
npm run dev # 开发服务器(vite)
npm run build # 生产构建(vite build),产物在 dist/
npm run preview # 本地预览构建产物(vite preview)项目没有配置 lint / test / typecheck 脚本。
模板自带的 README 让你在 .env.local 里配 GEMINI_API_KEY——对本项目无效。
.gitignore 确实忽略了 *.local,但代码里读的是 process.env.API_KEY(名字不同),
而且 vite.config.ts 没有把任何环境变量注入前端。照那个说明做不会有任何效果,
请直接用设置面板填 Key。
git clone 下来的目录名就是 -,直接 cd - 在多数 shell 里会被解释成「切回上一个目录」。
用 cd ./- 或 cd -- -。
项目是纯静态站点,构建产物 dist/ 可以丢到任何静态托管上。
以 Vercel 为例:
- 在 Vercel 里 Import 这个仓库;
- Framework Preset 选 Vite(Vercel 通常能自动识别),Build Command
npm run build,Output Directorydist; - 不需要配置任何环境变量(配了也不生效,见上文);
- 部署完成后,打开站点 → 配置 AI → 填自己的 Key。
仓库里没有
vercel.json,也没有.env.example。线上那份 demo 就是按上述方式部署的。
其他静态托管(Netlify / Cloudflare Pages / GitHub Pages)同理,把输出目录指向 dist 即可。
注意 Tailwind 与 mammoth 是从 CDN 加载的,站点会依赖外网——离线环境下样式与 docx 解析都会失效。
services/geminiService.ts 导出五个业务函数,它们全部经由内部私有函数 callAI() 转发:
| 函数 | 干什么 | 回给谁 |
|---|---|---|
generateScriptPlans |
生成 3 个候选方案(可带 avoidTitles 禁用名单) |
构思规划页 |
extendScriptStructure |
顺着已有节点续写 2–3 个新节点 | 结构编辑器「AI 继续生成剧情」 |
refineScriptAct |
细化单个节点,返回一段新的 description |
节点的「细化」按钮 |
splitScriptAct |
把单个节点拆成 2–4 个连续节点 | 节点的「拆分」按钮 |
analyzeFileContent |
从上传的 docx/txt 里抽取四个字段 | 灵感输入页的「AI 导入」 |
callAI(prompt, settings, schema?, systemInstruction?) 按 settings.provider 分流:
- 系统提示默认值:
你是一个专业的创意写作AI助手。 - 所有提示词都明确要求「语言必须是【简体中文】」,所以产出不会中英混杂。
- 角色列表通过
formatCharacters()统一格式化成1. [主角] 张三: 描述的多行块; 角色为空时会替换成「未提供,请自行发挥」,把决定权交回模型。
- 三个方向必须分化:要求「每个方案必须有独特的切入点(例如:方案1侧重悬疑,方案2侧重情感,方案3侧重动作)」。
- 「重做」时才注入禁用名单,并在提示词里说明这是用户点了重做。首次生成不带这个段落。
- 细化时提供「前情回顾」:把目标节点之前的全部节点按顺序列出当上下文; 若目标就是第一个节点,则替换为「这是故事的开篇。」。
- 拆分要求时间连续:明确「拆分后的节点必须保持时间上的连续性,能够平滑地替换原节点」。
- 文档解析要求推断角色定位:模型要自己判断每个角色属于主角 / 配角 / 反派BOSS / 群演。
- 方案与 AI 生成的节点用
${Date.now()}-${index}一类的时间戳 ID; - 编辑器里手动「添加空白节点」与 TXT 导入的节点用
Math.random().toString(36).substr(2, 9)(substr已废弃,建议换slice)。
两套 ID 混在同一个数组里,功能上没问题(只用作 React key),但排查时会略绕。
types.ts 只有 40 来行,是整个应用的数据契约:
| 类型 | 字段 |
|---|---|
CharacterRole |
联合类型:主角 / 主要配角 / 反派BOSS / 次要配角 / 群演 |
Character |
id · name · description · role |
UserInputs |
worldSetting · characters: Character[] · plotSnippets · theme |
ScriptAct |
id? · title · description |
ScriptPlan |
id · title · logline · synopsis · tone · acts: ScriptAct[] · mainConflict |
AppView |
input / selection / outline / structure_editor |
AIProvider |
default / gemini / deepseek |
AISettings |
provider · apiKey · baseUrl? |
AISettings.baseUrl是为「OpenAI 兼容端点」预留的字段,但设置面板并不提供编辑入口, 代码里也从未读取它——目前是死字段。
.
├── index.html # 入口 HTML:Tailwind Play CDN、mammoth CDN、importmap、字体、内联 tailwind.config
├── index.tsx # React 挂载点(StrictMode)
├── App.tsx # 顶层:AppView 状态机 + 全局状态 + 设置持久化 + 页头/页脚
├── types.ts # 全部接口与联合类型
├── metadata.json # AI Studio 元数据(name / description)
├── package.json # 依赖与三个 npm 脚本
├── tsconfig.json # TS 配置(noEmit、@/* 别名)
├── vite.config.ts # Vite 配置(只有 react 插件)
├── .gitignore
├── components/
│ ├── InputForm.tsx # 第一步:四字段表单 + 角色卡列表 + AI 导入
│ ├── PlanSelection.tsx # 第二步:三方案卡片网格 + 重做
│ ├── OutlineView.tsx # 第三步:可编辑大纲 + 幕结构预览
│ ├── StructureEditor.tsx # 第四步:节点编辑 + 细化/拆分/续写 + TXT 导入导出
│ └── SettingsModal.tsx # AI 通道与 Key 配置弹窗
└── services/
└── geminiService.ts # 4 个 Schema + callAI 分流 + 5 个业务函数
- Key 存在浏览器里,明文。 使用
localStorage.setItem('ai_script_settings', JSON.stringify({provider, apiKey})), 没有加密也没有过期。设置面板的提示是准确的:「您的 Key 仅存储在本地浏览器中,不会上传至任何第三方服务器」—— 但要注意它会被直接发给 Google 或 DeepSeek(这本来就是它存在的意义)。 共用电脑上使用时,记得用完清掉。 - 你的灵感与大纲不落任何盘。 输入内容、角色、生成的方案、改过的大纲全都只活在 React 内存状态里, 刷新页面即全部丢失,没有草稿自动保存、没有历史版本。写到大纲阶段后请及时「导出TXT」。
- 没有后端。 自带 Key 时浏览器直连
generativelanguage.googleapis.com或api.deepseek.com, 中间没有任何属于本项目的服务器。 - 没有任何埋点。 页面里只有 Tailwind CDN、mammoth CDN、Google Fonts 和自身的 JS 产物, 没有统计脚本、没有 cookie、没有第三方 SDK。
- 上传的文件不出浏览器之外。 docx 由 mammoth 在本地解成纯文本, 只有抽取后的文本会被送去模型;原文件不会被上传。
- 先跑通通道再投入内容。 第一次用,先随便填一句主题、点生成,确认返回正常再认真填。 否则填了半天才发现通道没配好,白写。
- 「重做」是好东西,别只点一次。 禁用列表会累积,第三、四轮往往才出现真正意外的方向。
- 把「重做」当灵感发散器用:即使对前三版都不满意,它们的标题与简介本身也能帮你确认自己不想要什么。
- 结构编辑器里「拆分」比「细化」更值钱。 三个阶段性的节点太粗时,拆一次就能得到可直接开写的颗粒度; 「细化」适合已经想清楚、只缺描写的节点。
- 先导出再大改。 TXT 导入是全量覆盖且没有撤销,改之前先导出留个底。
- 要换电脑或换浏览器,记得手动搬 Key——
localStorage不会跟着走。 .txt请用 UTF-8 编码保存。 读取走的是File.text(),GBK 编码的老文本会读成乱码。- 移动端体验有限:节点工具条在触屏上虽常驻,但底部固定操作条会占掉一部分屏幕高度, 建议在桌面浏览器里做重度编辑。
点了「生成剧本规划」,弹出「生成失败,请检查网络设置、API Key 是否正确,或稍后再试。」
最可能的原因是你还在「默认」通道上。这个通道在自部署环境下拿不到 Key,必然失败。
点右上角「配置 AI」,切到 Google Gemini (自选 Key) 或 DeepSeek (自选 Key),填入自己的 Key 再试。 如果已经切了通道还是失败,依次检查:Key 是否复制完整(前后别带空格)、Key 是否还有余额/配额、 所选通道与 Key 的归属是否匹配(别把 DeepSeek 的 Key 填进 Gemini 通道)。
为什么设置里写「调用 gemini-2.5-pro 模型」,抓包却看到别的模型名?
因为面板文案与实现不一致。源码里 Gemini 通道写死的是 gemini-2.0-flash,
页脚显示的「Powered by Gemini 2.5 Pro」也同样只是文案。
geminiService.ts 里留着一串注释,说明作者原本想用 2.5-pro,但担心 SDK 解析不到该模型名而保守地留在了 flash。
实际请求以代码为准:默认通道是 gemini-3-flash-preview,自选 Key 的 Gemini 通道是 gemini-2.0-flash。
刷新页面后我填的内容和大纲都没了
这是当前设计,不是 bug——只有 AI 配置会被持久化,输入与生成结果都只在内存里。 在结构编辑器里点「导出TXT」可以把节点存下来。
上传了 docx,结果角色出现了两份
导入时角色是追加而非去重合并。同一个文件导入两次就会有重复项,手动删掉多余的即可。 如果只想让 AI 补世界观和剧情、不想重复角色,导入前先把角色卡清空。
「AI 导入」报「导入失败,请重试或检查文件格式(支持 .txt 和 .docx)」
先确认扩展名确实是 .docx(老的 .doc 是另一种二进制格式,mammoth 处理不了),
.txt 请用 UTF-8 保存。另外这个提示也会在模型调用失败时出现——
因为导入流程包含一次 AI 分析,通道没配好同样会走到这里。
导入 TXT 之后,大纲的标题/简介/核心冲突没变
符合预期。导入只替换节点列表,不读取导出文件里的「风格」「核心冲突」等行, 更不会恢复从未被导出的「一句话梗概」与「故事简介」。这些字段以页面上的现值先为准。
提示里说「最多返回 2-3 个节点」,为什么有时一次给了 4 个?
提示词只是软约束,模型不保证严格遵守数量。返回几个就追加几个,多余或不足都可以手工增删。 同理「拆成 2-4 个」也不保证。
为什么点开「配置 AI」时,Key 输入框有时候不显示?
选「默认」通道时设计上就隐藏 Key 输入框(该通道本意是不需要 Key)。 一旦切到 Gemini 或 DeepSeek,输入框会立刻出现。
节点顺序不对,怎么拖动?
不支持拖拽排序。 顺序就是数组顺序,只能删除后重新添加。 如果想精确定序,可以「导出TXT」→ 在文本编辑器里调整段落顺序 → 「导入TXT (覆盖)」。
这个项目会替我把正文也写出来吗?
不会。它的产出止于幕/节点级的情节描述,定位是「大纲与结构工具」。 节点描述的颗粒度通常在几句话到一小段之间,适合作为正式写作的施工图。
以下是读代码时确认存在的问题,如实列出,均未在本仓库中擅自修改:
功能性
- 「默认(内置)」通道完全不可用 ——
process.env.API_KEY在 Vite 构建下取不到值,SDK 直接抛错。 要么修vite.config.ts的define(但会公开 Key),要么加服务端转发,要么把这个选项从面板上去掉。 - 面板文案与实现不符 —— 设置里写「调用 gemini-2.5-pro」,实际是
gemini-2.0-flash;页脚的「Gemini 2.5 Pro」同理。 - DeepSeek 通道的 JSON 约束自相矛盾 —— 提示词要数组,
response_format要对象。 - 无任何持久化 —— 输入与大纲刷新即丢,也没有自动草稿。
- TXT 导出不完整 —— 不包含「一句话梗概」与「故事简介」,往返只能保住节点。
- AI 导入的角色不去重 —— 重复导入产生重复角色卡。
- 无拖拽排序 —— 节点顺序只能通过删建或 TXT 往返调整。
- 无撤销/重做 —— 尤其 TXT 导入是破坏性覆盖,误操作只能靠事先导出。
- 并发保护只有一半 —— 全局只挡一个「细化/拆分」,但「AI 继续生成剧情」与「AI 导入」之间没有互斥。
- 长文档静默截断 —— 超过 100000 字符的部分被丢弃且不提示。
- txt 读取无编码探测 ——
File.text()按 UTF-8 解,GBK 文件会乱码。 - 离线不可用 —— Tailwind 与 mammoth 都走 CDN,样式与 docx 解析依赖外网。
工程性
- 错误处理全部靠
alert—— 没有错误边界(Error Boundary),也没有行内错误提示; 模型返回的非法 JSON 会以原始SyntaxError冒泡。 - 框架外依赖链脆弱 —— 核心样式来自 CDN 的 Tailwind Play CDN(官方明确不建议用于生产), 一旦该 CDN 不可达,界面会退化成无样式 HTML。
package.json有重复与版本冲突 ——@vitejs/plugin-react同时列在dependencies(^5.1.2) 与devDependencies(^5.0.0);vite同样重复(^7.3.1 与 ^6.2.0)。装上之后实际生效的是哪一版取决于 npm 的解析结果。- 包名是构建工具生成的 ——
name为copy-of-copy-of-灵感剧本规划器-版本3,带有明显的复制痕迹。 AISettings.baseUrl是死字段 —— 类型里有,面板不给改,代码不读。- ID 生成策略混用 —— 时间戳与
Math.random().substr(2,9)并存,substr已废弃。 - 导入正则有笔误 —— 字符类写成了
[0-90-9一二三四五六七八九十百](0-9重复了一次),功能正常但不严谨。 - 没有测试、没有 lint、没有 CI —— 三个 npm 脚本只覆盖 dev/build/preview。
index.html里的 importmap 与 Vite 打包并存 —— AI Studio 模板遗留,属于历史包袱。declare var mammoth: any—— 依赖 CDN 全局变量且完全放弃类型,typeof mammoth === 'undefined'才做检查。
其他
- 仓库名为
-,不利检索; - 没有 LICENSE 文件(见下节)。
全部提交集中在 2026-01-09,共 3 次,作者 kuroshio4396:
| 提交 | 时间 (UTC) | 说明 |
|---|---|---|
883d63e |
2026-01-09 08:04 | Initial commit |
52187db |
2026-01-09 08:07 | feat: Initialize project structure and dependencies |
e727898 |
2026-01-09 15:42 | feat: Update dependencies and fix Vite build |
从提交信息可以看出:项目是当天从 Google AI Studio 导出后直接推上来的,
第三次提交修过一次 Vite 构建配置(index.html 里那句
「关键修复:添加入口文件引用,确保 Vite 能打包 TypeScript 代码」的注释应该就来自那一次)。
此后没有新的提交,本文档是该仓库的第一次内容性更新。
本仓库目前没有 LICENSE 文件,因此默认「保留所有权利」——他人不能合法地复制、修改或分发。
如果你是作者并希望放开,最简单的做法是在仓库根目录添加一个 LICENSE 文件:
- 打开 https://choosealicense.com/ 选一个协议(开源常用 MIT / Apache-2.0,中文项目也常用 MPL-2.0);
- 新建
LICENSE文件,把协议全文粘进去,填好年份与署名; - 提交后 GitHub 会自动在仓库页右侧识别并显示协议名。
需要注意,package.json 里也没有 license 字段,建议一并补上以保持一致。
- 项目由 kuroshio4396 基于 Google AI Studio 的
aistudio-repository-template模板构建。 - 依赖 Google Gen AI SDK、 React、Vite、 Tailwind CSS、 mammoth.js,以及 DeepSeek 开放平台 的 OpenAI 兼容接口。
- 字体使用 Google Fonts 的 Noto Sans SC。
灵感剧本规划器 · 把碎片化的灵感,长成一份能往下拍的剧本大纲