ソースを参照

fmode-image v2.0: 6种模式(--app/--arch/--scene/--slide/--explode/--detail) + Windows兼容 + 最佳实践文档

liuyuyang 5 時間 前
コミット
710a405742
5 ファイル変更164 行追加123 行削除
  1. 22 5
      README.md
  2. 14 29
      bin/fmode-image.js
  3. 3 3
      package.json
  4. 60 68
      scripts/gen.py
  5. 65 18
      skills/fmode-image/SKILL.md

+ 22 - 5
README.md

@@ -1,11 +1,15 @@
 # fmode-image
 # fmode-image
 
 
-AI架构图和场景插图生成器。自动读取 Fmode API Key,支持架构图和场景图双模式
+AI架构图和场景插图生成器。自动读取 Fmode API Key,6种模式覆盖PPT配图全部场景
 
 
 ## 快速使用
 ## 快速使用
 ```bash
 ```bash
-npx fmode-image --arch "你的架构图描述" 输出文件名
-npx fmode-image --scene "你的场景描述" 输出文件名
+npx fmode-image --app    "标题xxx,左侧导航,顶部指标卡"  应用界面  ← 最推荐
+npx fmode-image --arch   "标题xxx,从上到下五层"           架构图
+npx fmode-image --scene  "法务团队深夜加班场景"             场景插图
+npx fmode-image --slide  "主标题/副标题"                   整页PPT
+npx fmode-image --explode "内部分层,数据流向"             爆炸图
+npx fmode-image --detail "零件特写"                        细节图
 ```
 ```
 
 
 ## 安装
 ## 安装
@@ -14,5 +18,18 @@ npm install -g fmode-image
 ```
 ```
 
 
 ## 环境变量
 ## 环境变量
-- `FMODE_API_KEY` — Fmode API 密钥
-- `SKILL_IMAGE_OUTPUT` — 输出目录 (默认当前目录)
+- `FMODE_API_KEY` — Fmode API密钥(必设)
+- `SKILL_IMAGE_OUTPUT` — 输出目录(默认当前目录)
+
+## 凭据读取顺序
+1. `FMODE_API_KEY` 环境变量
+2. `~/.fmode/config.json` 中的 api_key / fmodeApiToken
+3. `.env` 文件
+
+## 输出
+纯白底PNG,可直接拖入PPT使用。
+
+## 链接
+- GitHub: https://github.com/fmodecn/fmode-image
+- Gogs: https://git.fmode.cn/fmode/fmode-image
+- npm: https://www.npmjs.com/package/fmode-image

+ 14 - 29
bin/fmode-image.js

@@ -1,39 +1,24 @@
 #!/usr/bin/env node
 #!/usr/bin/env node
-const fs = require('fs');
-const os = require('os');
 const path = require('path');
 const path = require('path');
 const { spawnSync } = require('child_process');
 const { spawnSync } = require('child_process');
-
-const SKILL_NAME = 'fmode-image';
-const SOURCE_ROOT = path.resolve(__dirname, '..');
-const SKILL_SOURCE = path.join(SOURCE_ROOT, 'skills', SKILL_NAME);
-const GEN_SCRIPT = path.join(SKILL_SOURCE, 'scripts', 'gen.py');
-const WORKSPACE_ROOT = process.cwd();
-
-function runGen(passthrough) {
-  const python = process.platform === 'win32' ? 'python' : 'python3';
-  const result = spawnSync(python, [GEN_SCRIPT, ...passthrough], { stdio: 'inherit', shell: false });
-  if (result.error) {
-    console.error(`fmode-image: failed to launch generator: ${result.error.message}`);
-    process.exit(1);
-  }
-  process.exit(result.status ?? 0);
-}
-
+const GEN = path.resolve(__dirname, '..', 'scripts', 'gen.py');
 const args = process.argv.slice(2);
 const args = process.argv.slice(2);
