auricui-skill 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -2
  3. package/bin/install.mjs +66 -0
  4. package/package.json +29 -4
  5. package/skill/SKILL.md +64 -0
  6. package/skill/agents/openai.yaml +4 -0
  7. package/skill/assets/examples/ApprovalCard.tsx +8 -0
  8. package/skill/assets/examples/BasicApp.tsx +38 -0
  9. package/skill/assets/examples/BriefingCard.tsx +8 -0
  10. package/skill/assets/examples/ChatControls.tsx +49 -0
  11. package/skill/assets/examples/ChatPanel.tsx +73 -0
  12. package/skill/assets/examples/Checkbox.tsx +13 -0
  13. package/skill/assets/examples/Combobox.tsx +14 -0
  14. package/skill/assets/examples/Drawer.tsx +10 -0
  15. package/skill/assets/examples/MemoryCard.tsx +9 -0
  16. package/skill/assets/examples/MessageList.tsx +8 -0
  17. package/skill/assets/examples/Theme.tsx +14 -0
  18. package/skill/assets/examples/ToolCall.tsx +8 -0
  19. package/skill/assets/examples/Tree.tsx +15 -0
  20. package/skill/assets/examples/examples.css +11 -0
  21. package/skill/assets/library/auricui-0.1.0-preview.1.tgz +0 -0
  22. package/skill/assets/library/auricui-0.1.0-preview.3.tgz +0 -0
  23. package/skill/assets/library/auricui-0.1.0-preview.4.tgz +0 -0
  24. package/skill/assets/upstream/LICENSE +21 -0
  25. package/skill/assets/upstream/apps/desktop/chat/src/App.tsx +1099 -0
  26. package/skill/assets/upstream/apps/desktop/chat/src/app-bridge.ts +154 -0
  27. package/skill/assets/upstream/apps/desktop/chat/src/chat.css +1812 -0
  28. package/skill/assets/upstream/apps/desktop/chat/src/components/AddSkill.tsx +223 -0
  29. package/skill/assets/upstream/apps/desktop/chat/src/components/AgentActivityRow.tsx +716 -0
  30. package/skill/assets/upstream/apps/desktop/chat/src/components/AgentIcons.tsx +46 -0
  31. package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentCard.tsx +34 -0
  32. package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentPreview.tsx +331 -0
  33. package/skill/assets/upstream/apps/desktop/chat/src/components/BalanceNotice.tsx +22 -0
  34. package/skill/assets/upstream/apps/desktop/chat/src/components/BrandLogo.tsx +35 -0
  35. package/skill/assets/upstream/apps/desktop/chat/src/components/ComposerCard.tsx +182 -0
  36. package/skill/assets/upstream/apps/desktop/chat/src/components/ConversationSessionBar.tsx +292 -0
  37. package/skill/assets/upstream/apps/desktop/chat/src/components/CopyDiagnostics.tsx +24 -0
  38. package/skill/assets/upstream/apps/desktop/chat/src/components/Divider.tsx +47 -0
  39. package/skill/assets/upstream/apps/desktop/chat/src/components/FileChangesList.tsx +119 -0
  40. package/skill/assets/upstream/apps/desktop/chat/src/components/Markdown.tsx +198 -0
  41. package/skill/assets/upstream/apps/desktop/chat/src/components/ModelPopover.tsx +280 -0
  42. package/skill/assets/upstream/apps/desktop/chat/src/components/NoticeCard.tsx +87 -0
  43. package/skill/assets/upstream/apps/desktop/chat/src/components/Popover.tsx +195 -0
  44. package/skill/assets/upstream/apps/desktop/chat/src/components/PreviewFrame.tsx +129 -0
  45. package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectConversation.tsx +852 -0
  46. package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectStarters.tsx +118 -0
  47. package/skill/assets/upstream/apps/desktop/chat/src/components/Tooltip.tsx +191 -0
  48. package/skill/assets/upstream/apps/desktop/chat/src/components/TurnLog.tsx +583 -0
  49. package/skill/assets/upstream/apps/desktop/chat/src/components/TurnQuestionCard.tsx +133 -0
  50. package/skill/assets/upstream/apps/desktop/chat/src/components/dock-pane-bar.ts +14 -0
  51. package/skill/assets/upstream/apps/desktop/chat/src/components/project-version.ts +37 -0
  52. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/MentionMenuPlugin.tsx +372 -0
  53. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptEditor.tsx +476 -0
  54. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptPill.tsx +399 -0
  55. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptReferenceNode.tsx +164 -0
  56. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/index.ts +31 -0
  57. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/mention-menu.ts +206 -0
  58. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/prompt-text.ts +171 -0
  59. package/skill/assets/upstream/apps/desktop/chat/src/components/useStickToBottom.ts +320 -0
  60. package/skill/assets/upstream/apps/desktop/chat/src/components/useTicker.ts +55 -0
  61. package/skill/assets/upstream/apps/desktop/chat/src/i18n/index.ts +42 -0
  62. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/en-US.ts +408 -0
  63. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/es.ts +407 -0
  64. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ja.ts +407 -0
  65. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ko.ts +403 -0
  66. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/words.ts +6 -0
  67. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/zh-CN.ts +407 -0
  68. package/skill/assets/upstream/apps/desktop/chat/src/i18n/lookup.ts +12 -0
  69. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-activity-runs.ts +154 -0
  70. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-menu-rows.ts +41 -0
  71. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-refs.ts +105 -0
  72. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-transcript.ts +440 -0
  73. package/skill/assets/upstream/apps/desktop/chat/src/lib/asset-lists.ts +75 -0
  74. package/skill/assets/upstream/apps/desktop/chat/src/lib/attachment-place.ts +17 -0
  75. package/skill/assets/upstream/apps/desktop/chat/src/lib/balance.ts +16 -0
  76. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-layer.ts +10 -0
  77. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-menu.ts +18 -0
  78. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-store.ts +101 -0
  79. package/skill/assets/upstream/apps/desktop/chat/src/lib/composer-attachments.ts +189 -0
  80. package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-attachments.ts +178 -0
  81. package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-bridge.ts +291 -0
  82. package/skill/assets/upstream/apps/desktop/chat/src/lib/developer.ts +39 -0
  83. package/skill/assets/upstream/apps/desktop/chat/src/lib/esc-stop.ts +23 -0
  84. package/skill/assets/upstream/apps/desktop/chat/src/lib/file-changes.ts +32 -0
  85. package/skill/assets/upstream/apps/desktop/chat/src/lib/live-activity.ts +20 -0
  86. package/skill/assets/upstream/apps/desktop/chat/src/lib/local-agent-run.ts +1174 -0
  87. package/skill/assets/upstream/apps/desktop/chat/src/lib/local-sessions.ts +25 -0
  88. package/skill/assets/upstream/apps/desktop/chat/src/lib/model-calls.ts +87 -0
  89. package/skill/assets/upstream/apps/desktop/chat/src/lib/pretty-json.ts +12 -0
  90. package/skill/assets/upstream/apps/desktop/chat/src/lib/preview-top.ts +15 -0
  91. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-resources.ts +66 -0
  92. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-sessions.ts +66 -0
  93. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-versions.ts +116 -0
  94. package/skill/assets/upstream/apps/desktop/chat/src/lib/ref-pictures.ts +119 -0
  95. package/skill/assets/upstream/apps/desktop/chat/src/lib/relative-time.ts +46 -0
  96. package/skill/assets/upstream/apps/desktop/chat/src/lib/skill-pages.ts +29 -0
  97. package/skill/assets/upstream/apps/desktop/chat/src/lib/step-file.ts +23 -0
  98. package/skill/assets/upstream/apps/desktop/chat/src/lib/structural-share.ts +52 -0
  99. package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-film.ts +44 -0
  100. package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-transfer.ts +40 -0
  101. package/skill/assets/upstream/apps/desktop/chat/src/lib/unprompted.ts +27 -0
  102. package/skill/assets/upstream/apps/desktop/chat/src/lib/upload-limits.ts +53 -0
  103. package/skill/assets/upstream/apps/desktop/chat/src/mount.tsx +19 -0
  104. package/skill/assets/upstream/apps/desktop/chat/src/state/local-agent-store.ts +296 -0
  105. package/skill/assets/upstream/apps/desktop/chat/src/studio-refs.ts +307 -0
  106. package/skill/assets/upstream/apps/desktop/package.json +51 -0
  107. package/skill/assets/upstream/apps/desktop/shell/bridge.ts +415 -0
  108. package/skill/assets/upstream/apps/desktop/shell/index.html +27 -0
  109. package/skill/assets/upstream/apps/desktop/shell/main.tsx +17 -0
  110. package/skill/assets/upstream/apps/desktop/shell/shell.css +17 -0
  111. package/skill/assets/upstream/apps/desktop/shell/vite.config.ts +65 -0
  112. package/skill/assets/upstream/apps/desktop/src/agent-definitions.mjs +31 -0
  113. package/skill/assets/upstream/packages/openfilm/LICENSE +21 -0
  114. package/skill/assets/upstream/packages/openfilm/package.json +88 -0
  115. package/skill/assets/upstream/packages/openfilm/src/film-doc.d.mts +125 -0
  116. package/skill/assets/upstream/packages/openfilm/src/film-doc.mjs +986 -0
  117. package/skill/assets/upstream/packages/openfilm/src/overrides.mjs +73 -0
  118. package/skill/assets/upstream/packages/openfilm/src/timeline.mjs +1280 -0
  119. package/skill/assets/upstream/packages/openfilm/studio/server/bridge.js +209 -0
  120. package/skill/assets/upstream/packages/openfilm/studio/server/film-subtitle.mjs +785 -0
  121. package/skill/assets/upstream/packages/openfilm/studio/server/ops.mjs +403 -0
  122. package/skill/assets/upstream/packages/openfilm/studio/server/stage.js +1649 -0
  123. package/skill/assets/upstream/packages/openfilm/studio/server/track-layout.mjs +43 -0
  124. package/skill/assets/upstream/packages/openfilm/studio/ui/src/App.tsx +156 -0
  125. package/skill/assets/upstream/packages/openfilm/studio/ui/src/api.ts +328 -0
  126. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/AudioMeters.tsx +188 -0
  127. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockThumbs.tsx +84 -0
  128. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockWaveform.tsx +201 -0
  129. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLoadingScreen.tsx +56 -0
  130. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLogo.tsx +25 -0
  131. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Card.tsx +89 -0
  132. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ColorPicker.tsx +204 -0
  133. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ConfirmDialog.tsx +96 -0
  134. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ContextMenu.tsx +422 -0
  135. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/DesktopDialog.tsx +271 -0
  136. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ExamplesGallery.tsx +372 -0
  137. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmFullscreenBar.tsx +147 -0
  138. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmInspector.tsx +2310 -0
  139. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmStageSelect.tsx +3296 -0
  140. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FocusNav.tsx +28 -0
  141. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FontFamilyPicker.tsx +293 -0
  142. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FramePicker.tsx +69 -0
  143. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/InspectorPanel.tsx +25 -0
  144. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Launcher.tsx +38 -0
  145. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LayersPanel.tsx +277 -0
  146. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LiveTime.tsx +101 -0
  147. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPool.tsx +1413 -0
  148. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPreview.tsx +188 -0
  149. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MenuSelect.tsx +161 -0
  150. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MgOutline.tsx +24 -0
  151. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/OperationHistory.tsx +184 -0
  152. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PaneDivider.tsx +292 -0
  153. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PasteAttrsDialog.tsx +69 -0
  154. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PlaybackMenu.tsx +110 -0
  155. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Popover.tsx +159 -0
  156. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectCover.tsx +60 -0
  157. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectHistoryControls.tsx +748 -0
  158. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectLayoutMenu.tsx +82 -0
  159. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectMenu.tsx +111 -0
  160. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectSources.tsx +587 -0
  161. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectView.tsx +3222 -0
  162. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsLibrary.tsx +567 -0
  163. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsView.tsx +1593 -0
  164. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PropertiesSection.tsx +418 -0
  165. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ResourceInspector.tsx +100 -0
  166. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SelectionToolbar.tsx +157 -0
  167. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SkillsLibrary.tsx +504 -0
  168. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SourcePreview.tsx +464 -0
  169. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SpendAsks.tsx +88 -0
  170. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageMaskOverlay.tsx +299 -0
  171. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageTransformFrame.tsx +246 -0
  172. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.module.css +46 -0
  173. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.tsx +884 -0
  174. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleOverlay.tsx +148 -0
  175. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleRow.tsx +419 -0
  176. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TabStrip.tsx +188 -0
  177. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Timeline.tsx +4911 -0
  178. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TimelineMarkers.tsx +329 -0
  179. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Toast.tsx +55 -0
  180. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Tooltip.tsx +193 -0
  181. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/UpdateButton.tsx +104 -0
  182. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ViewerBar.tsx +98 -0
  183. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/WhatsNew.tsx +147 -0
  184. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/dock-pane-bar.ts +16 -0
  185. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportCenter.tsx +1502 -0
  186. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportTray.tsx +284 -0
  187. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/film-stage-hit.ts +18 -0
  188. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-fields.tsx +863 -0
  189. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-look.tsx +1357 -0
  190. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/controls-bar.tsx +397 -0
  191. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/transport.tsx +294 -0
  192. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/replace-drop.ts +20 -0
  193. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/AgentsSection.tsx +613 -0
  194. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/CustomServices.tsx +104 -0
  195. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/DiagnosticsGroup.tsx +202 -0
  196. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ModelsSection.tsx +179 -0
  197. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProviderMark.tsx +24 -0
  198. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProvidersSection.tsx +177 -0
  199. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsModal.tsx +511 -0
  200. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsSelect.tsx +84 -0
  201. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ShortcutsSection.tsx +291 -0
  202. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/StorageGroup.tsx +119 -0
  203. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/theme.ts +47 -0
  204. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ui.tsx +139 -0
  205. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/use-settings.ts +10 -0
  206. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/stage-menu.tsx +283 -0
  207. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/timeline-head-extras.tsx +91 -0
  208. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/typing-target.ts +45 -0
  209. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-editor-tools.tsx +408 -0
  210. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-page-changes.ts +48 -0
  211. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-project-history.ts +113 -0
  212. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/useBlockDrag.ts +639 -0
  213. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/Preview.tsx +426 -0
  214. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/player.ts +558 -0
  215. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/preview-frames.ts +36 -0
  216. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/steps.ts +36 -0
  217. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/undo.ts +156 -0
  218. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-film.ts +212 -0
  219. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-preview-renders.ts +89 -0
  220. package/skill/assets/upstream/packages/openfilm/studio/ui/src/globals.css +559 -0
  221. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/en.ts +1901 -0
  222. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/es.ts +1900 -0
  223. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/index.ts +130 -0
  224. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ja.ts +1900 -0
  225. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ko.ts +1896 -0
  226. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/words.ts +6 -0
  227. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/zh-CN.ts +1901 -0
  228. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/agent-row.ts +21 -0
  229. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/asset-timeline.ts +469 -0
  230. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/audio-levels.ts +60 -0
  231. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/batch-map.ts +28 -0
  232. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/browser.ts +10 -0
  233. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/canvas-fit.ts +116 -0
  234. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/chat-marks.ts +114 -0
  235. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-attrs.ts +102 -0
  236. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-fade.ts +70 -0
  237. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-look.ts +520 -0
  238. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-mask.ts +536 -0
  239. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-replace.ts +60 -0
  240. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/component-props.ts +128 -0
  241. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/context-length.ts +12 -0
  242. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/desktop-build.ts +44 -0
  243. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/diagnostics.ts +68 -0
  244. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/double-press.ts +22 -0
  245. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/dropped-overrides.ts +139 -0
  246. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-jobs.ts +191 -0
  247. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-spec.ts +261 -0
  248. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/field-input.ts +155 -0
  249. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-clip-edits.ts +82 -0
  250. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-frame.ts +134 -0
  251. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-overrides.ts +107 -0
  252. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-shape.ts +172 -0
  253. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film.ts +80 -0
  254. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-menu.ts +236 -0
  255. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-styles.ts +129 -0
  256. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/freeze-frame.ts +49 -0
  257. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/host.ts +438 -0
  258. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/inspect-scrub.ts +26 -0
  259. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap-store.ts +74 -0
  260. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap.ts +529 -0
  261. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/layers-tree.ts +133 -0
  262. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/look.ts +83 -0
  263. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/loudness.ts +58 -0
  264. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/markers.ts +152 -0
  265. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-bar.ts +58 -0
  266. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-folders.ts +45 -0
  267. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-place.ts +102 -0
  268. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-queue.ts +107 -0
  269. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-selection.ts +64 -0
  270. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/menu-position.ts +77 -0
  271. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mg-transform.ts +111 -0
  272. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mixed.ts +3 -0
  273. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/page-changes.ts +45 -0
  274. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/picture-files.ts +19 -0
  275. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-prefs.ts +46 -0
  276. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-renders.ts +71 -0
  277. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-delete.ts +20 -0
  278. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-folders.ts +144 -0
  279. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-history.ts +188 -0
  280. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-sources.ts +283 -0
  281. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/projects-page.ts +40 -0
  282. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/provider-marks.ts +35 -0
  283. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/providers.ts +163 -0
  284. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/resource-drag.ts +131 -0
  285. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/retry-image.ts +17 -0
  286. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved-insert.ts +40 -0
  287. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved.ts +125 -0
  288. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/selection-toolbar.ts +158 -0
  289. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/shortcut-hint.ts +21 -0
  290. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/similar-layers.ts +46 -0
  291. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/solo.ts +19 -0
  292. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-clip.ts +60 -0
  293. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-crop.ts +232 -0
  294. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-gesture.ts +277 -0
  295. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-layers.ts +71 -0
  296. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-mask.ts +176 -0
  297. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-measure.ts +53 -0
  298. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-types.ts +155 -0
  299. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-refs.ts +248 -0
  300. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-version.ts +41 -0
  301. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitle-cues.ts +238 -0
  302. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitles.ts +263 -0
  303. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/tab-menu.ts +6 -0
  304. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/template-controls.ts +131 -0
  305. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/theme-changes.ts +31 -0
  306. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cache.ts +63 -0
  307. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cell.ts +8 -0
  308. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-frame.ts +76 -0
  309. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-pick.ts +42 -0
  310. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timecode.ts +161 -0
  311. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-clipboard.ts +279 -0
  312. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-drag.ts +589 -0
  313. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-edit.ts +1138 -0
  314. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-keys.ts +63 -0
  315. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-layout.ts +1038 -0
  316. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-nav.ts +160 -0
  317. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-prefs.ts +73 -0
  318. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-split.ts +40 -0
  319. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-view.ts +105 -0
  320. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/title.ts +39 -0
  321. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-heights.ts +55 -0
  322. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-lock.ts +27 -0
  323. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/transitions.ts +459 -0
  324. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/type-metrics.ts +83 -0
  325. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-film-subtitles.ts +111 -0
  326. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-fonts.ts +181 -0
  327. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-loaded-thumbs.ts +91 -0
  328. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-edits.ts +134 -0
  329. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-font.ts +51 -0
  330. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-style.ts +68 -0
  331. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/waveform-columns.ts +48 -0
  332. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/web-update.ts +41 -0
  333. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/whats-new.ts +401 -0
  334. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/workspace-resources.ts +373 -0
  335. package/skill/assets/upstream/packages/openfilm/studio/ui/src/main.tsx +10 -0
  336. package/skill/assets/upstream/packages/openfilm/studio/ui/vite.config.ts +12 -0
  337. package/skill/assets/upstream/packages/shared/package.json +45 -0
  338. package/skill/assets/upstream/packages/shared/src/agent-activity.ts +263 -0
  339. package/skill/assets/upstream/packages/shared/src/contract.ts +285 -0
  340. package/skill/assets/upstream/packages/shared/src/index.ts +17 -0
  341. package/skill/assets/upstream/packages/shared/src/markdown-blocks.ts +252 -0
  342. package/skill/assets/upstream/packages/shared/src/modality.ts +4 -0
  343. package/skill/assets/upstream/packages/shared/src/prompt-reference.ts +113 -0
  344. package/skill/assets/upstream/packages/shared/src/title.ts +76 -0
  345. package/skill/assets/upstream/packages/shared/src/turn.ts +68 -0
  346. package/skill/assets/upstream/packages/shared/src/video.ts +26 -0
  347. package/skill/references/agent-components.md +33 -0
  348. package/skill/references/api-index.json +2693 -0
  349. package/skill/references/chat-components.md +65 -0
  350. package/skill/references/components.md +23 -0
  351. package/skill/references/core-components.md +44 -0
  352. package/skill/references/dependencies.md +22 -0
  353. package/skill/references/esm-package.md +48 -0
  354. package/skill/references/integration.md +43 -0
  355. package/skill/references/library-manifest.json +12 -0
  356. package/skill/references/source-manifest.json +325 -0
  357. package/skill/references/studio-components.md +24 -0
  358. package/skill/references/theme-motion.md +49 -0
  359. package/skill/references/troubleshooting.md +27 -0
  360. package/skill/references/verification.md +13 -0
  361. package/skill/scripts/check_dependencies.py +109 -0
  362. package/skill/scripts/inspect_api.py +60 -0
