From 6a37801d891129a6b64b9f0e6ce8cfdb16e34c2c Mon Sep 17 00:00:00 2001 From: liyachen Date: Thu, 16 Jul 2026 21:55:38 -0400 Subject: [PATCH] release: 0.2.0 (base folder, built-in SOPs with customize fork) --- CHANGELOG.md | 8 ++ main.js | 248 +++++++++++++++++++++++++++++++++----------------- manifest.json | 2 +- versions.json | 3 +- 4 files changed, 173 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c9bc24..b6e3fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.2.0 + +- Base folder setting: change one field and the four storage paths follow automatically. A Restore button resets the factory layout under `Clips/`. +- Built-in analysis SOPs (cover, hook, keyframe; each in Chinese and English) ship inside the plugin and are on by default: a mode whose SOP path is empty now carries its built-in analysis prompt in every saved note. A master switch in settings turns this off for material-only clipping. +- Per-mode Customize button forks the built-in SOP into `/SOPs` and points that mode's SOP path at the copy; clearing the path returns the mode to built-in. Each SOP row shows which source is currently in effect. +- SOP paths accept vault-relative paths in addition to absolute ones. +- English settings copy avoids dashes; all new settings strings are bilingual (English and Chinese). + ## 0.1.0 — Initial release First public release of Vault Autopilot, the vault-side companion to the **Obsidian Visual Clipper** Chrome extension. diff --git a/main.js b/main.js index 06e775c..35a467c 100644 --- a/main.js +++ b/main.js @@ -33,6 +33,7 @@ __export(main_exports, { }); module.exports = __toCommonJS(main_exports); var fs = __toESM(require("fs")); +var nodePath = __toESM(require("path")); var import_obsidian2 = require("obsidian"); // src/settings.ts @@ -62,15 +63,24 @@ var en_default = { "settings.port.desc": "Default 17183. Only change it if the port is taken; after changing, set the same value on the extension's welcome page (Advanced \u2192 Port), or the two sides will disconnect. Restart Obsidian afterwards.", "settings.maxFrames.name": "Max frames", "settings.maxFrames.desc": "Maximum frames kept per hook / keyframe clip (1 to 20). Default 5.", - "settings.installSops.name": "Bundled SOPs", - "settings.installSops.desc": "Three ready-made analysis SOPs (cover, hook, keyframe), each in Chinese and English. One click writes them into /SOPs. Existing files are never overwritten. Point the SOP path fields below at the ones you want to use.", - "settings.installSops.button": "Install", - "notice.sopsInstalled": "Installed {count} SOP file(s) into {folder}. Skipped {skipped} already there.", - "settings.sop.thumbnail": "Cover SOP path", - "settings.sop.screenshot": "Screenshot SOP path", - "settings.sop.hook": "Hook SOP path", - "settings.sop.keyframe": "Keyframe SOP path", - "settings.sop.desc": "Leave empty for material-only mode (no analysis prompt). Absolute path to a markdown file inside the vault.", + "settings.sopHeading": "Analysis SOPs", + "settings.useBuiltinSops.name": "Use built-in analysis SOPs", + "settings.useBuiltinSops.desc": "On: every saved clip carries its matching built-in SOP (cover, hook, keyframe), and plugin updates keep them current. Off: clips save material only, with no analysis prompt.", + "settings.sopRow.cover": "Cover SOP", + "settings.sopRow.screenshot": "Screenshot SOP", + "settings.sopRow.hook": "Hook SOP", + "settings.sopRow.keyframe": "Keyframe SOP", + "settings.sopRow.desc": "Leave empty to use the built-in SOP. Want to tweak it? Click Customize: the copy and the path are set up for you.", + "settings.sopRow.descNoBuiltin": "Screenshot mode has no built-in SOP. To attach one, enter the path of your own SOP file (inside the vault or absolute).", + "settings.sopRow.placeholder": "Empty = built-in", + "settings.sopRow.placeholderNoBuiltin": "Optional: path to your own SOP", + "settings.sopState.builtin": "In effect: built-in SOP (updates with the plugin)", + "settings.sopState.custom": "In effect: your custom file (clear the path to return to built-in)", + "settings.sopState.off": "In effect: no analysis (master switch is off)", + "settings.sopState.none": "In effect: no analysis (this mode has no built-in SOP)", + "settings.sopCustomize": "Customize", + "notice.sopExported": "Exported to {path}. Open it and edit; changes apply on the next save.", + "notice.sopExportExists": "{path} already exists. The path now points to it.", "notice.savedTo": "Saved to {folder}\nWant a different location? Settings \u2192 Vault Autopilot \u2192 Storage locations", "notice.portInUse": "Vault Autopilot: port {port} is already in use. Quit the program using it, or set the same new port in both the plugin settings and the extension settings.", "notice.sectionExists": '"{section}" already exists \u2014 not overwritten. To redo it, delete that section first, then clip again.', @@ -118,15 +128,24 @@ var zh_default = { "settings.port.desc": "\u9ED8\u8BA4 17183\u3002\u4EC5\u5F53\u7AEF\u53E3\u88AB\u5360\u7528\u65F6\u624D\u9700\u8981\u6539\uFF1B\u6539\u5B8C\u5FC5\u987B\u5728\u6269\u5C55\u7684\u5F15\u5BFC\u9875\uFF08\u9AD8\u7EA7 \u2192 \u7AEF\u53E3\uFF09\u6539\u6210\u540C\u4E00\u4E2A\u503C\uFF0C\u5426\u5219\u4E24\u8FB9\u4F1A\u65AD\u5F00\u3002\u6539\u540E\u91CD\u542F Obsidian\u3002", "settings.maxFrames.name": "\u62BD\u5E27\u6570\u91CF\u4E0A\u9650", "settings.maxFrames.desc": "Hook / \u5173\u952E\u5E27\u6A21\u5F0F\u6700\u591A\u4FDD\u5B58\u51E0\u5E27\uFF081\u201320\uFF09\u3002\u9ED8\u8BA4 5\u3002", - "settings.installSops.name": "\u5185\u7F6E SOP", - "settings.installSops.desc": "\u4E09\u4EFD\u73B0\u6210\u7684\u5206\u6790 SOP\uFF08\u5C01\u9762\u3001Hook\u3001\u5173\u952E\u5E27\uFF09\uFF0C\u4E2D\u82F1\u5404\u4E00\u4EFD\u3002\u70B9\u4E00\u4E0B\u5199\u5165 <\u6839\u6587\u4EF6\u5939>/SOPs\uFF0C\u5DF2\u5B58\u5728\u7684\u6587\u4EF6\u4E0D\u4F1A\u88AB\u8986\u76D6\u3002\u60F3\u542F\u7528\u54EA\u4EFD\uFF0C\u628A\u4E0B\u9762\u5BF9\u5E94\u7684 SOP \u8DEF\u5F84\u6307\u5411\u5B83\u3002", - "settings.installSops.button": "\u5B89\u88C5", - "notice.sopsInstalled": "\u5DF2\u5B89\u88C5 {count} \u4EFD SOP \u5230 {folder}\uFF0C\u8DF3\u8FC7 {skipped} \u4EFD\u5DF2\u5B58\u5728\u7684\u6587\u4EF6\u3002", - "settings.sop.thumbnail": "\u5C01\u9762 SOP \u8DEF\u5F84", - "settings.sop.screenshot": "\u622A\u56FE SOP \u8DEF\u5F84", - "settings.sop.hook": "Hook SOP \u8DEF\u5F84", - "settings.sop.keyframe": "\u5173\u952E\u5E27 SOP \u8DEF\u5F84", - "settings.sop.desc": "\u7559\u7A7A = \u7EAF\u7D20\u6750\u6A21\u5F0F\uFF08\u4E0D\u9644\u5E26\u5206\u6790\u63D0\u793A\uFF09\u3002\u586B vault \u5185 markdown \u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002", + "settings.sopHeading": "\u5206\u6790 SOP", + "settings.useBuiltinSops.name": "\u4F7F\u7528\u5185\u7F6E\u5206\u6790 SOP", + "settings.useBuiltinSops.desc": "\u5F00\u7740\uFF1A\u4FDD\u5B58 clip \u65F6\u81EA\u52A8\u9644\u5E26\u5BF9\u5E94\u7684\u5185\u7F6E\u5206\u6790 SOP\uFF08\u5C01\u9762\u3001Hook\u3001\u5173\u952E\u5E27\uFF09\uFF0C\u63D2\u4EF6\u66F4\u65B0\u65F6 SOP \u81EA\u52A8\u66F4\u65B0\u3002\u5173\u6389\uFF1A\u53EA\u5B58\u7D20\u6750\uFF0C\u4E0D\u5E26\u5206\u6790\u63D0\u793A\u3002", + "settings.sopRow.cover": "\u5C01\u9762 SOP", + "settings.sopRow.screenshot": "\u7F51\u9875\u622A\u56FE SOP", + "settings.sopRow.hook": "Hook SOP", + "settings.sopRow.keyframe": "\u5173\u952E\u5E27 SOP", + "settings.sopRow.desc": "\u7559\u7A7A = \u7528\u5185\u7F6E\u7684\u3002\u60F3\u5728\u5185\u7F6E\u57FA\u7840\u4E0A\u6539\uFF1F\u70B9\u300C\u81EA\u5B9A\u4E49\u300D\uFF0C\u526F\u672C\u548C\u8DEF\u5F84\u90FD\u4F1A\u5E2E\u4F60\u5F04\u597D\u3002", + "settings.sopRow.descNoBuiltin": "\u622A\u56FE\u6A21\u5F0F\u6CA1\u6709\u5185\u7F6E SOP\u3002\u60F3\u7ED9\u622A\u56FE\u6302\u5206\u6790\u63D0\u793A\uFF0C\u586B\u4E00\u4E2A\u4F60\u81EA\u5DF1\u7684 SOP \u6587\u4EF6\u8DEF\u5F84\uFF08\u5E93\u5185\u8DEF\u5F84\u6216\u7EDD\u5BF9\u8DEF\u5F84\u90FD\u884C\uFF09\u3002", + "settings.sopRow.placeholder": "\u7559\u7A7A\u5373\u7528\u5185\u7F6E", + "settings.sopRow.placeholderNoBuiltin": "\u53EF\u9009\uFF1A\u4F60\u81EA\u5DF1\u7684 SOP \u8DEF\u5F84", + "settings.sopState.builtin": "\u5F53\u524D\u751F\u6548\uFF1A\u5185\u7F6E SOP\uFF08\u8DDF\u968F\u63D2\u4EF6\u81EA\u52A8\u66F4\u65B0\uFF09", + "settings.sopState.custom": "\u5F53\u524D\u751F\u6548\uFF1A\u4F60\u7684\u81EA\u5B9A\u4E49\u6587\u4EF6\uFF08\u6E05\u7A7A\u8DEF\u5F84\u53EF\u56DE\u5230\u5185\u7F6E\uFF09", + "settings.sopState.off": "\u5F53\u524D\u751F\u6548\uFF1A\u4E0D\u5E26\u5206\u6790\uFF08\u603B\u5F00\u5173\u5DF2\u5173\uFF09", + "settings.sopState.none": "\u5F53\u524D\u751F\u6548\uFF1A\u4E0D\u5E26\u5206\u6790\uFF08\u6B64\u6A21\u5F0F\u65E0\u5185\u7F6E SOP\uFF09", + "settings.sopCustomize": "\u81EA\u5B9A\u4E49", + "notice.sopExported": "\u5DF2\u5BFC\u51FA\u5230 {path}\uFF0C\u6253\u5F00\u7F16\u8F91\u5373\u53EF\uFF0C\u6539\u52A8\u4E0B\u6B21\u4FDD\u5B58\u65F6\u751F\u6548\u3002", + "notice.sopExportExists": "{path} \u5DF2\u5B58\u5728\uFF0C\u8DEF\u5F84\u5DF2\u6307\u5411\u5B83\u3002", "notice.savedTo": "\u5DF2\u5B58\u5230 {folder}\n\u60F3\u6362\u4F4D\u7F6E\uFF1F\u8BBE\u7F6E \u2192 Vault Autopilot \u2192 \u5B58\u50A8\u4F4D\u7F6E", "notice.portInUse": "Vault Autopilot\uFF1A\u7AEF\u53E3 {port} \u88AB\u5360\u7528\u3002\u8BF7\u5173\u95ED\u5360\u7528\u5B83\u7684\u7A0B\u5E8F\uFF1B\u6216\u5728\u63D2\u4EF6\u8BBE\u7F6E\u548C\u6269\u5C55\u8BBE\u7F6E\u4E24\u5904\u6539\u6210\u540C\u4E00\u4E2A\u65B0\u7AEF\u53E3\u3002", "notice.sectionExists": "\u300C{section}\u300D\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6\u3002\u60F3\u91CD\u505A\u8BF7\u5148\u5220\u6389\u8BE5\u5C0F\u8282\u518D\u70B9\u3002", @@ -168,25 +187,19 @@ function variants(key) { } // src/sops/zh/封面拆解学习 SOP.md -var SOP_default = '---\ntitle: \u5C01\u9762\u62C6\u89E3\u5B66\u4E60 SOP2\ntype: note\npermalink: obsidian-sop/02-\u5199\u4F5C\u4E0E\u5185\u5BB9/\u5C01\u9762\u62C6\u89E3\u5B66\u4E60-sop2\n---\n\n# \u5C01\u9762\u4E0E\u6807\u9898\u5206\u6790 Prompt\n\n_\u8BFB\u5B8C\u8FD9\u4EFD\u6587\u6863\u540E\uFF0C\u76F4\u63A5\u6309\u4EE5\u4E0B\u6846\u67B6\u8F93\u51FA\u5206\u6790\u3002_\n\n---\n\n## \u8F93\u51FA\u7ED3\u6784\n\n**\u63CF\u8FF0**\n\u4E00\u53E5\u8BDD\u8BF4\u6E05\u695A\u5C01\u9762\u91CC\u6709\u4EC0\u4E48\uFF1A\u6709\u6CA1\u6709\u4EBA\u7269\u3001\u5728\u505A\u4EC0\u4E48\u3001\u6709\u6CA1\u6709\u9053\u5177\u6216\u5927\u5B57\u3001\u80CC\u666F\u5927\u6982\u662F\u4EC0\u4E48\u3002\n\n**\u5C01\u9762\u94A9\u5B50**\n\u7B2C\u4E00\u773C\u89E6\u53D1\u4E86\u4EC0\u4E48\u53CD\u5E94\u2014\u2014\u597D\u5947\u3001\u4E0D\u4FE1\u3001\u88AB\u770B\u89C1\u3001\u60F3\u77E5\u9053\u7B54\u6848\u2026\u2026\u8BF4\u6E05\u695A\u662F\u4EC0\u4E48\u8BA9\u4F60\u6709\u8FD9\u4E2A\u53CD\u5E94\uFF0C\u4EE5\u53CA\u4E3A\u4EC0\u4E48\u8FD9\u4E2A\u53CD\u5E94\u4F1A\u8BA9\u4F60\u70B9\u8FDB\u53BB\u3002\n\n**\u6807\u9898\u94A9\u5B50**\uFF08\u6709\u6807\u9898\u65F6\u5FC5\u586B\uFF0C\u65E0\u6807\u9898\u65F6\u8DF3\u8FC7\uFF09\n\u8BFB\u5B8C\u6807\u9898\u4E4B\u540E\u8111\u5B50\u91CC\u5192\u51FA\u6765\u7684\u7B2C\u4E00\u4E2A\u95EE\u9898\u662F\u4EC0\u4E48\u2014\u2014\u90A3\u4E2A\u95EE\u9898\u5C31\u662F\u94A9\u5B50\u3002\u8BF4\u6E05\u695A\u5B83\u662F\u600E\u4E48\u5236\u9020\u51FA\u8FD9\u4E2A\u95EE\u9898\u7684\uFF0C\u4EE5\u53CA\u4E3A\u4EC0\u4E48\u4F60\u4F1A\u60F3\u70B9\u8FDB\u53BB\u627E\u7B54\u6848\u3002\n\n**\u534F\u4F5C**\uFF08\u6709\u6807\u9898\u65F6\u5FC5\u586B\uFF0C\u65E0\u6807\u9898\u65F6\u8DF3\u8FC7\uFF09\n\u5C01\u9762\u548C\u6807\u9898\u52A0\u5728\u4E00\u8D77\uFF0C\u70B9\u51FB\u7684\u7406\u7531\u6709\u6CA1\u6709\u53D8\u5F97\u66F4\u5F3A\uFF1F\u8FD8\u662F\u8BF4\u4E00\u4E2A\u5C31\u591F\u4E86\uFF1F\n\n**\u7279\u6B8A\u5904\u7406**\uFF08\u6761\u4EF6\u89E6\u53D1\uFF0C\u4E0D\u662F\u6BCF\u5F20\u90FD\u6709\uFF09\n\u89E6\u53D1\u6761\u4EF6\uFF1A\u5C01\u9762\u6709\u7279\u6B8A\u89C6\u89C9\u6548\u679C\u3001\u660E\u663E\u7684\u5B57\u4F53\u5904\u7406\u3001\u6216\u503C\u5F97\u5B66\u4E60\u7684\u6784\u56FE\u624B\u6CD5\u3002\u666E\u901A\u7684\u300C\u4EBA\u8138 + \u80CC\u666F + \u6587\u5B57\u300D\u4E0D\u89E6\u53D1\u3002\n\u89E6\u53D1\u65F6\u5199\u4E09\u70B9\uFF1A\n- \u8FD9\u4E2A\u6548\u679C\u6216\u6280\u6CD5\u662F\u4EC0\u4E48\n- \u4E3A\u4EC0\u4E48\u8FD9\u6837\u505A\n- \u600E\u4E48\u590D\u523B\n\n---\n\n## \u8BED\u6C14\u8981\u6C42\n\n\u50CF\u5728\u8DDF\u4EBA\u8BF4"\u4F60\u770B\u8FD9\u5F20\u56FE\uFF0C\u4F60\u770B\u5B8C\u4EC0\u4E48\u611F\u89C9"\uFF0C\u4E0D\u662F\u5728\u5199\u8BBE\u8BA1\u62A5\u544A\u3002\n\n\u4E0D\u8981\u7528\uFF1A\n- "\u5929\u7136\u89C6\u89C9\u5165\u53E3"\u3001"\u89C6\u89C9\u5C42\u7EA7"\u3001"\u6784\u6210\u5B8C\u6574\u7684\u70B9\u51FB\u7406\u7531"\n- \u957F\u6392\u6BD4\u53E5\u3001AI \u603B\u7ED3\u8154\n\n\u8981\u505A\u5230\uFF1A\u6BCF\u9879 2-3 \u53E5\uFF0C\u77ED\uFF0C\u76F4\u63A5\uFF0C\u53E3\u8BED\u611F\u3002\n\n'; +var SOP_default = '# \u5C01\u9762\u4E0E\u6807\u9898\u5206\u6790 Prompt\n\n_\u8BFB\u5B8C\u8FD9\u4EFD\u6587\u6863\u540E\uFF0C\u76F4\u63A5\u6309\u4EE5\u4E0B\u6846\u67B6\u8F93\u51FA\u5206\u6790\u3002_\n\n---\n\n## \u8F93\u51FA\u7ED3\u6784\n\n**\u63CF\u8FF0**\n\u4E00\u53E5\u8BDD\u8BF4\u6E05\u695A\u5C01\u9762\u91CC\u6709\u4EC0\u4E48\uFF1A\u6709\u6CA1\u6709\u4EBA\u7269\u3001\u5728\u505A\u4EC0\u4E48\u3001\u6709\u6CA1\u6709\u9053\u5177\u6216\u5927\u5B57\u3001\u80CC\u666F\u5927\u6982\u662F\u4EC0\u4E48\u3002\n\n**\u5C01\u9762\u94A9\u5B50**\n\u7B2C\u4E00\u773C\u89E6\u53D1\u4E86\u4EC0\u4E48\u53CD\u5E94\u2014\u2014\u597D\u5947\u3001\u4E0D\u4FE1\u3001\u88AB\u770B\u89C1\u3001\u60F3\u77E5\u9053\u7B54\u6848\u2026\u2026\u8BF4\u6E05\u695A\u662F\u4EC0\u4E48\u8BA9\u4F60\u6709\u8FD9\u4E2A\u53CD\u5E94\uFF0C\u4EE5\u53CA\u4E3A\u4EC0\u4E48\u8FD9\u4E2A\u53CD\u5E94\u4F1A\u8BA9\u4F60\u70B9\u8FDB\u53BB\u3002\n\n**\u6807\u9898\u94A9\u5B50**\uFF08\u6709\u6807\u9898\u65F6\u5FC5\u586B\uFF0C\u65E0\u6807\u9898\u65F6\u8DF3\u8FC7\uFF09\n\u8BFB\u5B8C\u6807\u9898\u4E4B\u540E\u8111\u5B50\u91CC\u5192\u51FA\u6765\u7684\u7B2C\u4E00\u4E2A\u95EE\u9898\u662F\u4EC0\u4E48\u2014\u2014\u90A3\u4E2A\u95EE\u9898\u5C31\u662F\u94A9\u5B50\u3002\u8BF4\u6E05\u695A\u5B83\u662F\u600E\u4E48\u5236\u9020\u51FA\u8FD9\u4E2A\u95EE\u9898\u7684\uFF0C\u4EE5\u53CA\u4E3A\u4EC0\u4E48\u4F60\u4F1A\u60F3\u70B9\u8FDB\u53BB\u627E\u7B54\u6848\u3002\n\n**\u534F\u4F5C**\uFF08\u6709\u6807\u9898\u65F6\u5FC5\u586B\uFF0C\u65E0\u6807\u9898\u65F6\u8DF3\u8FC7\uFF09\n\u5C01\u9762\u548C\u6807\u9898\u52A0\u5728\u4E00\u8D77\uFF0C\u70B9\u51FB\u7684\u7406\u7531\u6709\u6CA1\u6709\u53D8\u5F97\u66F4\u5F3A\uFF1F\u8FD8\u662F\u8BF4\u4E00\u4E2A\u5C31\u591F\u4E86\uFF1F\n\n**\u7279\u6B8A\u5904\u7406**\uFF08\u6761\u4EF6\u89E6\u53D1\uFF0C\u4E0D\u662F\u6BCF\u5F20\u90FD\u6709\uFF09\n\u89E6\u53D1\u6761\u4EF6\uFF1A\u5C01\u9762\u6709\u7279\u6B8A\u89C6\u89C9\u6548\u679C\u3001\u660E\u663E\u7684\u5B57\u4F53\u5904\u7406\u3001\u6216\u503C\u5F97\u5B66\u4E60\u7684\u6784\u56FE\u624B\u6CD5\u3002\u666E\u901A\u7684\u300C\u4EBA\u8138 + \u80CC\u666F + \u6587\u5B57\u300D\u4E0D\u89E6\u53D1\u3002\n\u89E6\u53D1\u65F6\u5199\u4E09\u70B9\uFF1A\n- \u8FD9\u4E2A\u6548\u679C\u6216\u6280\u6CD5\u662F\u4EC0\u4E48\n- \u4E3A\u4EC0\u4E48\u8FD9\u6837\u505A\n- \u600E\u4E48\u590D\u523B\n\n---\n\n## \u8BED\u6C14\u8981\u6C42\n\n\u50CF\u5728\u8DDF\u4EBA\u8BF4"\u4F60\u770B\u8FD9\u5F20\u56FE\uFF0C\u4F60\u770B\u5B8C\u4EC0\u4E48\u611F\u89C9"\uFF0C\u4E0D\u662F\u5728\u5199\u8BBE\u8BA1\u62A5\u544A\u3002\n\n\u4E0D\u8981\u7528\uFF1A\n- "\u5929\u7136\u89C6\u89C9\u5165\u53E3"\u3001"\u89C6\u89C9\u5C42\u7EA7"\u3001"\u6784\u6210\u5B8C\u6574\u7684\u70B9\u51FB\u7406\u7531"\n- \u957F\u6392\u6BD4\u53E5\u3001AI \u603B\u7ED3\u8154\n\n\u8981\u505A\u5230\uFF1A\u6BCF\u9879 2-3 \u53E5\uFF0C\u77ED\uFF0C\u76F4\u63A5\uFF0C\u53E3\u8BED\u611F\u3002\n\n'; // src/sops/zh/视频Hook分析 SOP.md -var Hook_SOP_default = '---\ntitle: \u89C6\u9891Hook\u5206\u6790 SOP\ntype: note\npermalink: obsidian-sop/05-\u5BA1\u7F8E\u79EF\u7D2F/\u89C6\u9891-hook-\u5206\u6790-sop\n---\n\n# \u89C6\u9891 Hook \u5206\u6790 Prompt\n\n_\u8BFB\u5B8C\u8FD9\u4EFD\u6587\u6863\u540E\uFF0C\u76F4\u63A5\u6309\u4EE5\u4E0B\u6846\u67B6\u8F93\u51FA Hook \u5206\u6790\u3002_\n\n> Hook \u7684\u4F5C\u7528\u662F\u8BA9\u4EBA\u7559\u4E0B\uFF0C\u4E0D\u662F\u8BA9\u4EBA\u70B9\u8FDB\u6765\uFF08\u90A3\u662F\u5C01\u9762\u548C\u6807\u9898\u7684\u4E8B\uFF09\u3002\u5206\u6790\u65F6\u59CB\u7EC8\u56F4\u7ED5\u8FD9\u4E00\u4EF6\u4E8B\uFF1A\u8FD9\u6BB5\u5F00\u573A\u65E0\u8BBA\u662F\u8BDD\u8FD8\u662F\u753B\u9762\uFF0C\u600E\u4E48\u8BA9\u4EBA\u4E0D\u8D70\uFF1F\n\n---\n\n## \u8F93\u5165\n\n- \u89C6\u9891\u5F00\u573A\u5E27\u622A\u56FE\uFF080\u201315 \u79D2\uFF09\n- \u5B57\u5E55\u6587\u672C\uFF08\u6709\u5219\u7528\uFF0C\u6CA1\u6709\u5219\u53EA\u5206\u6790\u753B\u9762\u5E76\u6CE8\u660E\uFF09\n\n---\n\n## \u8F93\u51FA\u7ED3\u6784\n\n**Hook \u7C7B\u578B**\n\u4ECE\u4EE5\u4E0B\u9009\u4E00\u4E2A\u6216\u4E24\u4E2A\uFF0C\u4E00\u53E5\u8BDD\u8BF4\u4E3A\u4EC0\u4E48\uFF1A\n\u60AC\u5FF5\u578B / \u51B2\u7A81\u578B / \u4EF7\u503C\u627F\u8BFA\u578B / \u8EAB\u4EFD\u8BA4\u540C\u578B / \u89C6\u89C9\u51B2\u51FB\u578B / \u53CD\u5E38\u8BC6\u578B / \u6545\u4E8B\u5207\u5165\u578B\n\n**\u4ED6\u8BF4\u4E86\u4EC0\u4E48**\n\u5206\u6790\u5F00\u573A\u7684\u7B2C\u4E00\u53E5\u8BDD\u6216\u524D\u51E0\u53E5\u8BDD\uFF1A\u8FD9\u53E5\u8BDD\u5982\u4F55\u5728\u7B2C\u4E00\u65F6\u95F4\u62A2\u5360\u6CE8\u610F\u529B\uFF1F\u662F\u4EC0\u4E48\u53E5\u5F0F\u3001\u4EC0\u4E48\u627F\u8BFA\u3001\u4EC0\u4E48\u6570\u5B57\u3001\u8FD8\u662F\u4EC0\u4E48\u53CD\u5DEE\uFF1F\n\n**\u753B\u9762\u600E\u4E48\u914D\u5408**\n\u4E0D\u8981\u9010\u5E27\u63CF\u8FF0"\u7B2C\u4E00\u5E27\u662FX\uFF0C\u7B2C\u4E8C\u5E27\u662FY"\u3002\u5206\u6790\u753B\u9762\u5728\u505A\u4EC0\u4E48\u4E8B\u3001\u4E3A\u4EC0\u4E48\u8FD9\u6837\u505A\u80FD\u8BA9\u4EBA\u7559\u4E0B\u6765\u3002\u753B\u9762\u548C\u8BDD\u4E4B\u95F4\u662F\u653E\u5927\u5173\u7CFB\u8FD8\u662F\u8865\u5145\u5173\u7CFB\uFF1F\n\n**\u5982\u4F55\u590D\u5236**\n\u7ED9\u51FA\u53EF\u4EE5\u76F4\u63A5\u5957\u7528\u7684\u6A21\u677F\u6216\u64CD\u4F5C\u6B65\u9AA4\uFF0C\u5177\u4F53\u5230\u53E5\u5F0F\u3001\u753B\u9762\u5904\u7406\u65B9\u5F0F\u3002\u4E0D\u80FD\u5199"\u7528\u7C7B\u4F3C\u624B\u6CD5"\u3002\n\n**\u6211\u7684\u60F3\u6CD5**\n\uFF08\u7559\u7A7A\u2014\u2014\u8FD9\u662F\u4F60\u81EA\u5DF1\u586B\u7684\uFF09\n\n---\n\n## \u8BED\u6C14\u8981\u6C42\n\n\u50CF\u5728\u8DDF\u4EBA\u63CF\u8FF0\u4F60\u770B\u8FD9\u6BB5\u89C6\u9891\u7684\u611F\u53D7\uFF0C\u4E0D\u662F\u5728\u5199\u5206\u6790\u62A5\u544A\u3002\n\n\u4E0D\u8981\u7528\uFF1A\u89C6\u89C9\u5C42\u7EA7\u3001\u4FE1\u606F\u5BC6\u5EA6\u3001\u5F3A\u5316\u8BA4\u77E5\u3001\u9010\u5E27\u7F57\u5217\n\u8981\u505A\u5230\uFF1A\u77ED\u53E5\uFF0C\u53E3\u8BED\u611F\uFF0C\u6BCF\u9879 3-5 \u53E5\u4EE5\u5185\n\n'; +var Hook_SOP_default = '# \u89C6\u9891 Hook \u5206\u6790 Prompt\n\n_\u8BFB\u5B8C\u8FD9\u4EFD\u6587\u6863\u540E\uFF0C\u76F4\u63A5\u6309\u4EE5\u4E0B\u6846\u67B6\u8F93\u51FA Hook \u5206\u6790\u3002_\n\n> Hook \u7684\u4F5C\u7528\u662F\u8BA9\u4EBA\u7559\u4E0B\uFF0C\u4E0D\u662F\u8BA9\u4EBA\u70B9\u8FDB\u6765\uFF08\u90A3\u662F\u5C01\u9762\u548C\u6807\u9898\u7684\u4E8B\uFF09\u3002\u5206\u6790\u65F6\u59CB\u7EC8\u56F4\u7ED5\u8FD9\u4E00\u4EF6\u4E8B\uFF1A\u8FD9\u6BB5\u5F00\u573A\u65E0\u8BBA\u662F\u8BDD\u8FD8\u662F\u753B\u9762\uFF0C\u600E\u4E48\u8BA9\u4EBA\u4E0D\u8D70\uFF1F\n\n---\n\n## \u8F93\u5165\n\n- \u89C6\u9891\u5F00\u573A\u5E27\u622A\u56FE\uFF080\u201315 \u79D2\uFF09\n- \u5B57\u5E55\u6587\u672C\uFF08\u6709\u5219\u7528\uFF0C\u6CA1\u6709\u5219\u53EA\u5206\u6790\u753B\u9762\u5E76\u6CE8\u660E\uFF09\n\n---\n\n## \u8F93\u51FA\u7ED3\u6784\n\n**Hook \u7C7B\u578B**\n\u4ECE\u4EE5\u4E0B\u9009\u4E00\u4E2A\u6216\u4E24\u4E2A\uFF0C\u4E00\u53E5\u8BDD\u8BF4\u4E3A\u4EC0\u4E48\uFF1A\n\u60AC\u5FF5\u578B / \u51B2\u7A81\u578B / \u4EF7\u503C\u627F\u8BFA\u578B / \u8EAB\u4EFD\u8BA4\u540C\u578B / \u89C6\u89C9\u51B2\u51FB\u578B / \u53CD\u5E38\u8BC6\u578B / \u6545\u4E8B\u5207\u5165\u578B\n\n**\u4ED6\u8BF4\u4E86\u4EC0\u4E48**\n\u5206\u6790\u5F00\u573A\u7684\u7B2C\u4E00\u53E5\u8BDD\u6216\u524D\u51E0\u53E5\u8BDD\uFF1A\u8FD9\u53E5\u8BDD\u5982\u4F55\u5728\u7B2C\u4E00\u65F6\u95F4\u62A2\u5360\u6CE8\u610F\u529B\uFF1F\u662F\u4EC0\u4E48\u53E5\u5F0F\u3001\u4EC0\u4E48\u627F\u8BFA\u3001\u4EC0\u4E48\u6570\u5B57\u3001\u8FD8\u662F\u4EC0\u4E48\u53CD\u5DEE\uFF1F\n\n**\u753B\u9762\u600E\u4E48\u914D\u5408**\n\u4E0D\u8981\u9010\u5E27\u63CF\u8FF0"\u7B2C\u4E00\u5E27\u662FX\uFF0C\u7B2C\u4E8C\u5E27\u662FY"\u3002\u5206\u6790\u753B\u9762\u5728\u505A\u4EC0\u4E48\u4E8B\u3001\u4E3A\u4EC0\u4E48\u8FD9\u6837\u505A\u80FD\u8BA9\u4EBA\u7559\u4E0B\u6765\u3002\u753B\u9762\u548C\u8BDD\u4E4B\u95F4\u662F\u653E\u5927\u5173\u7CFB\u8FD8\u662F\u8865\u5145\u5173\u7CFB\uFF1F\n\n**\u5982\u4F55\u590D\u5236**\n\u7ED9\u51FA\u53EF\u4EE5\u76F4\u63A5\u5957\u7528\u7684\u6A21\u677F\u6216\u64CD\u4F5C\u6B65\u9AA4\uFF0C\u5177\u4F53\u5230\u53E5\u5F0F\u3001\u753B\u9762\u5904\u7406\u65B9\u5F0F\u3002\u4E0D\u80FD\u5199"\u7528\u7C7B\u4F3C\u624B\u6CD5"\u3002\n\n**\u6211\u7684\u60F3\u6CD5**\n\uFF08\u7559\u7A7A\u2014\u2014\u8FD9\u662F\u4F60\u81EA\u5DF1\u586B\u7684\uFF09\n\n---\n\n## \u8BED\u6C14\u8981\u6C42\n\n\u50CF\u5728\u8DDF\u4EBA\u63CF\u8FF0\u4F60\u770B\u8FD9\u6BB5\u89C6\u9891\u7684\u611F\u53D7\uFF0C\u4E0D\u662F\u5728\u5199\u5206\u6790\u62A5\u544A\u3002\n\n\u4E0D\u8981\u7528\uFF1A\u89C6\u89C9\u5C42\u7EA7\u3001\u4FE1\u606F\u5BC6\u5EA6\u3001\u5F3A\u5316\u8BA4\u77E5\u3001\u9010\u5E27\u7F57\u5217\n\u8981\u505A\u5230\uFF1A\u77ED\u53E5\uFF0C\u53E3\u8BED\u611F\uFF0C\u6BCF\u9879 3-5 \u53E5\u4EE5\u5185\n\n'; // src/sops/zh/视频关键帧分析 SOP.md -var SOP_default2 = '---\ntitle: \u89C6\u9891\u5173\u952E\u5E27\u5206\u6790 SOP\ntype: note\npermalink: obsidian-sop/05-\u5BA1\u7F8E\u79EF\u7D2F/\u89C6\u9891\u5173\u952E\u5E27\u5206\u6790-sop\n---\n\n# \u89C6\u9891\u5173\u952E\u5E27\u5206\u6790 SOP\n\n> \u8F93\u5165\uFF1A\u89C6\u9891\u67D0\u6BB5\u65F6\u95F4\u5185\u7684\u591A\u5F20\u5173\u952E\u5E27\u622A\u56FE\uFF08\u6587\u4EF6\u540D\u524D\u7F00 `keyframe-`\uFF09\u3002\n> \u8F93\u51FA\uFF1A\u4E00\u4EFD\u52A8\u6548\u5206\u6790\u7B14\u8BB0\uFF0C\u4E24\u5C42\u5185\u5BB9\uFF1A\u8BBE\u8BA1\u7406\u89E3 + \u5B9E\u73B0\u7EA6\u5B9A\u3002\n> \u76EE\u7684\uFF1A\u65E2\u80FD\u4F5C\u4E3A\u6539\u7F16\u53C2\u8003\uFF08\u7406\u89E3\u6846\u67B6\u548C\u611F\u89C9\uFF09\uFF0C\u4E5F\u80FD\u76F4\u63A5\u4EA4\u7ED9 Agent \u5B9E\u73B0\u800C\u4E0D\u9700\u8981 back and forth\u3002\n\n---\n\n## \u8F93\u51FA\u683C\u5F0F\n\n```\n# \u52A8\u6548\u5206\u6790 \u2014 {\u89C6\u9891\u6807\u9898} \xB7 \u52A8\u6548{N} \xB7 {start}s\u2013{end}s\n\n## \u5173\u952E\u5E27\u63CF\u8FF0\n\uFF08\u6309\u5E27\u987A\u5E8F\u63CF\u8FF0\uFF1A\u753B\u9762\u91CC\u6709\u4EC0\u4E48\u5143\u7D20\uFF0C\u5E27\u4E0E\u5E27\u4E4B\u95F4\u53D1\u751F\u4E86\u4EC0\u4E48\u53D8\u5316\u3002\n \u5BA2\u89C2\u63CF\u8FF0\uFF0C\u4E0D\u89E3\u91CA\u539F\u56E0\u3002\uFF09\n\n## \u52A8\u6548\u903B\u8F91\n\uFF08\u8FD9\u4E9B\u53D8\u5316\u80CC\u540E\u7684\u8BBE\u8BA1\u610F\u56FE\uFF1A\u4E3A\u4EC0\u4E48\u8FD9\u6837\u8FD0\u52A8\u3001\u8282\u594F\u611F\u662F\u4EC0\u4E48\u3001\n \u60F3\u7ED9\u89C2\u4F17\u4EC0\u4E48\u611F\u53D7\u3002\u5199\u6E05\u695A"\u6846\u67B6"\uFF0C\u800C\u4E0D\u662F\u5177\u4F53\u6570\u5B57\u2014\u2014\n \u8FD9\u6837\u6539\u5185\u5BB9\u65F6\u6846\u67B6\u8FD8\u80FD\u590D\u7528\u3002\uFF09\n\n## \u5B9E\u73B0\u7EA6\u5B9A\n\uFF08\u4EE5\u4E0B\u662F\u6BCF\u6B21\u505A\u8FD9\u7C7B HTML \u52A8\u6548 overlay \u90FD\u5FC5\u987B\u9075\u5B88\u7684\u7EA6\u5B9A\uFF0C\n \u4E0D\u9700\u8981\u518D\u95EE\uFF0C\u4E0D\u9700\u8981\u518D\u6539\uFF1A\uFF09\n\n- \u80CC\u666F\u6C38\u8FDC\u900F\u660E\uFF0C\u7528\u4E8E\u53E0\u52A0\u5B9E\u62CD\u89C6\u9891\n- \u52A0 `?preview` URL \u53C2\u6570\u65F6\u663E\u793A\u6DF1\u7070\u80CC\u666F `#1a1a1a`\uFF0C\u65B9\u4FBF\u9884\u89C8\u767D\u8272\u5143\u7D20\uFF1B\u6E32\u67D3\u65F6\u53BB\u6389\u53C2\u6570\n- \u6587\u4EF6\u9876\u90E8\u56FA\u5B9A CONFIG \u5757\uFF1A\n DURATION = [\u89C6\u9891\u65F6\u957F ms] \u2190 \u63A7\u5236\u6E32\u67D3\u51FA\u6765\u7684\u89C6\u9891\u957F\u5EA6\n FPS = 30\n [\u5176\u4ED6\u5185\u5BB9\u53C2\u6570] \u2190 \u8FD9\u4E2A\u52A8\u6548\u7279\u6709\uFF0C\u5217\u5728\u4E0B\u9762\n- \u6587\u4EF6\u4FDD\u5B58\u5728 `Raw/Superpower/`\n\n## \u6211\u7684\u60F3\u6CD5\n\uFF08\u7559\u7A7A\uFF09\n```\n\n---\n\n## \u586B\u5199\u89C4\u5219\n\n- **\u5173\u952E\u5E27\u63CF\u8FF0**\uFF1A\u53EA\u63CF\u8FF0"\u770B\u5230\u4E86\u4EC0\u4E48"\uFF0C\u9010\u5E27\u5199\uFF0C\u5E27\u95F4\u53D8\u5316\u91CD\u70B9\u6807\u51FA\n- **\u52A8\u6548\u903B\u8F91**\uFF1A\u56DE\u7B54"\u4E3A\u4EC0\u4E48\u8FD9\u6837\u505A"\uFF0C\u805A\u7126\u8BBE\u8BA1\u6846\u67B6\uFF0C\u4E0D\u8981\u5199\u6B7B\u5177\u4F53\u6570\u503C\uFF08\u6570\u503C\u662F\u5185\u5BB9\u53C2\u6570\uFF0C\u4F1A\u53D8\uFF09\n- **\u5B9E\u73B0\u7EA6\u5B9A**\uFF1A\u8FD9\u4E00\u5757\u6BCF\u6B21\u683C\u5F0F\u5B8C\u5168\u4E00\u6837\uFF0C\u76F4\u63A5\u590D\u5236\u8FC7\u53BB\uFF0C\u4E0D\u8981\u4FEE\u6539\n\n---\n\n## \u591A\u5E27\u5904\u7406\n\n- \u6309\u65F6\u95F4\u987A\u5E8F\u63CF\u8FF0\uFF0C\u4E0D\u8981\u8DF3\u5E27\n- \u5E27\u95F4\u5DEE\u5F02\u5C0F\uFF08\u8FDE\u7EED\u52A8\u753B\uFF09\uFF1A\u91CD\u70B9\u5199\u6574\u4F53\u8FD0\u52A8\u611F\uFF0C\u4E0D\u8981\u9010\u5E27\u7F57\u5217\n- \u5E27\u95F4\u5DEE\u5F02\u5927\uFF08\u6709\u526A\u8F91\u5207\u6362\uFF09\uFF1A\u91CD\u70B9\u5206\u6790\u5207\u6362\u8282\u594F\u548C\u903B\u8F91\n'; +var SOP_default2 = '# \u89C6\u9891\u5173\u952E\u5E27\u5206\u6790 SOP\n\n> \u8F93\u5165\uFF1A\u89C6\u9891\u67D0\u6BB5\u65F6\u95F4\u5185\u7684\u591A\u5F20\u5173\u952E\u5E27\u622A\u56FE\uFF08\u6587\u4EF6\u540D\u524D\u7F00 `keyframe-`\uFF09\u3002\n> \u8F93\u51FA\uFF1A\u4E00\u4EFD\u52A8\u6548\u5206\u6790\u7B14\u8BB0\uFF0C\u4E24\u5C42\u5185\u5BB9\uFF1A\u8BBE\u8BA1\u7406\u89E3 + \u5B9E\u73B0\u7EA6\u5B9A\u3002\n> \u76EE\u7684\uFF1A\u65E2\u80FD\u4F5C\u4E3A\u6539\u7F16\u53C2\u8003\uFF08\u7406\u89E3\u6846\u67B6\u548C\u611F\u89C9\uFF09\uFF0C\u4E5F\u80FD\u76F4\u63A5\u4EA4\u7ED9 Agent \u5B9E\u73B0\u800C\u4E0D\u9700\u8981 back and forth\u3002\n\n---\n\n## \u8F93\u51FA\u683C\u5F0F\n\n```\n# \u52A8\u6548\u5206\u6790 \u2014 {\u89C6\u9891\u6807\u9898} \xB7 \u52A8\u6548{N} \xB7 {start}s\u2013{end}s\n\n## \u5173\u952E\u5E27\u63CF\u8FF0\n\uFF08\u6309\u5E27\u987A\u5E8F\u63CF\u8FF0\uFF1A\u753B\u9762\u91CC\u6709\u4EC0\u4E48\u5143\u7D20\uFF0C\u5E27\u4E0E\u5E27\u4E4B\u95F4\u53D1\u751F\u4E86\u4EC0\u4E48\u53D8\u5316\u3002\n \u5BA2\u89C2\u63CF\u8FF0\uFF0C\u4E0D\u89E3\u91CA\u539F\u56E0\u3002\uFF09\n\n## \u52A8\u6548\u903B\u8F91\n\uFF08\u8FD9\u4E9B\u53D8\u5316\u80CC\u540E\u7684\u8BBE\u8BA1\u610F\u56FE\uFF1A\u4E3A\u4EC0\u4E48\u8FD9\u6837\u8FD0\u52A8\u3001\u8282\u594F\u611F\u662F\u4EC0\u4E48\u3001\n \u60F3\u7ED9\u89C2\u4F17\u4EC0\u4E48\u611F\u53D7\u3002\u5199\u6E05\u695A"\u6846\u67B6"\uFF0C\u800C\u4E0D\u662F\u5177\u4F53\u6570\u5B57\u2014\u2014\n \u8FD9\u6837\u6539\u5185\u5BB9\u65F6\u6846\u67B6\u8FD8\u80FD\u590D\u7528\u3002\uFF09\n\n## \u5B9E\u73B0\u7EA6\u5B9A\n\uFF08\u4EE5\u4E0B\u662F\u6BCF\u6B21\u505A\u8FD9\u7C7B HTML \u52A8\u6548 overlay \u90FD\u5FC5\u987B\u9075\u5B88\u7684\u7EA6\u5B9A\uFF0C\n \u4E0D\u9700\u8981\u518D\u95EE\uFF0C\u4E0D\u9700\u8981\u518D\u6539\uFF1A\uFF09\n\n- \u80CC\u666F\u6C38\u8FDC\u900F\u660E\uFF0C\u7528\u4E8E\u53E0\u52A0\u5B9E\u62CD\u89C6\u9891\n- \u52A0 `?preview` URL \u53C2\u6570\u65F6\u663E\u793A\u6DF1\u7070\u80CC\u666F `#1a1a1a`\uFF0C\u65B9\u4FBF\u9884\u89C8\u767D\u8272\u5143\u7D20\uFF1B\u6E32\u67D3\u65F6\u53BB\u6389\u53C2\u6570\n- \u6587\u4EF6\u9876\u90E8\u56FA\u5B9A CONFIG \u5757\uFF1A\n DURATION = [\u89C6\u9891\u65F6\u957F ms] \u2190 \u63A7\u5236\u6E32\u67D3\u51FA\u6765\u7684\u89C6\u9891\u957F\u5EA6\n FPS = 30\n [\u5176\u4ED6\u5185\u5BB9\u53C2\u6570] \u2190 \u8FD9\u4E2A\u52A8\u6548\u7279\u6709\uFF0C\u5217\u5728\u4E0B\u9762\n- \u6587\u4EF6\u4FDD\u5B58\u5728 `Raw/Superpower/`\n\n## \u6211\u7684\u60F3\u6CD5\n\uFF08\u7559\u7A7A\uFF09\n```\n\n---\n\n## \u586B\u5199\u89C4\u5219\n\n- **\u5173\u952E\u5E27\u63CF\u8FF0**\uFF1A\u53EA\u63CF\u8FF0"\u770B\u5230\u4E86\u4EC0\u4E48"\uFF0C\u9010\u5E27\u5199\uFF0C\u5E27\u95F4\u53D8\u5316\u91CD\u70B9\u6807\u51FA\n- **\u52A8\u6548\u903B\u8F91**\uFF1A\u56DE\u7B54"\u4E3A\u4EC0\u4E48\u8FD9\u6837\u505A"\uFF0C\u805A\u7126\u8BBE\u8BA1\u6846\u67B6\uFF0C\u4E0D\u8981\u5199\u6B7B\u5177\u4F53\u6570\u503C\uFF08\u6570\u503C\u662F\u5185\u5BB9\u53C2\u6570\uFF0C\u4F1A\u53D8\uFF09\n- **\u5B9E\u73B0\u7EA6\u5B9A**\uFF1A\u8FD9\u4E00\u5757\u6BCF\u6B21\u683C\u5F0F\u5B8C\u5168\u4E00\u6837\uFF0C\u76F4\u63A5\u590D\u5236\u8FC7\u53BB\uFF0C\u4E0D\u8981\u4FEE\u6539\n\n---\n\n## \u591A\u5E27\u5904\u7406\n\n- \u6309\u65F6\u95F4\u987A\u5E8F\u63CF\u8FF0\uFF0C\u4E0D\u8981\u8DF3\u5E27\n- \u5E27\u95F4\u5DEE\u5F02\u5C0F\uFF08\u8FDE\u7EED\u52A8\u753B\uFF09\uFF1A\u91CD\u70B9\u5199\u6574\u4F53\u8FD0\u52A8\u611F\uFF0C\u4E0D\u8981\u9010\u5E27\u7F57\u5217\n- \u5E27\u95F4\u5DEE\u5F02\u5927\uFF08\u6709\u526A\u8F91\u5207\u6362\uFF09\uFF1A\u91CD\u70B9\u5206\u6790\u5207\u6362\u8282\u594F\u548C\u903B\u8F91\n'; // src/sops/en/Cover Analysis SOP.md -var Cover_Analysis_SOP_default = '---\ntitle: Cover Analysis SOP2\ntype: note\npermalink: obsidian-sop/02-writing-and-content/cover-analysis-sop2\n---\n\n# Cover and Title Analysis Prompt\n\n_After reading this document, output your analysis directly using the framework below._\n\n---\n\n## Output Structure\n\n**Description**\nState in one sentence what is in the cover: is there a person, what are they doing, are there props or large text, what does the background roughly look like.\n\n**Cover Hook**\nWhat reaction did the first glance trigger: curiosity, disbelief, feeling seen, wanting to know the answer. Explain what caused this reaction and why this reaction would make someone click in.\n\n**Title Hook** (required when there is a title, skip when there is no title)\nWhat is the first question that comes to mind right after reading the title. That question is the hook. Explain how the title creates this question and why you would want to click in to find the answer.\n\n**Collaboration** (required when there is a title, skip when there is no title)\nWhen the cover and title are combined, does the reason to click become stronger, or is either one enough on its own?\n\n**Special Treatment** (conditional, not every cover has this)\nTrigger condition: the cover has a special visual effect, noticeable font treatment, or a composition technique worth learning. A plain "face plus background plus text" does not trigger this.\nWhen triggered, write three points:\n- What this effect or technique is\n- Why it was done this way\n- How to replicate it\n\n---\n\n## Tone Requirements\n\nWrite like you are telling someone "look at this image, what do you feel after seeing it," not writing a design report.\n\nDo not use:\n- Phrases like "a natural visual entry point," "visual hierarchy," "forms a complete reason to click"\n- Long parallel sentences, AI summary tone\n\nDo this instead: 2 to 3 sentences per item, short, direct, conversational.\n'; +var Cover_Analysis_SOP_default = '# Cover and Title Analysis Prompt\n\n_After reading this document, output your analysis directly using the framework below._\n\n---\n\n## Output Structure\n\n**Description**\nState in one sentence what is in the cover: is there a person, what are they doing, are there props or large text, what does the background roughly look like.\n\n**Cover Hook**\nWhat reaction did the first glance trigger: curiosity, disbelief, feeling seen, wanting to know the answer. Explain what caused this reaction and why this reaction would make someone click in.\n\n**Title Hook** (required when there is a title, skip when there is no title)\nWhat is the first question that comes to mind right after reading the title. That question is the hook. Explain how the title creates this question and why you would want to click in to find the answer.\n\n**Collaboration** (required when there is a title, skip when there is no title)\nWhen the cover and title are combined, does the reason to click become stronger, or is either one enough on its own?\n\n**Special Treatment** (conditional, not every cover has this)\nTrigger condition: the cover has a special visual effect, noticeable font treatment, or a composition technique worth learning. A plain "face plus background plus text" does not trigger this.\nWhen triggered, write three points:\n- What this effect or technique is\n- Why it was done this way\n- How to replicate it\n\n---\n\n## Tone Requirements\n\nWrite like you are telling someone "look at this image, what do you feel after seeing it," not writing a design report.\n\nDo not use:\n- Phrases like "a natural visual entry point," "visual hierarchy," "forms a complete reason to click"\n- Long parallel sentences, AI summary tone\n\nDo this instead: 2 to 3 sentences per item, short, direct, conversational.\n'; // src/sops/en/Video Hook Analysis SOP.md -var Video_Hook_Analysis_SOP_default = `--- -title: Video Hook Analysis SOP -type: note -permalink: obsidian-sop/05-aesthetic-collection/video-hook-analysis-sop ---- - -# Video Hook Analysis Prompt +var Video_Hook_Analysis_SOP_default = `# Video Hook Analysis Prompt _After reading this document, output your Hook analysis directly using the framework below._ @@ -230,38 +243,44 @@ Do this instead: short sentences, conversational, no more than 3 to 5 sentences `; // src/sops/en/Video Keyframe Analysis SOP.md -var Video_Keyframe_Analysis_SOP_default = '---\ntitle: Video Keyframe Analysis SOP\ntype: note\npermalink: obsidian-sop/05-aesthetic-collection/video-keyframe-analysis-sop\n---\n\n# Video Keyframe Analysis SOP\n\n> Input: multiple keyframe screenshots from a segment of the video (filename prefix `keyframe-`).\n> Output: a motion analysis note with two layers of content: design understanding plus implementation conventions.\n> Purpose: can be used as a reference for adaptation (understanding the framework and feel), and can also be handed directly to an Agent to implement without back and forth.\n\n---\n\n## Output Format\n\n```\n# Motion Analysis - {Video Title} \xB7 Motion {N} \xB7 {start}s-{end}s\n\n## Keyframe Description\n(Describe in frame order: what elements are in the frame, what changes\n between frames. Describe objectively, do not explain the reason.)\n\n## Motion Logic\n(The design intent behind these changes: why it moves this way, what the\n rhythm is, what feeling it should give the viewer. Describe the "framework"\n clearly, not specific numbers, so the framework can be reused when the\n content changes.)\n\n## Implementation Conventions\n(The following are conventions that must be followed every time you build\n this type of HTML motion overlay. No need to ask again, no need to change\n them:)\n\n- The background is always transparent, for overlaying on top of the real footage\n- Adding the `?preview` URL parameter shows a dark gray background `#1a1a1a`, to make it easier to preview white elements; remove the parameter when rendering\n- A fixed CONFIG block at the top of the file:\n DURATION = [video duration in ms] \u2190 controls the length of the rendered video\n FPS = 30\n [other content parameters] \u2190 specific to this motion, listed below\n- File saved in `Raw/Superpower/`\n\n## My Thoughts\n(leave blank)\n```\n\n---\n\n## Filling Rules\n\n- **Keyframe Description**: only describe "what you see," write frame by frame, highlight the changes between frames\n- **Motion Logic**: answer "why it was done this way," focus on the design framework, do not hardcode specific values (values are content parameters and will change)\n- **Implementation Conventions**: this section is exactly the same format every time, copy it over directly, do not modify it\n\n---\n\n## Handling Multiple Frames\n\n- Describe in chronological order, do not skip frames\n- When the difference between frames is small (continuous animation): focus on the overall sense of motion, do not list frame by frame\n- When the difference between frames is large (there are cuts or transitions): focus on analyzing the rhythm and logic of the transitions\n'; +var Video_Keyframe_Analysis_SOP_default = '# Video Keyframe Analysis SOP\n\n> Input: multiple keyframe screenshots from a segment of the video (filename prefix `keyframe-`).\n> Output: a motion analysis note with two layers of content: design understanding plus implementation conventions.\n> Purpose: can be used as a reference for adaptation (understanding the framework and feel), and can also be handed directly to an Agent to implement without back and forth.\n\n---\n\n## Output Format\n\n```\n# Motion Analysis - {Video Title} \xB7 Motion {N} \xB7 {start}s-{end}s\n\n## Keyframe Description\n(Describe in frame order: what elements are in the frame, what changes\n between frames. Describe objectively, do not explain the reason.)\n\n## Motion Logic\n(The design intent behind these changes: why it moves this way, what the\n rhythm is, what feeling it should give the viewer. Describe the "framework"\n clearly, not specific numbers, so the framework can be reused when the\n content changes.)\n\n## Implementation Conventions\n(The following are conventions that must be followed every time you build\n this type of HTML motion overlay. No need to ask again, no need to change\n them:)\n\n- The background is always transparent, for overlaying on top of the real footage\n- Adding the `?preview` URL parameter shows a dark gray background `#1a1a1a`, to make it easier to preview white elements; remove the parameter when rendering\n- A fixed CONFIG block at the top of the file:\n DURATION = [video duration in ms] \u2190 controls the length of the rendered video\n FPS = 30\n [other content parameters] \u2190 specific to this motion, listed below\n- File saved in `Raw/Superpower/`\n\n## My Thoughts\n(leave blank)\n```\n\n---\n\n## Filling Rules\n\n- **Keyframe Description**: only describe "what you see," write frame by frame, highlight the changes between frames\n- **Motion Logic**: answer "why it was done this way," focus on the design framework, do not hardcode specific values (values are content parameters and will change)\n- **Implementation Conventions**: this section is exactly the same format every time, copy it over directly, do not modify it\n\n---\n\n## Handling Multiple Frames\n\n- Describe in chronological order, do not skip frames\n- When the difference between frames is small (continuous animation): focus on the overall sense of motion, do not list frame by frame\n- When the difference between frames is large (there are cuts or transitions): focus on analyzing the rhythm and logic of the transitions\n'; // src/bundled-sops.ts -var BUNDLED_SOPS = [ - { filename: "\u5C01\u9762\u62C6\u89E3\u5B66\u4E60 SOP.md", content: SOP_default }, - { filename: "\u89C6\u9891Hook\u5206\u6790 SOP.md", content: Hook_SOP_default }, - { filename: "\u89C6\u9891\u5173\u952E\u5E27\u5206\u6790 SOP.md", content: SOP_default2 }, - { filename: "Cover Analysis SOP.md", content: Cover_Analysis_SOP_default }, - { filename: "Video Hook Analysis SOP.md", content: Video_Hook_Analysis_SOP_default }, - { filename: "Video Keyframe Analysis SOP.md", content: Video_Keyframe_Analysis_SOP_default } -]; -async function installBundledSops(ops, baseFolder, sops = BUNDLED_SOPS) { - const folder = `${(baseFolder || "Clips").trim().replace(/\/+$/, "") || "Clips"}/SOPs`; - await ops.ensureFolder(folder); - const written = []; - const skipped = []; - for (const s of sops) { - const path = `${folder}/${s.filename}`; - if (ops.fileExists(path)) { - skipped.push(path); - continue; - } - await ops.create(path, s.content); - written.push(path); +var BUILTIN = { + thumbnail: { + zh: { filename: "\u5C01\u9762\u62C6\u89E3\u5B66\u4E60 SOP.md", content: SOP_default }, + en: { filename: "Cover Analysis SOP.md", content: Cover_Analysis_SOP_default } + }, + hook: { + zh: { filename: "\u89C6\u9891Hook\u5206\u6790 SOP.md", content: Hook_SOP_default }, + en: { filename: "Video Hook Analysis SOP.md", content: Video_Hook_Analysis_SOP_default } + }, + keyframe: { + zh: { filename: "\u89C6\u9891\u5173\u952E\u5E27\u5206\u6790 SOP.md", content: SOP_default2 }, + en: { filename: "Video Keyframe Analysis SOP.md", content: Video_Keyframe_Analysis_SOP_default } } - return { written, skipped }; +}; +function builtinSopFor(mode, language) { + var _a, _b; + return (_b = (_a = BUILTIN[mode]) == null ? void 0 : _a[language]) == null ? void 0 : _b.content; +} +async function exportBuiltinSop(ops, baseFolder, mode, language) { + var _a; + const sop = (_a = BUILTIN[mode]) == null ? void 0 : _a[language]; + if (!sop) return void 0; + const folder = `${(baseFolder || "Clips").trim().replace(/\/+$/, "") || "Clips"}/SOPs`; + const path = `${folder}/${sop.filename}`; + if (ops.fileExists(path)) return { path, existed: true }; + await ops.ensureFolder(folder); + await ops.create(path, sop.content); + return { path, existed: false }; } // src/settings.ts var DEFAULT_SETTINGS = { language: "en", baseFolder: "Clips", + useBuiltinSops: true, httpServer: { enabled: true, port: 17183 @@ -390,29 +409,70 @@ var VaultAutopilotSettingTab = class extends import_obsidian.PluginSettingTab { await this.plugin.saveSettings(); } })); - new import_obsidian.Setting(containerEl).setName(t("settings.installSops.name")).setDesc(t("settings.installSops.desc")).addButton((b) => b.setButtonText(t("settings.installSops.button")).onClick(async () => { - const { written, skipped } = await installBundledSops( - this.plugin.sopInstallOps(), - this.plugin.settings.baseFolder - ); - new import_obsidian.Notice(t("notice.sopsInstalled", { - count: written.length, - folder: `${this.plugin.settings.baseFolder}/SOPs`, - skipped: skipped.length - }), 8e3); - })); - const sopModes = [ - ["thumbnail", t("settings.sop.thumbnail")], - ["screenshot", t("settings.sop.screenshot")], - ["hook", t("settings.sop.hook")], - ["keyframe", t("settings.sop.keyframe")] + new import_obsidian.Setting(containerEl).setName(t("settings.sopHeading")).setHeading(); + const sopRows = [ + { mode: "thumbnail", name: t("settings.sopRow.cover"), hasBuiltin: true }, + { mode: "screenshot", name: t("settings.sopRow.screenshot"), hasBuiltin: false }, + { mode: "hook", name: t("settings.sopRow.hook"), hasBuiltin: true }, + { mode: "keyframe", name: t("settings.sopRow.keyframe"), hasBuiltin: true } ]; - for (const [mode, label] of sopModes) { - new import_obsidian.Setting(containerEl).setName(label).setDesc(t("settings.sop.desc")).addText((t2) => t2.setValue(this.plugin.settings.clipRules[mode].sopPath).onChange(async (v) => { - this.plugin.settings.clipRules[mode].sopPath = v.trim(); + const sopStateEls = {}; + const refreshSopStates = () => { + for (const row of sopRows) { + const el = sopStateEls[row.mode]; + if (!el) continue; + const custom = this.plugin.settings.clipRules[row.mode].sopPath.trim().length > 0; + let key; + let color; + if (custom) { + key = "settings.sopState.custom"; + color = "#b08c2e"; + } else if (!row.hasBuiltin) { + key = "settings.sopState.none"; + color = "var(--text-muted)"; + } else if (this.plugin.settings.useBuiltinSops) { + key = "settings.sopState.builtin"; + color = "var(--color-green, #3d8a5f)"; + } else { + key = "settings.sopState.off"; + color = "var(--text-muted)"; + } + el.textContent = t(key); + el.style.color = color; + } + }; + new import_obsidian.Setting(containerEl).setName(t("settings.useBuiltinSops.name")).setDesc(t("settings.useBuiltinSops.desc")).addToggle((tg) => tg.setValue(this.plugin.settings.useBuiltinSops).onChange(async (v) => { + this.plugin.settings.useBuiltinSops = v; + await this.plugin.saveSettings(); + refreshSopStates(); + })); + for (const row of sopRows) { + const setting = new import_obsidian.Setting(containerEl).setName(row.name).setDesc(t(row.hasBuiltin ? "settings.sopRow.desc" : "settings.sopRow.descNoBuiltin")).addText((txt) => txt.setPlaceholder(t(row.hasBuiltin ? "settings.sopRow.placeholder" : "settings.sopRow.placeholderNoBuiltin")).setValue(this.plugin.settings.clipRules[row.mode].sopPath).onChange(async (v) => { + this.plugin.settings.clipRules[row.mode].sopPath = v.trim(); await this.plugin.saveSettings(); + refreshSopStates(); })); + if (row.hasBuiltin) { + setting.addButton((b) => b.setButtonText(t("settings.sopCustomize")).onClick(async () => { + const r = await exportBuiltinSop( + this.plugin.sopInstallOps(), + this.plugin.settings.baseFolder, + row.mode, + this.plugin.settings.language + ); + if (!r) return; + this.plugin.settings.clipRules[row.mode].sopPath = r.path; + await this.plugin.saveSettings(); + new import_obsidian.Notice(t(r.existed ? "notice.sopExportExists" : "notice.sopExported", { path: r.path }), 8e3); + this.display(); + })); + } + const stateEl = setting.infoEl.createDiv(); + stateEl.style.fontSize = "12px"; + stateEl.style.marginTop = "4px"; + sopStateEls[row.mode] = stateEl; } + refreshSopStates(); } }; @@ -736,14 +796,14 @@ function mergeSection(existing, section) { } // src/clip-router.ts -async function routeClip(payload, clipRules, vaultOps) { - if (payload.mode === "thumbnail") return handleThumbnail(payload, clipRules.thumbnail, vaultOps); +async function routeClip(payload, clipRules, vaultOps, builtinSops = {}) { + if (payload.mode === "thumbnail") return handleThumbnail(payload, clipRules.thumbnail, vaultOps, builtinSops.thumbnail); if (payload.mode === "screenshot") { const normalized = normalizeScreenshot(payload); - return handleScreenshot(normalized, clipRules.screenshot, vaultOps, clipRules.thumbnail.outputFolder, clipRules.thumbnail.thumbnailFolder); + return handleScreenshot(normalized, clipRules.screenshot, vaultOps, clipRules.thumbnail.outputFolder, clipRules.thumbnail.thumbnailFolder, builtinSops.screenshot); } - if (payload.mode === "hook") return handleMultiFrame(payload, clipRules.hook, vaultOps, clipRules.thumbnail.outputFolder, clipRules.thumbnail.thumbnailFolder); - if (payload.mode === "keyframe") return handleMultiFrame(payload, clipRules.keyframe, vaultOps, clipRules.thumbnail.outputFolder, clipRules.thumbnail.thumbnailFolder); + if (payload.mode === "hook") return handleMultiFrame(payload, clipRules.hook, vaultOps, clipRules.thumbnail.outputFolder, clipRules.thumbnail.thumbnailFolder, builtinSops.hook); + if (payload.mode === "keyframe") return handleMultiFrame(payload, clipRules.keyframe, vaultOps, clipRules.thumbnail.outputFolder, clipRules.thumbnail.thumbnailFolder, builtinSops.keyframe); throw new Error("Unknown clip mode"); } function normalizeScreenshot(payload) { @@ -809,7 +869,8 @@ function buildScreenshotTemplate(payload, imageNames, sopContent) { parts.push(`---`, ``, `## ${t("note.notesHeading")}`, ``); return parts.join("\n"); } -async function handleScreenshot(payload, rule, vaultOps, searchFolder, assetFolder) { +async function handleScreenshot(payload, rule, vaultOps, searchFolder, assetFolder, builtinSop) { + var _a; if (!rule.outputFolder) { throw new Error(t("error.screenshotFolderNotConfigured")); } @@ -829,7 +890,7 @@ async function handleScreenshot(payload, rule, vaultOps, searchFolder, assetFold const existing = await findNoteByVideoId(key, searchFolder, vaultOps); const intoVideoNote = !!existing || extractVideoId(payload.url, void 0) != null; const meta = { platform: detectPlatform(payload.url), videoId: key, videoUrl: payload.url, title: payload.title }; - const sopContent = readSopSafely(rule.sopPath, vaultOps); + const sopContent = (_a = readSopSafely(rule.sopPath, vaultOps)) != null ? _a : builtinSop; if (intoVideoNote) { const r = await upsertVideoNote(meta, screenshotSection(imageNames, sopContent), vaultOps, searchFolder); await ensureCover(meta.videoId, payload.cover_url, vaultOps, assetFolder); @@ -852,7 +913,8 @@ async function findNoteByVideoId(videoId, folder, vaultOps) { } return null; } -async function handleThumbnail(payload, rule, vaultOps) { +async function handleThumbnail(payload, rule, vaultOps, builtinSop) { + var _a; if (!rule.outputFolder || !rule.thumbnailFolder) { throw new Error(t("error.videoFolderNotConfigured")); } @@ -861,7 +923,7 @@ async function handleThumbnail(payload, rule, vaultOps) { const thumbnailPath = `${rule.thumbnailFolder}/${thumbnailFile}`; const imgData = await vaultOps.downloadUrl(payload.thumbnail_url); await vaultOps.createBinary(thumbnailPath, imgData); - const sopContent = readSopSafely(rule.sopPath, vaultOps); + const sopContent = (_a = readSopSafely(rule.sopPath, vaultOps)) != null ? _a : builtinSop; const section = coverSection(thumbnailFile, sopContent); const meta = { platform: payload.platform, @@ -872,8 +934,8 @@ async function handleThumbnail(payload, rule, vaultOps) { }; return upsertVideoNote(meta, section, vaultOps, rule.outputFolder); } -async function handleMultiFrame(payload, rule, vaultOps, searchFolder, assetFolder) { - var _a, _b, _c; +async function handleMultiFrame(payload, rule, vaultOps, searchFolder, assetFolder, builtinSop) { + var _a, _b, _c, _d; const count = payload.frames_select && payload.frames_select > 0 ? payload.frames_select : (_a = rule.maxFrames) != null ? _a : 5; const sampled = count >= payload.frames.length ? payload.frames : sampleFrames(payload.frames, count); const stem = `${payload.mode}-${sanitize(payload.video_title)}-${Date.now()}`; @@ -895,11 +957,11 @@ async function handleMultiFrame(payload, rule, vaultOps, searchFolder, assetFold await vaultOps.createBinary(`${framesDir}/${name}`, bytes.buffer); frameNames.push(name); } - const sopContent = readSopSafely(rule.sopPath, vaultOps); + const sopContent = (_b = readSopSafely(rule.sopPath, vaultOps)) != null ? _b : builtinSop; let section; if (payload.mode === "hook") { section = hookSection( - { url: payload.url, platform, endSeconds: (_c = (_b = payload.time_range) == null ? void 0 : _b.end) != null ? _c : 15, frameNames, transcript: payload.transcript, aiResult: void 0 }, + { url: payload.url, platform, endSeconds: (_d = (_c = payload.time_range) == null ? void 0 : _c.end) != null ? _d : 15, frameNames, transcript: payload.transcript, aiResult: void 0 }, sopContent ); } else { @@ -970,6 +1032,17 @@ var VaultAutopilotPlugin = class extends import_obsidian2.Plugin { } } } + // Built-in SOP contents for modes whose sopPath is empty, respecting the + // master switch and the plugin language. + builtinSops() { + if (!this.settings.useBuiltinSops) return {}; + const lang = this.settings.language; + return { + thumbnail: builtinSopFor("thumbnail", lang), + hook: builtinSopFor("hook", lang), + keyframe: builtinSopFor("keyframe", lang) + }; + } // Narrow vault access for the settings tab's bundled-SOP installer. sopInstallOps() { return { @@ -1004,7 +1077,10 @@ var VaultAutopilotPlugin = class extends import_obsidian2.Plugin { create: async (p, content) => { await this.app.vault.create(p, content); }, - readFileSync: (p) => fs.readFileSync(p, "utf8"), + readFileSync: (p) => { + const abs = nodePath.isAbsolute(p) ? p : nodePath.join(this.app.vault.adapter.getBasePath(), p); + return fs.readFileSync(abs, "utf8"); + }, downloadUrl: async (url) => { const resp = await (0, import_obsidian2.requestUrl)({ url, method: "GET" }); return resp.arrayBuffer; @@ -1027,7 +1103,7 @@ var VaultAutopilotPlugin = class extends import_obsidian2.Plugin { this.server = createServer2( port, async (payload) => { - const { notePath, notice } = await routeClip(payload, this.settings.clipRules, vaultOps); + const { notePath, notice } = await routeClip(payload, this.settings.clipRules, vaultOps, this.builtinSops()); if (notePath) await this.maybeFirstSaveNotice(payload.mode, notePath); const obsidianUrl = notePath ? `obsidian://open?vault=${encodeURIComponent(this.app.vault.getName())}&file=${encodeURIComponent(notePath)}` : void 0; return { obsidianUrl, notice }; diff --git a/manifest.json b/manifest.json index 7df933a..c483de1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "vault-autopilot", "name": "Vault Autopilot", - "version": "0.1.0", + "version": "0.2.0", "minAppVersion": "1.4.0", "description": "Receive screenshots and video frames from the Obsidian Visual Clipper Chrome extension over a local port and write structured notes into your vault.", "author": "liyachen", diff --git a/versions.json b/versions.json index cdffaed..9a9de87 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,4 @@ { - "0.1.0": "1.4.0" + "0.1.0": "1.4.0", + "0.2.0": "1.4.0" }