-if (args.length === 0) {
-  console.log(`fmode-image — Fmode Image Generator
+if (!args.length || args[0]==='--help') {
+  console.log(`fmode-image — AI架构图和场景插图生成器
 
 
 用法:
 用法:
-  npx fmode-image --arch "prompt" [name]     生成架构图 (1792x1024, ¥0.5)
-  npx fmode-image --scene "prompt" [name]    生成场景图 (1024x1024, ¥0.3)
-
-示例:
-  npx fmode-image --arch "内容营销预审Agent Harness架构,五层..." case01
-  npx fmode-image --scene "法务团队被稿件淹没的办公场景" legal-scene
+  npx fmode-image --arch    "prompt" [name]    架构图 ¥0.5
+  npx fmode-image --app     "prompt" [name]    应用界面 ¥0.5
+  npx fmode-image --explode "prompt" [name]    爆炸图 ¥0.5
+  npx fmode-image --scene   "prompt" [name]    场景插图 ¥0.3
+  npx fmode-image --detail  "prompt" [name]    细节图 ¥0.3
+  npx fmode-image --slide   "prompt" [name]    整页PPT ¥0.5
 
 
-自动读取 FMODE_API_KEY (env/.fmode/config.json/.env)
+自动读取 FMODE_API_KEY (env/config.json/.env)
+输出目录: SKILL_IMAGE_OUTPUT 环境变量控制(默认当前目录)
 `);
 `);
   process.exit(0);
   process.exit(0);
 }
 }
-runGen(args);
+const python = process.platform==='win32'?'python':'python3';
+const r = spawnSync(python, [GEN, ...args], { stdio: 'inherit', shell: false });
+process.exit(r.status??0);

+ 3 - 3
package.json

@@ -1,7 +1,7 @@
 {
 {
   "name": "fmode-image",
   "name": "fmode-image",
-  "version": "1.0.0",
-  "description": "Fmode Image Generator: 架构图和场景插图生成器。自动读取API Key,支持AI架构图(1792x1024)和场景插图(1024x1024)双模式,纯白底PNG直接用于PPT。",
+  "version": "2.0.0",
+  "description": "Fmode Image Generator: \u67b6\u6784\u56fe\u548c\u573a\u666f\u63d2\u56fe\u751f\u6210\u5668\u3002\u81ea\u52a8\u8bfb\u53d6API Key\uff0c\u652f\u6301AI\u67b6\u6784\u56fe(1792x1024)\u548c\u573a\u666f\u63d2\u56fe(1024x1024)\u53cc\u6a21\u5f0f\uff0c\u7eaf\u767d\u5e95PNG\u76f4\u63a5\u7528\u4e8ePPT\u3002",
   "type": "commonjs",
   "type": "commonjs",
   "bin": {
   "bin": {
     "fmode-image": "bin/fmode-image.js"
     "fmode-image": "bin/fmode-image.js"
@@ -26,4 +26,4 @@
     "ppt"
     "ppt"
   ],
   ],
   "license": "MIT"
   "license": "MIT"
-}
+}

+ 60 - 68
scripts/gen.py

@@ -1,100 +1,92 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
-"""skill-image: 架构图 + 场景插图生成器
+"""
+fmode-image v2.0 — 架构图/应用界面/场景插图/整页PPT生成器
+
 用法:
 用法:
-  python3 gen.py --arch "prompt" [name]     # 架构图 1792x1024 ¥0.5/张
-  python3 gen.py --scene "prompt" [name]    # 场景图 1024x1024 ¥0.3/张
-  python3 gen.py --batch-arch <json_file>   # 批量架构图
+  python3 gen.py --arch    "prompt" [name]    架构图 1792x1024 ¥0.5
+  python3 gen.py --app     "prompt" [name]    应用界面 1792x1024 ¥0.5
+  python3 gen.py --explode "prompt" [name]    爆炸图 1792x1024 ¥0.5
+  python3 gen.py --scene   "prompt" [name]    场景插图 1024x1024 ¥0.3
+  python3 gen.py --detail  "prompt" [name]    细节图 1024x1024 ¥0.3
+  python3 gen.py --slide   "prompt" [name]    整页PPT 1920x1080 ¥0.5
 
 
-凭据: 自动读取 FMODE_API_KEY (env/config)
+凭据: 自动读取 FMODE_API_KEY (env/config.json/.env)
+输出: 白底PNG,SKILL_IMAGE_OUTPUT 控制输出目录
 """
 """
 import json, base64, urllib.request, os, sys
 import json, base64, urllib.request, os, sys
+sys.stdout.reconfigure(encoding='utf-8', errors='replace')
 
 
 API = 'https://api.fmode.cn/v1/images/generations'
 API = 'https://api.fmode.cn/v1/images/generations'
 
 