@@ -0,0 +1,511 @@
1
+ /**
2
+ * Settings, opened from the logo at the top left: a search and the sections in groups on the left, the section on the
3
+ * right as a page of titled groups. General (the theme and the language), Agents (in an app with a chat: the agents
4
+ * it can use), Providers (the person's own keys for media services), Models (which provider and model makes each kind
5
+ * of media), the keyboard shortcuts, Advanced and Developer; the search finds a section by any phrase on it. At the
6
+ * foot of the nav, quietly, where OpenFilm lives: its code, the examples and the website.
7
+ */
8
+ import React from 'react';
9
+ import { createPortal } from 'react-dom';
10
+ import { X, Settings2, Keyboard, Sun, Moon, Monitor, Plug, Boxes, Bot, Search, SquareTerminal, Loader2, SlidersHorizontal } from 'lucide-react';
11
+ import { LANGUAGES, phrasesUnder, useLanguage, useLanguageChoice, useT, type LanguageChoice } from '@/i18n';
12
+ import { studioHost, type HostDeveloper, type HostUpdateState } from '@/lib/host';
13
+ import { showWhatsNew } from '@/lib/whats-new';
14
+ import { usePreviewPrefs, writePreviewPref } from '@/lib/preview-prefs';
15
+ import { hasNotes, newestVersion } from '../WhatsNew';
16
+ import { useWebUpdate } from '../UpdateButton';
17
+ import { api, type StudioPrefs, type StudioVersion } from '@/api';
18
+ import { AgentsSection } from './AgentsSection';
19
+ import { DiagnosticsGroup } from './DiagnosticsGroup';
20
+ import { ModelsSection } from './ModelsSection';
21
+ import { ProvidersSection } from './ProvidersSection';
22
+ import { SettingsSelect } from './SettingsSelect';
23
+ import { ShortcutsSection } from './ShortcutsSection';
24
+ import { SettingsButton, SettingsGroup, SettingsItem, SettingsPage, SettingsSegmented, SettingsSwitch } from './ui';
25
+ import { useThemeMode, type ThemeMode } from './theme';
26
+ import { StorageGroup } from './StorageGroup';
27
+
28
+ export type SettingsSection = 'general' | 'agents' | 'providers' | 'models' | 'developer' | 'shortcuts' | 'advanced';
29
+
30
+ /* the nav, in groups: what OpenFilm does, then help (Agents only inside an app with a chat: lib/host.ts `agents`;
31
+ Advanced for what is already set for the best experience; Developer always, for its diagnostics) */
32
+ const NAV: Array<{ group: 'settings' | 'help'; sections: SettingsSection[] }> = [
33
+ { group: 'settings', sections: ['general', ...(studioHost?.agents ? ['agents' as const] : []), 'providers', 'models'] },
34
+ { group: 'help', sections: ['shortcuts', 'advanced', 'developer'] },
35
+ ];
36
+
37
+ /** where OpenFilm lives, linked at the foot of the nav */
38
+ const LINKS = [
39
+ { label: 'settings.links.github', href: 'https://github.com/openfilm/openfilm' },
40
+ { label: 'settings.links.examples', href: 'https://github.com/openfilm/examples' },
41
+ { label: 'settings.links.site', href: 'https://openfilm.dev' },
42
+ ];
43
+
44
+ const SECTION_ICON: Record<SettingsSection, React.ReactNode> = {
45
+ general: <Settings2 size={18} strokeWidth={1.75} />,
46
+ agents: <Bot size={18} strokeWidth={1.75} />,
47
+ providers: <Plug size={18} strokeWidth={1.75} />,
48
+ models: <Boxes size={18} strokeWidth={1.75} />,
49
+ developer: <SquareTerminal size={18} strokeWidth={1.75} />,
50
+ shortcuts: <Keyboard size={18} strokeWidth={1.75} />,
51
+ advanced: <SlidersHorizontal size={18} strokeWidth={1.75} />,
52
+ };
53
+
54
+ /** what a search through each section reads: its name and every phrase it shows */
55
+ const SECTION_WORDS: Record<SettingsSection, string | string[]> = {
56
+ general: ['settings.appearance', 'settings.storage'],
57
+ agents: 'settings.agents',
58
+ providers: 'settings.providers',
59
+ models: 'settings.models',
60
+ developer: ['settings.developer', 'settings.report'],
61
+ shortcuts: 'settings.shortcuts',
62
+ advanced: ['settings.advanced', 'settings.playback'],
63
+ };
64
+
65
+ export interface SettingsModalProps {
66
+ open: boolean;
67
+ onClose: () => void;
68
+ /** Where it opens (General when not given). */
69
+ section?: SettingsSection;
70
+ }
71
+
72
+ /** Opens on General each time, or on the section asked for. */
73
+ export function SettingsModal({ open, onClose, section: opening }: SettingsModalProps) {
74
+ const t = useT();
75
+ const language = useLanguage();
76
+ const [section, setSection] = React.useState<SettingsSection>('general');
77
+ const [query, setQuery] = React.useState('');
78
+ const [shown, setShown] = React.useState(false);
79
+ const dialog = React.useRef<HTMLDivElement>(null);
80
+ const search = React.useRef<HTMLInputElement>(null);
81
+ /* the latest onClose without re-running the effect below: the page re-renders every frame while it plays, and
82
+ re-running it would fade the dialog out and in and take the focus back each time */
83
+ const close = React.useRef(onClose);
84
+ close.current = onClose;
85
+
86
+ React.useEffect(() => { if (open) { setSection(opening ?? 'general'); setQuery(''); } }, [open, opening]);
87
+
88
+ /* the sections a search finds: by their names and by every phrase on them */
89
+ const words = React.useMemo(() => Object.fromEntries((Object.keys(SECTION_WORDS) as SettingsSection[]).map((id) => [
90
+ id, [t(`settings.sections.${id}`), ...[SECTION_WORDS[id]].flat().flatMap((under) => phrasesUnder(under, language))].join('\n').toLowerCase(),
91
+ ])) as Record<SettingsSection, string>, [t, language]);
92
+ const needle = query.trim().toLowerCase();
93
+ const nav = NAV.map((group) => ({ ...group, sections: group.sections.filter((id) => !needle || words[id].includes(needle)) }))
94
+ .filter((group) => group.sections.length > 0);
95
+ const found = nav.flatMap((group) => group.sections);
96
+ const showing: SettingsSection | null = found.includes(section) ? section : found[0] ?? null;
97
+
98
+ React.useEffect(() => {
99
+ if (!open) return;
100
+ const previousFocus = document.activeElement as HTMLElement | null;
101
+ const onKey = (e: KeyboardEvent) => {
102
+ if (e.defaultPrevented) return;
103
+ if (e.key === 'Escape') close.current();
104
+ if (e.key !== 'Tab' || !dialog.current?.contains(document.activeElement)) return;
105
+ const focusable = Array.from(dialog.current.querySelectorAll<HTMLElement>('button:not(:disabled), a[href], input:not(:disabled), select:not(:disabled), [tabindex="0"]'))
106
+ .filter((element) => element.getClientRects().length > 0);
107
+ const first = focusable[0];
108
+ const last = focusable[focusable.length - 1];
109
+ if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last?.focus(); }
110
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first?.focus(); }
111
+ };
112
+ window.addEventListener('keydown', onKey);
113
+ const prev = document.body.style.overflow;
114
+ document.body.style.overflow = 'hidden';
115
+ const r = requestAnimationFrame(() => { setShown(true); search.current?.focus(); });
116
+ return () => {
117
+ window.removeEventListener('keydown', onKey);
118
+ document.body.style.overflow = prev;
119
+ cancelAnimationFrame(r);
120
+ setShown(false);
121
+ if (previousFocus?.isConnected) previousFocus.focus();
122
+ };
123
+ }, [open]);
124
+
125
+ if (!open) return null;
126
+
127
+ return createPortal(
128
+ <div
129
+ className="fixed inset-0 z-[10050] grid place-items-center p-6"
130
+ style={{ background: 'rgba(0,0,0,0.5)', opacity: shown ? 1 : 0, transition: 'opacity 160ms ease' }}
131
+ onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}
132
+ >
133
+ <div
134
+ ref={dialog}
135
+ role="dialog"
136
+ aria-modal="true"
137
+ aria-labelledby="settings-title"
138
+ className="relative flex h-[min(800px,90vh)] w-full max-w-[1120px] overflow-hidden rounded-[14px] border border-[var(--border)] bg-[var(--dock-pane)] shadow-[0_24px_64px_-12px_rgba(0,0,0,0.5),0_4px_16px_rgba(0,0,0,0.14)]"
139
+ style={{ opacity: shown ? 1 : 0, transform: shown ? 'scale(1)' : 'scale(0.98)', transition: 'opacity 160ms ease, transform 200ms ease' }}
140
+ >
141
+ <nav aria-label={t('settings.title')} className="flex w-[264px] shrink-0 flex-col overflow-y-auto border-r border-[var(--border)] px-3 pb-4 pt-3">
142
+ <label className="mb-3 flex h-10 shrink-0 items-center gap-2.5 rounded-[9px] border border-[var(--border)] px-3 text-[var(--text-muted)] transition focus-within:border-[var(--text-muted)]">
143
+ <Search size={17} strokeWidth={1.75} className="shrink-0" aria-hidden />
144
+ <input
145
+ ref={search}
146
+ value={query}
147
+ onChange={(e) => setQuery(e.target.value)}
148
+ placeholder={t('settings.search')}
149
+ aria-label={t('settings.search')}
150
+ className="min-w-0 flex-1 bg-transparent text-[15px] text-[var(--text)] outline-none placeholder:text-[var(--text-faint)]"
151
+ />
152
+ </label>
153
+ {nav.map((group) => (
154
+ <div key={group.group} className="mt-3 flex flex-col gap-0.5">
155
+ <div className="px-2.5 pb-1.5 text-[13px] text-[var(--text-faint)]">{t(group.group === 'help' ? 'settings.groups.help' : 'settings.title')}</div>
156
+ {group.sections.map((id) => {
157
+ const active = showing === id;
158
+ return (
159
+ <button
160
+ key={id}
161
+ type="button"
162
+ aria-current={active ? 'page' : undefined}
163
+ onClick={() => setSection(id)}
164
+ className={`flex h-10 min-w-0 items-center gap-3 rounded-[8px] px-2.5 text-left text-[15px] transition ${
165
+ active ? 'bg-[var(--bg-active)] text-[var(--text)]' : 'text-[var(--text-dim)] hover:bg-[var(--bg-hover)] hover:text-[var(--text)]'
166
+ }`}
167
+ >
168
+ <span className={`shrink-0 ${active ? 'text-[var(--text)]' : 'text-[var(--text-muted)]'}`}>{SECTION_ICON[id]}</span>
169
+ <span className="min-w-0 truncate">{t(`settings.sections.${id}`)}</span>
170
+ </button>
171
+ );
172
+ })}
173
+ </div>
174
+ ))}
175
+ {found.length === 0 ? <p className="px-2.5 pt-4 text-[14px] text-[var(--text-faint)]">{t('settings.noResults')}</p> : null}
176
+ <div className="mt-auto flex flex-wrap gap-x-4 gap-y-1 px-2.5 pt-6 text-[13px] text-[var(--text-faint)]">
177
+ {LINKS.map((link) => (
178
+ <a key={link.href} href={link.href} target="_blank" rel="noreferrer" className="rounded-[4px] transition hover:text-[var(--text)]">{t(link.label)}</a>
179
+ ))}
180
+ </div>
181
+ </nav>
182
+
183
+ <div className="relative flex min-w-0 flex-1 flex-col">
184
+ <button type="button" onClick={onClose} aria-label={t('settings.done')}
185
+ className="absolute right-4 top-4 z-10 flex h-9 w-9 items-center justify-center rounded-[8px] text-[var(--text-muted)] transition hover:bg-[var(--bg-hover)] hover:text-[var(--text)]">
186
+ <X size={19} strokeWidth={1.75} />
187
+ </button>
188
+ <div key={showing ?? 'none'} className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-10 pb-12 pt-9">
189
+ {showing ? <h2 id="settings-title" className="mb-7 text-[22px] font-semibold tracking-[-0.015em] text-[var(--text)]">{t(`settings.sections.${showing}`)}</h2> : null}
190
+ {showing === 'general' && <GeneralSection />}
191
+ {showing === 'agents' && <AgentsSection />}
192
+ {showing === 'providers' && <ProvidersSection />}
193
+ {showing === 'models' && <ModelsSection onConnect={() => { setQuery(''); setSection('providers'); }} />}
194
+ {showing === 'developer' && <DeveloperSection />}
195
+ {showing === 'shortcuts' && <ShortcutsSection />}
196
+ {showing === 'advanced' && <AdvancedSection />}
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </div>,
201
+ document.body,
202
+ );
203
+ }
204
+
205
+ function GeneralSection() {
206
+ const t = useT();
207
+ const [mode, setMode] = useThemeMode();
208
+ const [language, setLanguage] = useLanguageChoice();
209
+
210
+ return (
211
+ <SettingsPage>
212
+ <SettingsGroup title={t('settings.appearance.group')}>
213
+ <SettingsItem
214
+ title={t('settings.appearance.theme')}
215
+ description={t('settings.appearance.themeHint')}
216
+ control={(
217
+ <SettingsSegmented<ThemeMode>
218
+ label={t('settings.appearance.theme')}
219
+ value={mode}
220
+ onChange={setMode}
221
+ options={[
222
+ { value: 'system', label: t('settings.appearance.system'), icon: <Monitor size={15} /> },
223
+ { value: 'light', label: t('settings.appearance.light'), icon: <Sun size={15} /> },
224
+ { value: 'dark', label: t('settings.appearance.dark'), icon: <Moon size={15} /> },
225
+ ]}
226
+ />
227
+ )}
228
+ />
229
+ <SettingsItem
230
+ title={t('settings.appearance.language')}
231
+ description={t('settings.appearance.languageHint')}
232
+ control={(
233
+ /* each language named in itself, so a person who cannot read the one shown still finds theirs */
234
+ <SettingsSelect<LanguageChoice>
235
+ label={t('settings.appearance.language')}
236
+ value={language}
237
+ onChange={setLanguage}
238
+ options={[
239
+ { value: 'system', label: t('settings.appearance.languageSystem') },
240
+ ...LANGUAGES.map((l) => ({ value: l.code, label: l.name })),
241
+ ]}
242
+ />
243
+ )}
244
+ />
245
+ </SettingsGroup>
246
+ <StorageGroup />
247
+ {studioHost?.update?.onState ? <AppAboutGroup /> : <AboutGroup />}
248
+ </SettingsPage>
249
+ );
250
+ }
251
+
252
+ /**
253
+ * How the preview plays heavy footage (studio/server/proxies.mjs): from a lighter copy made in the background (a
254
+ * proxy), and how tall that copy is; whether pages draw at the viewer's size, and whether stretches that play late are
255
+ * rendered to play smoothly (lib/preview-prefs). Exports always read the originals and draw at full size.
256
+ */
257
+ function PlaybackGroup() {
258
+ const t = useT();
259
+ const { fitPages, renders } = usePreviewPrefs();
260
+ const [prefs, setPrefs] = React.useState<StudioPrefs | null>(null);
261
+ React.useEffect(() => { void api.prefs().then(setPrefs).catch(() => {}); }, []);
262
+ if (!prefs) return null;
263
+ const change = (proxies: Partial<StudioPrefs['proxies']>) => {
264
+ setPrefs({ ...prefs, proxies: { ...prefs.proxies, ...proxies } });
265
+ void api.setPrefs({ proxies }).then(setPrefs).catch(() => {});
266
+ };
267
+ return (
268
+ <SettingsGroup title={t('settings.playback.group')}>
269
+ <SettingsItem
270
+ title={t('settings.playback.proxies')}
271
+ description={t('settings.playback.proxiesHint')}
272
+ control={<SettingsSwitch checked={prefs.proxies.on} onChange={(on) => change({ on })} label={t('settings.playback.proxies')} />}
273
+ />
274
+ {prefs.proxies.on ? (
275
+ <SettingsItem
276
+ title={t('settings.playback.proxySize')}
277
+ description={t('settings.playback.proxySizeHint')}
278
+ control={(
279
+ <SettingsSegmented<'540' | '720' | '1080'>
280
+ label={t('settings.playback.proxySize')}
281
+ value={String(prefs.proxies.height) as '540' | '720' | '1080'}
282
+ onChange={(v) => change({ height: Number(v) as 540 | 720 | 1080 })}
283
+ options={[{ value: '540', label: '540p' }, { value: '720', label: '720p' }, { value: '1080', label: '1080p' }]}
284
+ />
285
+ )}
286
+ />
287
+ ) : null}
288
+ <SettingsItem
289
+ title={t('settings.advanced.fitPages')}
290
+ description={t('settings.advanced.fitPagesHint')}
291
+ control={<SettingsSwitch checked={fitPages} onChange={(on) => writePreviewPref('fitPages', on)} label={t('settings.advanced.fitPages')} />}
292
+ />
293
+ <SettingsItem
294
+ title={t('settings.advanced.renders')}
295
+ description={t('settings.advanced.rendersHint')}
296
+ control={<SettingsSwitch checked={renders} onChange={(on) => writePreviewPref('renders', on)} label={t('settings.advanced.renders')} />}
297
+ />
298
+ </SettingsGroup>
299
+ );
300
+ }
301
+
302
+ /** What is already set for the best experience, for whoever wants it otherwise: how the preview plays (PlaybackGroup). */
303
+ function AdvancedSection() {
304
+ const t = useT();
305
+ return (
306
+ <SettingsPage note={t('settings.advanced.intro')}>
307
+ <PlaybackGroup />
308
+ </SettingsPage>
309
+ );
310
+ }
311
+
312
+ /**
313
+ * This Studio's version and whether npm has a newer openfilm. In the desktop app, Studio comes with the app and is
314
+ * updated with it; anywhere else the person (or their agent) runs the latest, and the CLI restarts Studio on it.
315
+ */
316
+ function AboutGroup() {
317
+ const t = useT();
318
+ const [info, setInfo] = React.useState<StudioVersion | null>(null);
319
+ const [state, setState] = React.useState<'idle' | 'checking' | 'failed'>('idle');
320
+ React.useEffect(() => { void api.version().then(setInfo).catch(() => {}); }, []);
321
+ const inApp = Boolean(studioHost);
322
+ /* Update here as in the top bar: Studio runs the newest and starts again on it */
323
+ const web = useWebUpdate();
324
+ const check = () => {
325
+ setState('checking');
326
+ const before = info?.checkedAt ?? null;
327
+ void api.version(true)
328
+ .then((v) => { setInfo(v); setState(v.checkedAt != null && v.checkedAt !== before ? 'idle' : 'failed'); })
329
+ .catch(() => setState('failed'));
330
+ };
331
+ const status = inApp ? t('settings.about.inApp')
332
+ : info?.source ? t('settings.about.source')
333
+ : info && !info.checking ? t('settings.about.off')
334
+ : state === 'checking' ? t('settings.about.checking')
335
+ : state === 'failed' ? t('settings.about.failed')
336
+ : info?.newer ? t('settings.about.newer').replace('{version}', info.latest ?? '')
337
+ : info?.latest ? t('settings.about.upToDate')
338
+ : t('settings.about.never');
339
+ return (
340
+ <SettingsGroup title={t('settings.about.group')}>
341
+ <SettingsItem
342
+ title={info ? `OpenFilm ${info.version}` : 'OpenFilm'}
343
+ description={status}
344
+ control={inApp || !info?.checking ? null : (
345
+ <SettingsButton disabled={state === 'checking'} onClick={check}>{t('settings.about.check')}</SettingsButton>
346
+ )}
347
+ >
348
+ {hasNotes(info?.version) ? <WhatsNewLink version={info!.version} /> : null}
349
+ </SettingsItem>
350
+ {!inApp && !info?.source && info?.newer ? (
351
+ <SettingsItem
352
+ title={t('settings.about.howTitle')}
353
+ description={web.update?.status === 'updating' ? t('update.webUpdating')
354
+ : web.update?.status === 'waiting' ? t(web.update.reason === 'exporting' ? 'update.webExporting' : 'update.webMaking')
355
+ : web.update?.status === 'failed' ? t('update.webFailed').replace('{error}', web.update.error)
356
+ : t('settings.about.how')}
357
+ control={(
358
+ <>
359
+ <code className="select-all rounded-md bg-[var(--surface-2)] px-2 py-1 font-mono text-[12px] text-[var(--text)]">npx -y openfilm@latest update</code>
360
+ <SettingsButton variant="primary" disabled={!web.update || web.update.status === 'updating'} onClick={web.start}>
361
+ {web.update?.status === 'updating' ? <Loader2 size={14} className="animate-spin" aria-hidden /> : null}
362
+ {t('update.webUpdate')}
363
+ </SettingsButton>
364
+ </>
365
+ )}
366
+ />
367
+ ) : null}
368
+ </SettingsGroup>
369
+ );
370
+ }
371
+
372
+ /** This release's notes again (components/WhatsNew). */
373
+ function WhatsNewLink({ version }: { version: string }) {
374
+ const t = useT();
375
+ return (
376
+ <button type="button" onClick={() => showWhatsNew([version])}
377
+ className="mb-2 text-[13.5px] text-[var(--text-muted)] underline underline-offset-2 transition hover:text-[var(--text)]">
378
+ {t('settings.about.whatsNew')}
379
+ </button>
380
+ );
381
+ }
382
+
383
+ /** A check that answered this long ago is still news: opening About within it looks again only on a click. */
384
+ const CHECKED_FRESH_MS = 10 * 60_000;
385
+
386
+ /**
387
+ * The app's version and its update (lib/host.ts `update`), as an app's About shows them: looked for on opening (unless
388
+ * a check answered a few minutes ago), downloaded by itself with its progress, Restart when it is ready (it installs at
389
+ * the next quit too). The menu's Check for Updates… stays.
390
+ */
391
+ function AppAboutGroup() {
392
+ const t = useT();
393
+ const language = useLanguage();
394
+ const update = studioHost?.update;
395
+ const [state, setState] = React.useState<HostUpdateState | null>(null);
396
+ const [refused, setRefused] = React.useState<string | null>(null);
397
+ React.useEffect(() => update?.onState?.(setState), [update]);
398
+ /* looked for once on opening, when the last answer is old or there is none */
399
+ const looked = React.useRef(false);
400
+ React.useEffect(() => {
401
+ if (!state || looked.current) return;
402
+ looked.current = true;
403
+ const stale = state.checkedAt == null || Date.now() - state.checkedAt > CHECKED_FRESH_MS;
404
+ if (stale && ['idle', 'current', 'error'].includes(state.status)) void update?.check?.();
405
+ }, [state, update]);
406
+ const check = () => { setRefused(null); void update?.check?.(); };
407
+ const restart = () => {
408
+ setRefused(null);
409
+ void update?.restart().then((r) => { if (!r.ok && r.error) setRefused(r.error); });
410
+ };
411
+ const ago = (at: number) => {
412
+ const minutes = Math.round((Date.now() - at) / 60_000);
413
+ const rtf = new Intl.RelativeTimeFormat(language, { numeric: 'auto' });
414
+ return minutes < 60 ? rtf.format(-minutes, 'minute') : minutes < 48 * 60 ? rtf.format(-Math.round(minutes / 60), 'hour') : rtf.format(-Math.round(minutes / 1440), 'day');
415
+ };
416
+ const s = state;
417
+ const next = s?.availableVersion ?? '';
418
+ const waiting = refused ?? (s?.status === 'ready' ? s.error : null);
419
+ const why = waiting === 'agent-busy' ? t('update.agentBusy') : waiting === 'exporting' ? t('update.exporting')
420
+ : waiting === 'install-failed' ? t('update.installFailed').replace('{version}', next) : null;
421
+ const errorText = s?.error === 'offline' ? t('settings.about.appOffline') : s?.error === 'unavailable' ? t('settings.about.appUnavailable') : t('settings.about.appFailed');
422
+ const busy = (text: string) => <span className="inline-flex items-center gap-1.5"><Loader2 size={13} className="animate-spin" aria-hidden />{text}</span>;
423
+ const description = !s ? null
424
+ : s.status === 'off' ? t('settings.about.appOff')
425
+ : s.status === 'idle' || s.status === 'checking' ? busy(t('settings.about.checking'))
426
+ : s.status === 'current' ? `${t('settings.about.upToDate')}${s.checkedAt ? ` ${t('settings.about.appChecked').replace('{time}', ago(s.checkedAt))}` : ''}`
427
+ : s.status === 'available' || s.status === 'downloading' ? busy(`${t('settings.about.appDownloading').replace('{version}', next)}${s.status === 'downloading' ? ` ${Math.round(s.progress)}%` : ''}`)
428
+ : s.status === 'staging' ? busy(t('settings.about.appStaging').replace('{version}', next))
429
+ : s.status === 'ready' ? (why ?? t('update.ready').replace('{version}', next))
430
+ : s.status === 'installing' ? busy(t('update.installing'))
431
+ : errorText;
432
+ const control = !s ? null
433
+ : s.status === 'current' ? <SettingsButton onClick={check}>{t('settings.about.check')}</SettingsButton>
434
+ : s.status === 'error' ? <SettingsButton onClick={check}>{t('settings.about.appRetry')}</SettingsButton>
435
+ : s.status === 'ready' ? <SettingsButton variant="primary" onClick={restart}>{t('update.restart')}</SettingsButton>
436
+ : null;
437
+ return (
438
+ <SettingsGroup title={t('settings.about.group')}>
439
+ <SettingsItem
440
+ title={`OpenFilm ${s?.version ?? ''}`.trim()}
441
+ badge={s?.channel === 'beta' ? <span className="rounded-full bg-[var(--fill-tsp)] px-2 py-0.5 text-[11.5px] text-[var(--text-muted)]">beta</span> : null}
442
+ description={description}
443
+ control={control}
444
+ >
445
+ {hasNotes(s?.version) ? <WhatsNewLink version={s!.version} /> : null}
446
+ {s?.status === 'downloading' ? (
447
+ <div className="h-1 overflow-hidden rounded-full bg-[var(--fill-tsp)]" role="progressbar" aria-valuenow={Math.round(s.progress)} aria-valuemin={0} aria-valuemax={100}>
448
+ <div className="h-full rounded-full bg-[var(--text)] transition-[width]" style={{ width: `${Math.max(2, s.progress)}%` }} />
449
+ </div>
450
+ ) : null}
451
+ </SettingsItem>
452
+ </SettingsGroup>
453
+ );
454
+ }
455
+
456
+ /**
457
+ * Diagnostics (DiagnosticsGroup), for when something does not work; how playing goes, in the viewer's corner
458
+ * (lib/preview-prefs `stats`); in the app, what its chat shows of each turn: what it changed in the files, and its
459
+ * full log (lib/host.ts `developer`).
460
+ */
461
+ function DeveloperSection() {
462
+ const t = useT();
463
+ const developer = studioHost?.developer;
464
+ const [prefs, setPrefs] = React.useState<HostDeveloper | null>(null);
465
+ React.useEffect(() => {
466
+ if (!developer) return undefined;
467
+ void developer.get().then(setPrefs);
468
+ return developer.onChange(setPrefs);
469
+ }, [developer]);
470
+ const change = (next: Partial<HostDeveloper>) => {
471
+ setPrefs((was) => (was ? { ...was, ...next } : was));
472
+ void developer?.set(next);
473
+ };
474
+ const { stats } = usePreviewPrefs();
475
+ return (
476
+ <SettingsPage note={t('settings.developer.intro')}>
477
+ <DiagnosticsGroup />
478
+ <SettingsGroup title={t('settings.developer.playbackGroup')}>
479
+ <SettingsItem
480
+ title={t('settings.developer.playbackStats')}
481
+ description={t('settings.developer.playbackStatsHint')}
482
+ control={<SettingsSwitch checked={stats} onChange={(on) => writePreviewPref('stats', on)} label={t('settings.developer.playbackStats')} />}
483
+ />
484
+ </SettingsGroup>
485
+ {developer ? (
486
+ <SettingsGroup title={t('settings.developer.group')} footnote={t('settings.developer.where')}>
487
+ {/* drawn once the app has said whether each is on: drawn off first, a switch slid on each time the page opened */}
488
+ <SettingsItem
489
+ title={t('settings.developer.changes')}
490
+ description={t('settings.developer.changesHint')}
491
+ control={prefs ? <SettingsSwitch checked={prefs.changes} label={t('settings.developer.changes')} onChange={(on) => change({ changes: on })} /> : null}
492
+ />
493
+ <SettingsItem
494
+ title={t('settings.developer.logs')}
495
+ description={t('settings.developer.logsHint')}
496
+ control={prefs ? <SettingsSwitch checked={prefs.logs} label={t('settings.developer.logs')} onChange={(on) => change({ logs: on })} /> : null}
497
+ />
498
+ </SettingsGroup>
499
+ ) : null}
500
+ {newestVersion() ? (
501
+ <SettingsGroup title={t('settings.developer.releaseGroup')}>
502
+ <SettingsItem
503
+ title={t('settings.developer.previewWhatsNew')}
504
+ description={t('settings.developer.previewWhatsNewHint').replace('{version}', newestVersion()!)}
505
+ control={<SettingsButton onClick={() => showWhatsNew([newestVersion()!])}>{t('settings.developer.preview')}</SettingsButton>}
506
+ />
507
+ </SettingsGroup>
508
+ ) : null}
509
+ </SettingsPage>
510
+ );
511
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * A choice in Settings from a longer list: the trigger a bordered field, the list a menu card portaled to the page (Popover), so
3
+ * the pane it sits in never clips it. The arrow keys move through it, Enter picks, typing jumps to a name.
4
+ */
5
+ import React from 'react';
6
+ import { Check, ChevronDown } from 'lucide-react';
7
+ import { Popover } from '@/components/Popover';
8
+
9
+ type Option<T extends string> = { value: T; label: string; icon?: React.ReactNode };
10
+
11
+ export function SettingsSelect<T extends string>({ label, value, options, onChange, width }: {
12
+ label: string;
13
+ value: T;
14
+ options: Option<T>[];
15
+ onChange: (value: T) => void;
16
+ /** a fixed width (a form field); without one the trigger is as wide as what it shows */
17
+ width?: number;
18
+ }) {
19
+ const [open, setOpen] = React.useState(false);
20
+ const list = React.useRef<HTMLDivElement>(null);
21
+ const typed = React.useRef({ text: '', at: 0 });
22
+ const selected = options.find((option) => option.value === value) ?? options[0]!;
23
+
24
+ /* the list opens on the chosen one */
25
+ React.useEffect(() => {
26
+ if (!open) return;
27
+ const r = requestAnimationFrame(() => (list.current?.querySelector<HTMLElement>('[aria-selected="true"]') ?? list.current?.querySelector<HTMLElement>('[role="option"]'))?.focus());
28
+ return () => cancelAnimationFrame(r);
29
+ }, [open]);
30
+
31
+ const choose = (option: Option<T>) => { setOpen(false); if (option.value !== value) onChange(option.value); };
32
+ const onKeyDown = (e: React.KeyboardEvent) => {
33
+ const items = Array.from(list.current?.querySelectorAll<HTMLElement>('[role="option"]') ?? []);
34
+ const at = items.indexOf(document.activeElement as HTMLElement);
35
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
36
+ e.preventDefault();
37
+ items[(at + (e.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length]?.focus();
38
+ } else if (e.key === 'Home' || e.key === 'End') {
39
+ e.preventDefault();
40
+ items[e.key === 'Home' ? 0 : items.length - 1]?.focus();
41
+ } else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
42
+ const now = Date.now();
43
+ typed.current = { text: (now - typed.current.at < 700 ? typed.current.text : '') + e.key.toLowerCase(), at: now };
44
+ const match = options.findIndex((o) => o.label.toLowerCase().startsWith(typed.current.text));
45
+ if (match >= 0) items[match]?.focus();
46
+ }
47
+ };
48
+
49
+ return (
50
+ <Popover
51
+ open={open}
52
+ onOpenChange={setOpen}
53
+ placement="auto"
54
+ align="end"
55
+ layer={10060}
56
+ trigger={(
57
+ <button type="button" aria-label={`${label}: ${selected.label}`} aria-haspopup="listbox" aria-expanded={open}
58
+ style={width ? { width } : undefined}
59
+ className={`flex h-10 max-w-full items-center gap-2 ${width ? '' : 'w-auto'} rounded-[8px] border px-3 text-left text-[14.5px] text-[var(--text)] transition ${open ? 'border-[var(--text-muted)]' : 'border-[var(--border)] hover:border-[var(--border-strong)]'}`}>
60
+ {selected.icon ? <span className="flex shrink-0 text-[var(--text-muted)]">{selected.icon}</span> : null}
61
+ {/* without a width it is as wide as what it shows */}
62
+ <span className={width ? 'min-w-0 flex-1 truncate' : 'whitespace-nowrap'}>{selected.label}</span>
63
+ <ChevronDown size={15} className="shrink-0 text-[var(--text-muted)]" />
64
+ </button>
65
+ )}
66
+ >
67
+ <div ref={list} role="listbox" aria-label={label} onKeyDown={onKeyDown}
68
+ style={{ minWidth: width ?? 160 }}
69
+ className="max-h-[min(320px,var(--popover-max-h))] overflow-y-auto rounded-[10px] border border-[var(--border)] bg-[var(--surface)] p-1 shadow-[var(--shadow-lg)]">
70
+ {options.map((option) => {
71
+ const on = option.value === value;
72
+ return (
73
+ <button key={option.value} type="button" role="option" aria-selected={on} onClick={() => choose(option)}
74
+ className="flex h-9 w-full items-center gap-2 rounded-[6px] px-2 text-left text-[14.5px] text-[var(--text)] outline-none hover:bg-[var(--bg-hover)] focus-visible:bg-[var(--bg-hover)]">
75
+ {option.icon ? <span className="flex shrink-0 text-[var(--text-muted)]">{option.icon}</span> : null}
76
+ <span className="min-w-0 flex-1 truncate">{option.label}</span>
77
+ <Check size={16} className={on ? 'shrink-0 text-[var(--text)]' : 'invisible shrink-0'} />
78
+ </button>
79
+ );
80
+ })}
81
+ </div>
82
+ </Popover>
83
+ );
84
+ }