+MODES = {
+    '--arch':    {'prefix':'纯白背景扁平风格架构图。','suffix':'简洁扁平商务风格,纯白背景适合PPT。','size':'1792x1024','cost':0.5,'desc':'架构/分层/闭环图'},
+    '--app':     {'prefix':'现代SaaS应用界面截图,浅色主题。','suffix':'界面细节清晰锐利,中文界面文字,像真实在运行的产品。','size':'1792x1024','cost':0.5,'desc':'产品界面(表现力最强)'},
+    '--explode': {'prefix':'技术爆炸图,内部分层剖开并标注数据流向。','suffix':'扁平科技风格,纯白背景,层级与流向清晰。','size':'1792x1024','cost':0.5,'desc':'内部运转/拆解图'},
+    '--scene':   {'prefix':'扁平插画场景。','suffix':'清新明亮风格。','size':'1024x1024','cost':0.3,'desc':'场景/人物/痛点插图'},
+    '--detail':  {'prefix':'精密细节特写图。','suffix':'写实质感,清晰锐利。','size':'1024x1024','cost':0.3,'desc':'零件/材质/实物特写'},
+    '--slide':   {'prefix':'一页16:9演示幻灯片。','suffix':'留白充足,字号层级分明,商务演示风格。','size':'1920x1080','cost':0.5,'desc':'整页PPT直接使用'},
+}
+
 def _get_key():
 def _get_key():
-    for src_name, src_val in [
-        ('FMODE_API_KEY env', os.environ.get('FMODE_API_KEY', '')),
-        ('ANTHROPIC_AUTH_TOKEN env', os.environ.get('ANTHROPIC_AUTH_TOKEN', '')),
-    ]:
-        if src_val and '***' not in src_val and len(src_val) > 10:
-            return src_val
+    for v in [os.environ.get('FMODE_API_KEY',''), os.environ.get('ANTHROPIC_AUTH_TOKEN','')]:
+        if v and '***' not in v and len(v) > 10: return v
     try:
     try:
-        with open(os.path.expanduser('~/.fmode/config.json')) as f:
+        with open(os.path.expanduser('~/.fmode/config.json'),encoding='utf-8') as f:
             c = json.load(f)
             c = json.load(f)
-            for k in ('api_key', 'FMODE_API_KEY'):
+            for k in ('api_key','FMODE_API_KEY','fmodeApiToken'):
                 if c.get(k): return c[k]
                 if c.get(k): return c[k]
     except: pass
     except: pass
     try:
     try:
-        with open(os.path.expanduser('~/.fmode/config.yaml')) as f:
+        with open(os.path.expanduser('~/.fmode/config.yaml'),encoding='utf-8') as f:
             for l in f:
             for l in f:
-                for kw in ('api_key', 'FMODE_API_KEY'):
+                for kw in ('api_key','FMODE_API_KEY','fmodeApiToken'):
                     if kw in l:
                     if kw in l:
-                        v = l.split(':')[1].strip().strip('"\'')
+                        v = l.split(':',1)[1].strip().strip('"\'')
                         if v and v != '${FMODE_API_KEY}': return v
                         if v and v != '${FMODE_API_KEY}': return v
     except: pass
     except: pass
-    for p in ['/opt/data/.env', '.env']:
+    for p in ['/opt/data/.env','.env']:
         try:
         try:
-            with open(p) as f:
+            with open(p,encoding='utf-8') as f:
                 for l in f:
                 for l in f:
                     if 'FMODE_API_KEY' in l:
                     if 'FMODE_API_KEY' in l:
-                        v = l.split('=')[1].strip().strip('"\'')
-                        if v and '***' not in v and len(v) > 10: return v
+                        v = l.split('=',1)[1].strip().strip('"\'')
+                        if v and '***' not in v and len(v)>10: return v
         except: pass
         except: pass
-    print('❌ 未找到 API Key。设置环境变量 FMODE_API_KEY 或 ~/.fmode/config.json')
+    print('No API Key found. Set FMODE_API_KEY env or ~/.fmode/config.json')
     sys.exit(1)
     sys.exit(1)
 
 
-API_KEY = _get_key()
-
 def _call_api(prompt, size):
 def _call_api(prompt, size):
-    body = json.dumps({"model": "gpt-image-2.5-sunburst", "prompt": prompt, "n": 1, "size": size}).encode()
-    req = urllib.request.Request(API, data=body,
-        headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
-        method='POST')
-    resp = urllib.request.urlopen(req, timeout=300)
+    body = json.dumps({"model":"gpt-image-2.5-sunburst","prompt":prompt,"n":1,"size":size}).encode()
+    req = urllib.request.Request(API,data=body,
+        headers={"Authorization":f"Bearer {_get_key()}","Content-Type":"application/json"},method='POST')
+    resp = urllib.request.urlopen(req,timeout=300)
     data = json.loads(resp.read())
     data = json.loads(resp.read())
-    if 'b64_json' in data['data'][0]:
-        return base64.b64decode(data['data'][0]['b64_json'])
-    if 'url' in data['data'][0]:
-        return urllib.request.urlopen(data['data'][0]['url'], timeout=60).read()
+    if 'b64_json' in data['data'][0]: return base64.b64decode(data['data'][0]['b64_json'])
+    if 'url' in data['data'][0]: return urllib.request.urlopen(data['data'][0]['url'],timeout=60).read()
     raise Exception('No image data')
     raise Exception('No image data')
 
 
-def gen_arch(prompt, name='arch'):
-    """架构图: 1792x1024 ¥0.5/张,纯白底PPT用"""
-    full = f'纯白背景扁平风格架构图。{prompt} 简洁扁平商务风格,纯白背景适合PPT。'
-    out = _gen(full, '1792x1024', name)
-    return out
-
-def gen_scene(prompt, name='scene'):
-    """场景/插图: 1024x1024 ¥0.3/张,照片/示意图/画面"""
-    full = f'扁平插画场景。{prompt} 清新明亮风格。'
-    out = _gen(full, '1024x1024', name)
-    return out
-
-def _gen(prompt, size, name):
-    outdir = os.environ.get('SKILL_IMAGE_OUTPUT', '.')
-    os.makedirs(outdir, exist_ok=True)
-    path = f'{outdir}/{name}.png'
-    data = _call_api(prompt, size)
-    with open(path, 'wb') as f:
-        f.write(data)
+def gen(mode, prompt, name='output'):
+    cfg = MODES.get(mode)
+    if not cfg: print(f'Unknown mode {mode}'); sys.exit(1)
+    full = f'{cfg["prefix"]}{prompt} {cfg["suffix"]}'
+    outdir = os.environ.get('SKILL_IMAGE_OUTPUT','.')
+    os.makedirs(outdir,exist_ok=True)
+    path = os.path.join(outdir,f'{name}.png')
+    data = _call_api(full,cfg['size'])
+    with open(path,'wb') as f: f.write(data)
     sz = os.path.getsize(path)//1024
     sz = os.path.getsize(path)//1024
-    print(f'✅ {sz}KB -> {path}')
+    print(f'OK {cfg["desc"]} {sz}KB {cfg["size"]} {path}')
     return path
     return path
 
 
-def batch_arch(items):
-    """items: [(name, prompt), ...]"""
-    for name, prompt in items:
-        print(f'{name}:', end=' ', flush=True)
-        try:
-            gen_arch(prompt, name)
-        except Exception as e:
-            print(f'❌ {e}')
+gen_arch=lambda p,n='arch':gen('--arch',p,n)
+gen_app=lambda p,n='app':gen('--app',p,n)
+gen_explode=lambda p,n='explode':gen('--explode',p,n)
+gen_scene=lambda p,n='scene':gen('--scene',p,n)
+gen_detail=lambda p,n='detail':gen('--detail',p,n)
+gen_slide=lambda p,n='slide':gen('--slide',p,n)
 
 
-if __name__ == '__main__':
-    if len(sys.argv) < 3:
-        print(__doc__)
-        sys.exit(1)
-    mode, content = sys.argv[1], sys.argv[2]
-    name = sys.argv[3] if len(sys.argv) > 3 else 'output'
-    {'--arch': lambda: gen_arch(content, name),
-     '--scene': lambda: gen_scene(content, name)}.get(mode, lambda: print(f'Unknown mode {mode}'))()
+if __name__=='__main__':
+    if len(sys.argv)<3: print(__doc__); sys.exit(1)
+    mode,content=sys.argv[1],sys.argv[2]
+    name=sys.argv[3] if len(sys.argv)>3 else 'output'
+    gen(mode,content,name)

+ 65 - 18
skills/fmode-image/SKILL.md

@@ -1,37 +1,84 @@
 ---
 ---
 name: fmode-image
 name: fmode-image
 description: "触发词:生成架构图/场景图/插图。走API出白底PNG,自动读Key,¥0.3-0.5/张。"
 description: "触发词:生成架构图/场景图/插图。走API出白底PNG,自动读Key,¥0.3-0.5/张。"
-version: 1.0.0
+version: 2.0.0
 author: Fmode
 author: Fmode
 license: MIT
 license: MIT
 platforms: [linux, macos, windows]
 platforms: [linux, macos, windows]
 ---
 ---
 
 
-# fmode-image — AI架构图和场景插图生成器
+# fmode-image v2 — AI架构图和场景插图生成器
+
+6种模式 · 自动读API Key · 白底PNG直接放PPT
 
 
 ## 凭据自动获取
 ## 凭据自动获取
-自动读取:FMODE_API_KEY (env) → ~/.fmode/config.json → ~/.fmode/config.yaml → .env
+FMODE_API_KEY (env) → ~/.fmode/config.json → .env
 
 
-## 双模式
+## 六种模式速查
 
 
-### --arch 架构图
-- **尺寸**: 1792×1024 (PPT横版)
-- **成本**: ≈¥0.5/张
-- **风格**: 纯白底、扁平、商务
+| 模式 | 尺寸 | 成本 | 用途 | 推荐度 |
+|------|------|------|------|--------|
+| `--app` | 1792×1024 | ¥0.5 | **应用界面**(表现力最强) | ⭐⭐⭐⭐⭐ |
+| `--arch` | 1792×1024 | ¥0.5 | 架构图/分层/闭环 | ⭐⭐⭐⭐ |
+| `--slide` | 1920×1080 | ¥0.5 | 整页PPT(封面/金句) | ⭐⭐⭐⭐ |
+| `--explode` | 1792×1024 | ¥0.5 | 爆炸图/内部运转 | ⭐⭐⭐ |
+| `--scene` | 1024×1024 | ¥0.3 | 场景插图/痛点 | ⭐⭐⭐⭐ |
+| `--detail` | 1024×1024 | ¥0.3 | 零件/材质特写 | ⭐⭐ |
 
 
-### --scene 场景/插图
-- **尺寸**: 1024×1024 (方形)
-- **成本**: ≈¥0.3/张 (省40%)
-- **风格**: 扁平插画、清新明亮
+## 快速开始
 
 
-## 用法
 ```bash
 ```bash
-# 架构图
-npx fmode-image --arch "内容营销预审Agent Harness架构,五层..." case01
+# 安装
+npm install -g fmode-image
+
+# 生成应用界面(表现力最强,推荐!)
+npx fmode-image --app "标题"内容合规系统"左侧导航5项,顶部指标卡3张" my-app
+
+# 生成架构图
+npx fmode-image --arch "标题"Harness五层引擎"从上到下五层用箭头连接" my-arch
 
 
-# 场景图
-npx fmode-image --scene "法务团队被稿件淹没的场景" legal-scene
+# 生成场景插图
+npx fmode-image --scene "法务团队深夜加班堆满稿件的办公场景,人物表情焦虑" legal-scene
+
+# 生成整页PPT
+npx fmode-image --slide "主标题内容营销的超级预审Agent,副标题把合规从3天变成3分钟" cover
 ```
 ```
 
 
+## 最佳实践
+
+### --app 应用界面(投入产出比最高)
+- 结构:左导航 + 顶部指标卡 + 中间表格 + 右侧详情面板
+- 指标卡带同比(↑12%)瞬间有真实感
+- 表格行用彩色状态徽章、截断省略号
+- 所有数字必须与讲稿一致(模型会编造)
+
+### --arch 架构图
+- 右侧数据卡是灵魂——让架构图从说明书变成成果展示
+- 每层给名称 + 副标 + 三个要点
+- 数字必须来自讲稿且逐项核对
+- 层级名与讲稿逐字一致
+
+### --scene 场景插图
+- 给情绪词(焦虑/惊喜/专注)比纯描述生动十倍
+- 环境元素承载信息:67%退审率大屏、深夜时钟
+- 对比场景(Before/After)冲击最大
+- 同案例内风格统一
+
+### --slide 整页PPT
+- 封面页/金句页/章节过渡页最稳(文字少)
+- 用1920×1080严格16:9比例
+- 内容页文字多易糊,推荐素材图+HTML排版
+
+## Prompt技巧
+1. 数字要给全写死,模型会编造
+2. 单段文字≤12字,长句拆短标签
+3. 关键标题/数字在prompt里出现两次
+4. 方向描述要明确(从X提升到Y,不说"显著提升")
+5. 同案例多图固定四样:配色色值/风格词/版式/品牌落款
+
+## 输出控制
+- 环境变量 `SKILL_IMAGE_OUTPUT` 控制目录(默认当前目录)
+- 文件名语义化:`arch-harness-5layer.png`
+
 ## 独立脚本
 ## 独立脚本
-scripts/gen.py 可直接运行,不依赖node:python3 gen.py --arch "..." out
+scripts/gen.py 可直接运行:`python3 gen.py --arch "..." out`