refactor: プラグイン設定をmanifest.jsonから動的レンダリングに変更#93
Conversation
新規プラグイン追加時にフロントエンドのコード変更が不要になるよう、 ハードコードされた設定マップをplugin storeからの動的読み込みに変更。 - ConfigField型を拡張し、全カスタムフィールド型(prompt-builder, input-list等)をサポート - configUtils.tsを新規作成: manifest config → NodeField変換、showWhen評価 - NodeSettings.tsxをplugin store優先の動的スキーマ解決に変更(nodeConfigsはフォールバック) - i18nキーマップをnode IDからの自動導出に置き換え - FieldSelectorNode/DataPreviewPopupの出力フィールドをplugin storeから動的取得 - Canvas.tsxの動的ポート生成をmanifestのconfigフィールド型による汎用判定に変更 - 各プラグインのmanifest.jsonにカスタムフィールド型とshowWhen条件を追加 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughEditor UI now derives node ports, fields, and dynamic options from plugin manifests via new types and config utilities; pluginStore exposes plugin configs; editor components prefer plugin-provided inputs/outputs with fallbacks; several plugin manifests adopt new field types and showWhen rules. Changes
Sequence Diagram(s)sequenceDiagram
participant Canvas as Canvas Component
participant Store as Plugin Store
participant Utils as Config Utils
participant Renderer as Node Renderer
Canvas->>Store: getPluginConfig(node.type)
Store-->>Canvas: config (Record<string,ConfigField>|undefined)
Canvas->>Utils: manifestConfigToNodeFields(config)
Utils-->>Canvas: NodeField[] (normalized)
Canvas->>Canvas: inspect NodeField types (prompt-builder / input-list / etc.)
Canvas->>Renderer: override nodeInputs -> generated ports
Renderer-->>Canvas: rendered node with dynamic ports
sequenceDiagram
participant NodeSettings as NodeSettings Panel
participant Store as Plugin Store
participant Utils as Config Utils
participant Field as Field Renderer
NodeSettings->>Store: getPluginConfig(node.type)
Store-->>NodeSettings: config
NodeSettings->>Utils: manifestConfigToNodeFields(config)
Utils-->>NodeSettings: fields: NodeField[]
loop each field
NodeSettings->>Utils: evaluateShowWhen(field.showWhen, currentConfig)
Utils-->>NodeSettings: visible (true/false)
alt visible
NodeSettings->>Field: request dynamic options (dependsOn / dynamic)
Field-->>NodeSettings: renderable field UI
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (6)
plugins/openai-llm/manifest.json (1)
128-131:promptSectionsis always visible alongsidesystemPrompt— add adescriptionfor clarity.Users see both
systemPromptandpromptSectionsat the same time with no explanation of how they interact. At minimum, adescriptionfield would guide users. If these are mutually exclusive, ashowWhencondition on one would reduce confusion.♻️ Proposed addition
"promptSections": { "type": "prompt-builder", - "label": "Prompt Builder" + "label": "Prompt Builder", + "description": "Compose a dynamic prompt from static text blocks and named input ports. Overrides the simple prompt input when input-type sections are defined." },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/openai-llm/manifest.json` around lines 128 - 131, The schema currently defines "promptSections" (type: "prompt-builder") but has no description and is always shown alongside "systemPrompt", causing user confusion; add a "description" string to the "promptSections" entry explaining how it relates to "systemPrompt" (e.g., when to use each and that they are alternatives), and if they are mutually exclusive add a "showWhen" condition on either "promptSections" or "systemPrompt" (use the existing condition syntax used elsewhere in the manifest) so only the appropriate input is visible at a time.apps/web/components/editor/Canvas.tsx (2)
245-245:getPluginByIdis called twice per node in the sameuseMemopass — unify into one call.
pluginForPorts(Line 245) andplugin(Line 287) are bothgetPluginById(node.type). Since both references are used within the same map iteration, this is a redundant store lookup per node. Combine them:♻️ Proposed fix
- // Dynamic port generation based on manifest config field types - const pluginForPorts = getPluginById(node.type); - if (pluginForPorts?.config && node.config) { - for (const [fieldKey, fieldDef] of Object.entries(pluginForPorts.config)) { + // Get plugin once for both dynamic port generation and category mapping + const plugin = getPluginById(node.type); + if (plugin?.config && node.config) { + for (const [fieldKey, fieldDef] of Object.entries(plugin.config)) { // ... dynamic port generation ... } } // ... get outputs ... - // Get category from plugin or fall back to legacy function - const plugin = getPluginById(node.type); const category = plugin?.category ? mapPluginCategoryToLegacy(plugin.category) : getNodeCategory(node.type);Also applies to: 287-290
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/editor/Canvas.tsx` at line 245, The code calls getPluginById(node.type) twice for the same node; inside the useMemo map, call getPluginById(node.type) once and assign it to a single local variable (e.g., plugin) and then use that variable for both pluginForPorts and plugin usages; update any places referencing pluginForPorts (line where it's declared) and the later plugin reference (lines ~287-290) to use the single variable so the lookup is not duplicated.
826-855:getNodeInputshardcoded fallback foropenai-llm/text-transformnow partially duplicates the dynamic block — long-term maintenance risk.The new dynamic port generation block (Lines 244–272) handles
prompt-builderandinput-listfields generically from the manifest. However,getNodeInputsat Lines 827–855 still has hardcoded plugin-specific branches for the exact same cases. These two paths coexist and must be kept in sync manually:
- The dynamic block runs only when the plugin is loaded (
pluginForPorts?.configis truthy).getNodeInputsruns only as fallback when the plugin is not yet loaded (pluginInputs.length === 0).This is a maintenance trap: adding a new
prompt-builderorinput-listplugin requires updatinggetNodeInputsto match. The long-term direction should be to remove the hardcoded cases fromgetNodeInputsonce the plugin store is reliably populated at app startup.Consider whether these hardcoded plugin-specific blocks in
getNodeInputscan be removed once the plugin loading is guaranteed to complete before the canvas renders (e.g., via a loading gate). At minimum, add a comment ingetNodeInputslinking it to the dynamic block so the relationship is clear.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/editor/Canvas.tsx` around lines 826 - 855, getNodeInputs currently contains hardcoded branches for 'openai-llm' (promptSections) and 'text-transform' (templateInputs) that duplicate the dynamic port generation implemented elsewhere (the plugin-driven logic that reads prompt-builder / input-list from manifests), creating a maintenance trap; remove those plugin-specific branches from getNodeInputs (leave only a generic default fallback like a single 'prompt' or 'text' port) so port generation is centralized in the plugin-driven code (the code that uses pluginForPorts/pluginInputs), or if you cannot remove them yet, add a clear TODO comment in getNodeInputs referencing the dynamic block and the manifest keys ('prompt-builder'/'input-list') and note to delete these branches once plugin loading is guaranteed at startup (ensure references to getNodeInputs, promptSections, templateInputs, pluginForPorts, and pluginInputs are kept in the comment).plugins/text-transform/manifest.json (1)
54-57:templateInputsis unconditionally visible — consider addingshowWhenThe
templateInputsfield has noshowWhencondition, so it will appear for every operation (uppercase,lowercase,trim, etc.) even though it is only meaningful whenoperation === "template". The same PR already usesshowWhenforreasoningEffortinopenai-llm, so the infrastructure is in place. Also, adescriptionproperty would bring this field in line with every other config entry in this file.♻️ Proposed fix
"templateInputs": { "type": "input-list", - "label": "Template Inputs" + "label": "Template Inputs", + "description": "Named inputs injected as {{name}} variables in the template", + "showWhen": { + "field": "operation", + "operator": "equals", + "value": "template" + } },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/text-transform/manifest.json` around lines 54 - 57, Add a conditional visibility rule and description to the existing templateInputs config: update the templateInputs entry to include a showWhen that checks the operation field equals "template" (so templateInputs only appears for operation === "template") and add a descriptive description property consistent with other entries; reference the templateInputs symbol and the operation field when making this change.apps/web/components/editor/DataPreviewPopup.tsx (1)
19-37:nodeOutputFieldsis duplicated acrossDataPreviewPopup.tsxandFieldSelectorNode.tsx— extract to a shared constant.The two hardcoded fallback maps are already out of sync:
FieldSelectorNode.tsxincludes'data-formatter','template-editor','switch','foreach'entries that are absent here, and the'http-request'entry in both files includes a'headers'field that the plugin manifest does not define as an output. As plugin coverage grows, these maps will continue to diverge silently.Extract the shared constant to e.g.
apps/web/lib/nodeOutputFields.ts:♻️ Proposed refactor
// apps/web/lib/nodeOutputFields.ts (new file) +export const FALLBACK_NODE_OUTPUT_FIELDS: Record<string, string[]> = { + 'twitch-chat': ['text', 'author', 'message'], + 'youtube-chat': ['text', 'author', 'message'], + 'manual-input': ['text'], + 'openai-llm': ['response'], + 'anthropic-llm': ['response'], + 'google-llm': ['response'], + 'ollama-llm': ['response'], + 'timer': ['tick', 'count'], + 'http-request': ['response', 'status'], + 'text-transform': ['result'], + 'data-formatter': ['formatted', 'parsed'], + 'field-selector': ['output'], + 'template-editor': ['output'], + 'random': ['value'], + 'variable': ['value'], + 'switch': ['value', 'data'], + 'delay': ['output'], + 'loop': ['index', 'value'], + 'foreach': ['item', 'index'], +};Then in both
DataPreviewPopup.tsxandFieldSelectorNode.tsx:-// Known output fields for each node type -const nodeOutputFields: Record<string, string[]> = { ... }; +import { FALLBACK_NODE_OUTPUT_FIELDS } from '@/lib/nodeOutputFields';Also applies to: 107-111
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/editor/DataPreviewPopup.tsx` around lines 19 - 37, The duplicate fallback map nodeOutputFields should be extracted to a single shared constant and both DataPreviewPopup and FieldSelectorNode should import it: create a new module (e.g., apps/web/lib/nodeOutputFields.ts) exporting the nodeOutputFields object, update DataPreviewPopup and FieldSelectorNode to remove their local nodeOutputFields maps and import the shared constant, and reconcile entries (remove the nonexistent 'headers' output from 'http-request' and ensure the shared map includes 'data-formatter', 'template-editor', 'switch', and 'foreach' as present in FieldSelectorNode) so both consumers use the same source of truth.apps/web/lib/types.ts (1)
152-154: Consider makingoperatoroptional in the modernShowWhenConditionvariant.Currently
operatoris required (operator: string), but the implementation inevaluateShowWhendoesn't use it, and all existing manifests use the legacy{ key, value }form. If operator-based evaluation is deferred, marking it optional (operator?: string) avoids forcing callers to supply a value that is currently ignored, and keeps the contract honest.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/lib/types.ts` around lines 152 - 154, Update the ShowWhenCondition type so the modern variant doesn't require an unused operator: change the `{ field: string; operator: string; value: string | string[] }` variant to make operator optional (`operator?: string`), and then ensure any code using evaluateShowWhen and other consumers of ShowWhenCondition (e.g., evaluateShowWhen) compiles with operator possibly undefined (handle or ignore the undefined case as current logic does).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/components/editor/Canvas.tsx`:
- Around line 244-272: The code in Canvas.tsx sets nodeInputs by casting
node.config[fieldKey] to PromptSection[] and string[] without verifying it's an
array; update the logic inside the loop that inspects pluginForPorts.config (the
block handling fieldDef.type === 'prompt-builder' and 'input-list') to first
check Array.isArray(node.config[fieldKey]) before treating it as an array, only
then map to nodeInputs using formatPortLabel and PortType; if the value is not
an array, skip that field (or fallback to an empty array) so getPluginById,
node.config, PromptSection, formatPortLabel and nodeInputs are protected from
TypeError at render time.
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 1940-1969: The dynamic-LLM branch is too broad: it's triggered for
any field with field.dynamic && field.dependsOn and can accidentally treat
non-LLM selects as LLM model pickers; narrow the condition to only run for
actual LLM model fields (e.g., check field.key === "llm_model" || field.key ===
"model" or introduce and check a new field.optionsSource === "llm" on the
field), then use getPluginById(providerPluginId) and LLM_MODEL_OPTIONS only when
that LLM-specific indicator is present; update any field definitions that should
use this branch to include the new hint if you add one.
In `@apps/web/lib/configUtils.ts`:
- Around line 47-61: manifestConfigToNodeFields currently drops manifest
constraint properties; update the mapper (manifestConfigToNodeFields) to include
min, max, required, and default when converting a ConfigField to a NodeField so
the UI can render constraints and defaults; specifically add min: field.min,
max: field.max, required: field.required, and default: field.default to the
returned object (leaving normalizeOptions(field.options) and other keys
unchanged) and ensure any type mapping (mapFieldType) still applies.
- Around line 67-86: The function evaluateShowWhen currently ignores
condition.operator; update it to dispatch on condition.operator (read
condition.operator) inside evaluateShowWhen so the visibility logic matches the
ShowWhenCondition type: extract fieldKey (from condition.key or
condition.field), normalize expectedValues as Array, compute currentValue =
String(config[fieldKey] ?? ''), then switch on condition.operator (treat
undefined as "in" for backward compatibility) and return true for "in"/"equals"
when expectedValues includes currentValue (or equals when you want single-value
semantics), return the inverse for "not-in"/"not-equals", and throw or default
to false for unknown operators; reference evaluateShowWhen, ShowWhenCondition,
condition.operator, expectedValues, currentValue in your changes.
---
Nitpick comments:
In `@apps/web/components/editor/Canvas.tsx`:
- Line 245: The code calls getPluginById(node.type) twice for the same node;
inside the useMemo map, call getPluginById(node.type) once and assign it to a
single local variable (e.g., plugin) and then use that variable for both
pluginForPorts and plugin usages; update any places referencing pluginForPorts
(line where it's declared) and the later plugin reference (lines ~287-290) to
use the single variable so the lookup is not duplicated.
- Around line 826-855: getNodeInputs currently contains hardcoded branches for
'openai-llm' (promptSections) and 'text-transform' (templateInputs) that
duplicate the dynamic port generation implemented elsewhere (the plugin-driven
logic that reads prompt-builder / input-list from manifests), creating a
maintenance trap; remove those plugin-specific branches from getNodeInputs
(leave only a generic default fallback like a single 'prompt' or 'text' port) so
port generation is centralized in the plugin-driven code (the code that uses
pluginForPorts/pluginInputs), or if you cannot remove them yet, add a clear TODO
comment in getNodeInputs referencing the dynamic block and the manifest keys
('prompt-builder'/'input-list') and note to delete these branches once plugin
loading is guaranteed at startup (ensure references to getNodeInputs,
promptSections, templateInputs, pluginForPorts, and pluginInputs are kept in the
comment).
In `@apps/web/components/editor/DataPreviewPopup.tsx`:
- Around line 19-37: The duplicate fallback map nodeOutputFields should be
extracted to a single shared constant and both DataPreviewPopup and
FieldSelectorNode should import it: create a new module (e.g.,
apps/web/lib/nodeOutputFields.ts) exporting the nodeOutputFields object, update
DataPreviewPopup and FieldSelectorNode to remove their local nodeOutputFields
maps and import the shared constant, and reconcile entries (remove the
nonexistent 'headers' output from 'http-request' and ensure the shared map
includes 'data-formatter', 'template-editor', 'switch', and 'foreach' as present
in FieldSelectorNode) so both consumers use the same source of truth.
In `@apps/web/lib/types.ts`:
- Around line 152-154: Update the ShowWhenCondition type so the modern variant
doesn't require an unused operator: change the `{ field: string; operator:
string; value: string | string[] }` variant to make operator optional
(`operator?: string`), and then ensure any code using evaluateShowWhen and other
consumers of ShowWhenCondition (e.g., evaluateShowWhen) compiles with operator
possibly undefined (handle or ignore the undefined case as current logic does).
In `@plugins/openai-llm/manifest.json`:
- Around line 128-131: The schema currently defines "promptSections" (type:
"prompt-builder") but has no description and is always shown alongside
"systemPrompt", causing user confusion; add a "description" string to the
"promptSections" entry explaining how it relates to "systemPrompt" (e.g., when
to use each and that they are alternatives), and if they are mutually exclusive
add a "showWhen" condition on either "promptSections" or "systemPrompt" (use the
existing condition syntax used elsewhere in the manifest) so only the
appropriate input is visible at a time.
In `@plugins/text-transform/manifest.json`:
- Around line 54-57: Add a conditional visibility rule and description to the
existing templateInputs config: update the templateInputs entry to include a
showWhen that checks the operation field equals "template" (so templateInputs
only appears for operation === "template") and add a descriptive description
property consistent with other entries; reference the templateInputs symbol and
the operation field when making this change.
- Canvas.tsx: Array.isArray チェック追加、getPluginById 重複呼び出し解消 - Canvas.tsx: getNodeInputs からハードコード分岐を削除(動的ポート生成に統一) - NodeSettings.tsx: 動的LLMセレクト条件を field.key === "model" に限定 - configUtils.ts: manifestConfigToNodeFields に min/max/required/defaultValue 追加 - configUtils.ts: evaluateShowWhen で operator (eq/neq/in) をディスパッチ - types.ts: ShowWhenCondition の operator をオプショナルに、NodeField に min/max/required/defaultValue 追加 - nodeOutputFields を共有定数として抽出 (FieldSelectorNode, DataPreviewPopup) - openai-llm manifest: promptSections に description 追加 - text-transform manifest: templateInputs に showWhen と description 追加 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/components/panels/NodeSettings.tsx (1)
1915-1926:⚠️ Potential issue | 🟡 Minor
min,max, andrequiredfromNodeFieldare mapped but never applied as HTML attributes on the number input.
NodeFieldnow hasmin/max/required/defaultValue(added as part of the previous fix), andmanifestConfigToNodeFieldscorrectly populates them from manifests (e.g.,voicevox-ttsspeed: 0.5–2.0, pitch: −0.15–0.15). However, the"number"case inrenderFielddoes not pass these as HTML attributes, so the browser imposes no bounds and the user receives no feedback.🛠 Proposed fix
case "number": return ( <input type="number" value={value as number} onChange={(e) => handleChange(field.key, e.target.value === "" ? undefined : parseFloat(e.target.value)) } placeholder={field.placeholder} + min={field.min} + max={field.max} + required={field.required} style={inputStyle} /> );
defaultValueshould also be considered duringlocalConfiginitialization:// In useEffect where localConfig is set: - setLocalConfig(selectedNode.config || {}); + const base = selectedNode.config || {}; + // Apply field defaults for missing keys + const withDefaults = fields.reduce((acc, f) => { + if (!(f.key in base) && f.defaultValue !== undefined) acc[f.key] = f.defaultValue; + return acc; + }, { ...base }); + setLocalConfig(withDefaults);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/panels/NodeSettings.tsx` around lines 1915 - 1926, The "number" branch in renderField does not apply NodeField's min/max/required/defaultValue to the rendered <input>; update the number case in renderField to pass field.min, field.max, and field.required as HTML attributes (e.g., min={field.min} max={field.max} required={field.required}) and set the input's value fallback to field.defaultValue when localConfig value is undefined; also ensure localConfig initialization logic (where localConfig is created/seeded) uses field.defaultValue for number fields so the UI and validation reflect manifest-provided bounds (refer to renderField, NodeField, and the localConfig initialization code paths).
🧹 Nitpick comments (1)
apps/web/lib/configUtils.ts (1)
7-17:opt.value as string | numbersilently coerces non-string/number option values.
ConfigField['options']declaresvalue: unknown, so a manifest providingvalue: true(boolean) orvalue: nullpasses this cast at compile time but remains the original type at runtime. TheNodeField.optionstype only acceptsstring | number, yet the cast hides the mismatch instead of normalising it.♻️ Proposed fix – normalise explicitly
- return { label: opt.label, value: opt.value as string | number }; + const v = opt.value; + return { + label: opt.label, + value: typeof v === 'number' ? v : String(v ?? ''), + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/lib/configUtils.ts` around lines 7 - 17, The current normalizeOptions function casts opt.value to string|number which hides runtime mismatches between ConfigField['options'] (value: unknown) and NodeField.options; update normalizeOptions to perform runtime normalization instead: in the branch for object options (the one using opt.label/opt.value), read const v = opt.value and if typeof v === 'string' || typeof v === 'number' use v as the value, otherwise convert v to a string via String(v) (and treat null/undefined as '' if you want empty string) so the returned { label, value } always conforms to NodeField.options; keep the string-option branch unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/components/editor/Canvas.tsx`:
- Around line 248-257: In Canvas.tsx inside the block that handles fieldDef.type
=== 'prompt-builder', validate each PromptSection.content before creating ports:
change the logic that builds nodeInputs (currently using inputSections.map with
id: section.content) to first filter out sections where section.content is falsy
or empty (e.g., === ''), then map to ports; do the same defensive filter for the
'input-list' handling later so you never create a port with id: "" (refer to
node.config[fieldKey], PromptSection, inputSections and the code that assigns
nodeInputs).
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 1833-1845: The effect that checks
getPluginById(selectedNode.type)?.config is missing a dependency on the plugin
store load status, causing fetchAnimations()/fetchModels() to be skipped if
plugins finish loading after node selection; update the dependency array for the
effect that references selectedNode/getPluginById to also include the
plugins-loaded flag (e.g., isLoaded or isPluginsLoaded from the plugin store) so
the effect re-runs once plugins are populated and triggers fetchAnimations() and
fetchModels() when the plugin config contains "animation-file" or "model-file".
- Around line 1941-1951: The dynamic model-select branch only runs when
field.key === "model", so fields like emotion-analyzer's "llm_model" never get
the dynamic options; update the conditional in NodeSettings (the block
referencing field.dynamic, field.dependsOn, field.key, localConfig,
providerPluginId, getPluginById, dynamicModelOptions, and LLM_MODEL_OPTIONS) to
also accept other LLM field names—e.g., change the check to include
field.key.includes("model") or explicitly allow "llm_model"—so
providerPlugin/modelOptions resolution runs for those keys and the select
renders dynamic options.
In `@apps/web/lib/configUtils.ts`:
- Around line 92-103: The switch on operator currently treats any unknown
operator (like "not-in") as the default "in" case which reverses intended
behavior; update the switch to explicitly handle 'in' and 'not-in' (e.g., add a
case 'in' that returns expectedValues.includes(currentValue) and a case 'not-in'
that returns !expectedValues.includes(currentValue)), and make the default
branch throw or log a clear error for unsupported operators instead of falling
through; reference the existing operator variable and the
expectedValues/currentValue comparisons when implementing these cases.
In `@apps/web/lib/nodeOutputFields.ts`:
- Around line 6-26: The fallback mapping in nodeOutputFields is out of sync with
the canvas port IDs returned by getNodeOutputs (used by DataPreviewPopup and
FieldSelectorNode); update nodeOutputFields so the keys match getNodeOutputs
exactly: change 'timer' to ['tick','timestamp'], change 'switch' to
['true','false'], change 'loop' to ['loop','done'], change 'foreach' to include
['item','index','done'], and remove 'headers' from 'http-request' so it only
lists ['response','status']; verify these entries in nodeOutputFields align with
the port IDs in Canvas.tsx/getNodeOutputs.
---
Outside diff comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 1915-1926: The "number" branch in renderField does not apply
NodeField's min/max/required/defaultValue to the rendered <input>; update the
number case in renderField to pass field.min, field.max, and field.required as
HTML attributes (e.g., min={field.min} max={field.max}
required={field.required}) and set the input's value fallback to
field.defaultValue when localConfig value is undefined; also ensure localConfig
initialization logic (where localConfig is created/seeded) uses
field.defaultValue for number fields so the UI and validation reflect
manifest-provided bounds (refer to renderField, NodeField, and the localConfig
initialization code paths).
---
Nitpick comments:
In `@apps/web/lib/configUtils.ts`:
- Around line 7-17: The current normalizeOptions function casts opt.value to
string|number which hides runtime mismatches between ConfigField['options']
(value: unknown) and NodeField.options; update normalizeOptions to perform
runtime normalization instead: in the branch for object options (the one using
opt.label/opt.value), read const v = opt.value and if typeof v === 'string' ||
typeof v === 'number' use v as the value, otherwise convert v to a string via
String(v) (and treat null/undefined as '' if you want empty string) so the
returned { label, value } always conforms to NodeField.options; keep the
string-option branch unchanged.
- Canvas.tsx: PromptSection.content が空の場合をフィルタ、input-list も同様
- NodeSettings.tsx: useEffect に isPluginsLoaded 依存追加(プラグインロード後の再実行)
- NodeSettings.tsx: 動的モデルセレクトを field.key.includes("model") に拡張(llm_model 対応)
- NodeSettings.tsx: number 入力に min/max/required/defaultValue を反映
- configUtils.ts: evaluateShowWhen に not-in 演算子追加、未知の演算子を警告
- configUtils.ts: normalizeOptions でランタイム型検証(unsafe cast 除去)
- nodeOutputFields.ts: Canvas.tsx の getNodeOutputs と同期(timer, switch, loop, foreach, http-request)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/components/panels/NodeSettings.tsx (1)
1890-1890:⚠️ Potential issue | 🟡 Minor
field.defaultValueis unreachable — number inputs never show manifest defaults.Line 1890 sets
value = localConfig[field.key] ?? "". Because??only nullish-coalescesnull/undefined, when the key is missing fromlocalConfig,valuebecomes""(an empty string, not nullish). On line 1919,(value ?? field.defaultValue ?? "")therefore always returns""and never reachesfield.defaultValue.The commit message ("number 入力での
defaultValue反映") indicates this was intentional, but the??chain is subtly broken for this case.🛠️ Proposed fix
case "number": return ( <input type="number" - value={(value ?? field.defaultValue ?? "") as number} + value={(value !== "" ? value : (field.defaultValue ?? "")) as number} onChange={(e) => handleChange(field.key, e.target.value === "" ? undefined : parseFloat(e.target.value)) } placeholder={field.placeholder} min={field.min} max={field.max} required={field.required} style={inputStyle} /> );Also applies to: 1919-1919
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/panels/NodeSettings.tsx` at line 1890, The bug is that value is set with a fallback to the empty string (const value = localConfig[field.key] ?? ""), which prevents the later nullish-coalesce (value ?? field.defaultValue ?? "") from ever using field.defaultValue; change value so it is undefined when the key is missing instead of "", e.g. compute value using a presence check like const value = field.key in localConfig ? localConfig[field.key] : undefined (or use undefined/null as the fallback) so (value ?? field.defaultValue ?? "") can correctly fall back to field.defaultValue; update usages referencing value/localConfig/field.key accordingly.
🧹 Nitpick comments (1)
apps/web/components/panels/NodeSettings.tsx (1)
2454-2456:toPluginCamelcloses over nothing — move it outside the component.The function is recreated on every render without needing any component state or props. Hoisting it to module scope eliminates the unnecessary allocation.
♻️ Proposed refactor
+// Convert kebab-case/snake_case to camelCase for i18n key derivation +const toPluginCamel = (id: string) => + id.replace(/[-_]([a-z0-9])/g, (_, c: string) => c.toUpperCase()); + export default function NodeSettings() { ... - // Convert kebab-case/snake_case to camelCase for i18n key derivation - const toPluginCamel = (id: string) => - id.replace(/[-_]([a-z0-9])/g, (_, c: string) => c.toUpperCase());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/panels/NodeSettings.tsx` around lines 2454 - 2456, The helper toPluginCamel is defined inside the NodeSettings component and is recreated on every render; move this pure function to module scope (above the component) so it is hoisted and not reallocated each render. Cut the const toPluginCamel = (id: string) => id.replace(/[-_]([a-z0-9])/g, (_, c: string) => c.toUpperCase()); out of the component body and paste it at top-level in the module, keep the same signature and types, and leave all references inside the component unchanged so callers continue to use toPluginCamel without other edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 1949-1954: The code unsafely casts
providerPlugin?.config?.model?.options to {label:string;value:string|number}[]
which breaks when the manifest uses the string[] shorthand and bypasses the
existing normalizeOptions logic; export normalizeOptions from
apps/web/lib/configUtils.ts and in NodeSettings.tsx import and call
normalizeOptions(providerPlugin?.config?.model?.options) to produce a properly
typed options array, then fall back to LLM_MODEL_OPTIONS[dependsOnValue] || []
as before (update the modelOptions assignment to use the normalized result
instead of the direct cast).
---
Outside diff comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Line 1890: The bug is that value is set with a fallback to the empty string
(const value = localConfig[field.key] ?? ""), which prevents the later
nullish-coalesce (value ?? field.defaultValue ?? "") from ever using
field.defaultValue; change value so it is undefined when the key is missing
instead of "", e.g. compute value using a presence check like const value =
field.key in localConfig ? localConfig[field.key] : undefined (or use
undefined/null as the fallback) so (value ?? field.defaultValue ?? "") can
correctly fall back to field.defaultValue; update usages referencing
value/localConfig/field.key accordingly.
---
Nitpick comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 2454-2456: The helper toPluginCamel is defined inside the
NodeSettings component and is recreated on every render; move this pure function
to module scope (above the component) so it is hoisted and not reallocated each
render. Cut the const toPluginCamel = (id: string) =>
id.replace(/[-_]([a-z0-9])/g, (_, c: string) => c.toUpperCase()); out of the
component body and paste it at top-level in the module, keep the same signature
and types, and leave all references inside the component unchanged so callers
continue to use toPluginCamel without other edits.
- NodeSettings.tsx: value の初期値を undefined に変更し defaultValue フォールバックを有効化 - NodeSettings.tsx: 動的モデルオプションに normalizeOptions を使用(unsafe cast 除去) - NodeSettings.tsx: toPluginCamel をモジュールスコープに移動 - NodeSettings.tsx: animation-file/model-file の value 型ガード追加 - configUtils.ts: normalizeOptions をエクスポート Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/components/panels/NodeSettings.tsx (1)
2055-2067:⚠️ Potential issue | 🟡 Minor
checkboxdoesn't honorfield.defaultValue, unlike every other field type.Line 1894 initializes
valuetoundefinedspecifically to enablevalue ?? field.defaultValue ?? ""for text/number/textarea/password. Thecheckboxcase useschecked={!!value}, which treatsundefinedasfalseand ignoresfield.defaultValue. A manifest entry withdefaultValue: truefor a checkbox will always render unchecked until the user explicitly saves.🐛 Proposed fix
- checked={!!value} + checked={(value ?? field.defaultValue ?? false) as boolean}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/panels/NodeSettings.tsx` around lines 2055 - 2067, The checkbox branch in NodeSettings.tsx is ignoring field.defaultValue because it uses checked={!!value}; update the checkbox to derive its checked state from value nullish-coalesced with field.defaultValue (e.g., use value ?? field.defaultValue) so a manifest defaultValue: true renders checked initially, and keep onChange using handleChange(field.key, e.target.checked) to persist user changes.
🧹 Nitpick comments (2)
apps/web/components/panels/NodeSettings.tsx (2)
1947-1951:field.key.includes("model")is still a heuristic — prefer explicit key matching.Any future dynamic-select field whose key happens to contain
"model"(e.g.,"data_model_type","3d_model_selector") with adependsOnwill silently enter the LLM model branch. The previous review suggested using explicit equality ("model"or"llm_model") or a dedicatedoptionsSourcediscriminator; the currentincludesis an improvement but still imprecise.♻️ Proposed fix — explicit key allowlist
- if (field.dynamic && field.dependsOn && field.key.includes("model")) { + const LLM_MODEL_KEYS = new Set(["model", "llm_model"]); + if (field.dynamic && field.dependsOn && LLM_MODEL_KEYS.has(field.key)) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/panels/NodeSettings.tsx` around lines 1947 - 1951, The branch that detects LLM-model selects uses the fuzzy check field.key.includes("model"), which can misclassify unrelated fields; update the condition in the dynamic-select handling (the block that checks field.dynamic and field.dependsOn) to use an explicit allowlist or discriminator instead — for example, check field.key === "model" || field.key === "llm_model" or use a dedicated field.optionsSource === "llm" (or similar) if present; ensure you update all places referencing this logic (the dynamic model select branch using providerPluginId and dependsOnValue) so only true LLM model fields take the LLM path.
2459-2467:getNodeLabelsilently ignores itsnodeTypeparameter in the fallback path.The fallback
return schemaLabel(line 2466) uses the outer-scope variable, which is always bound toselectedNode.type, not thenodeTypeargument. It works today because the only call site isgetNodeLabel(selectedNode.type), but the signature implies it's reusable for arbitrary node types.♻️ Proposed fix — pass schemaLabel as an explicit argument or inline the function
- const getNodeLabel = (nodeType: string): string => { + const getNodeLabel = (nodeType: string, fallback: string): string => { const derivedKey = `nodeConfig.${toPluginCamel(nodeType)}.label`; const derived = t(derivedKey); if (derived !== derivedKey) return derived; - return schemaLabel; + return fallback; };Call site:
- {getNodeLabel(selectedNode.type)} + {getNodeLabel(selectedNode.type, schemaLabel)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/panels/NodeSettings.tsx` around lines 2459 - 2467, getNodeLabel currently ignores its nodeType parameter on the fallback path by returning the outer-scope schemaLabel (bound to selectedNode.type); fix by making the fallback depend on the passed nodeType: either add a second parameter (e.g., schemaLabel) to getNodeLabel and pass the appropriate label from callers (including where you call getNodeLabel(selectedNode.type)), or compute the fallback inside getNodeLabel using nodeType (e.g., lookup nodeConfigs[nodeType]?.label or pluginStore label) so the function no longer reads the outer selectedNode.type implicitly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 1837-1847: The fetch logic only looks at
getPluginById(selectedNode.type)?.config, so built-in UI nodes like
avatar-display that use the static nodeConfigs fallback never trigger
fetchAnimations/fetchModels; update the logic in the component where
getPluginById is used (the block referencing selectedNode.type and pluginConfig)
to also inspect nodeConfigs[selectedNode.type] (or the fallback config object)
for field types, and run fetchAnimations() and fetchModels() when those config
fields include type "animation-file" or "model-file"; keep existing checks for
getPluginById and add the same checks for nodeConfigs to ensure avatar-display
triggers the same fetches.
---
Outside diff comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 2055-2067: The checkbox branch in NodeSettings.tsx is ignoring
field.defaultValue because it uses checked={!!value}; update the checkbox to
derive its checked state from value nullish-coalesced with field.defaultValue
(e.g., use value ?? field.defaultValue) so a manifest defaultValue: true renders
checked initially, and keep onChange using handleChange(field.key,
e.target.checked) to persist user changes.
---
Nitpick comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 1947-1951: The branch that detects LLM-model selects uses the
fuzzy check field.key.includes("model"), which can misclassify unrelated fields;
update the condition in the dynamic-select handling (the block that checks
field.dynamic and field.dependsOn) to use an explicit allowlist or discriminator
instead — for example, check field.key === "model" || field.key === "llm_model"
or use a dedicated field.optionsSource === "llm" (or similar) if present; ensure
you update all places referencing this logic (the dynamic model select branch
using providerPluginId and dependsOnValue) so only true LLM model fields take
the LLM path.
- Around line 2459-2467: getNodeLabel currently ignores its nodeType parameter
on the fallback path by returning the outer-scope schemaLabel (bound to
selectedNode.type); fix by making the fallback depend on the passed nodeType:
either add a second parameter (e.g., schemaLabel) to getNodeLabel and pass the
appropriate label from callers (including where you call
getNodeLabel(selectedNode.type)), or compute the fallback inside getNodeLabel
using nodeType (e.g., lookup nodeConfigs[nodeType]?.label or pluginStore label)
so the function no longer reads the outer selectedNode.type implicitly.
- NodeSettings.tsx: アニメーション/モデルフェッチで nodeConfigs フォールバックも検査 - NodeSettings.tsx: checkbox で field.defaultValue を反映 - NodeSettings.tsx: 動的モデルセレクトを明示的キー一覧 (model, llm_model) に変更 - NodeSettings.tsx: getNodeLabel の fallback を nodeType パラメータから導出 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/components/panels/NodeSettings.tsx (1)
2090-2104:⚠️ Potential issue | 🟡 MinorShared
animationInputRef/modelInputRefwill misroute uploads when a manifest defines multiple fields of the same type.Both
animation-fileandmodel-filefield renderers share a singleuseRefacross all instances. When a node has twoanimation-filefields (now possible for any community plugin via manifest), React assigns the ref to the last rendered<input type="file">; clicking the first field's "Upload FBX" button silently triggers the second input'sonChange, which uses the second field'sfield.keyclosure — writing the uploaded URL to the wrong config key.The cleanest fix without hooks-in-loops is a ref-map pattern:
🐛 Proposed fix — keyed ref map
- const animationInputRef = useRef<HTMLInputElement>(null); - const modelInputRef = useRef<HTMLInputElement>(null); + const animationInputRefs = useRef<Record<string, HTMLInputElement | null>>({}); + const modelInputRefs = useRef<Record<string, HTMLInputElement | null>>({});Inside the
animation-filecase:- <input type="file" ref={animationInputRef} ... /> + <input + type="file" + ref={(el) => { animationInputRefs.current[field.key] = el; }} + ... + /> <button - onClick={() => animationInputRef.current?.click()} + onClick={() => animationInputRefs.current[field.key]?.click()} ... >Apply the same pattern to the
model-filecase withmodelInputRefs.Also applies to: 2171-2185
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/panels/NodeSettings.tsx` around lines 2090 - 2104, The file inputs for animation-file and model-file share single refs (animationInputRef, modelInputRef) causing uploads to be routed to the wrong field when multiple same-type fields exist; replace each single ref with a keyed ref map (e.g., animationInputRefs.current: Record<string, HTMLInputElement | null>) and set the input ref with a callback that assigns animationInputRefs.current[field.key] = el, update the button onClick to call animationInputRefs.current[field.key]?.click(), and keep the existing onChange logic but ensure it reads the file from e.target.files[0] and calls handleAnimationUpload(file, field.key) (and clears the input value) — apply the same keyed-ref pattern for modelInputRefs and handleModelUpload in the model-file renderer.
🧹 Nitpick comments (1)
apps/web/components/panels/NodeSettings.tsx (1)
1864-1868:schemaLabelis dead code — remove it.
schemaLabelis computed but never referenced; the JSX at line 2497 already callsgetNodeLabel(selectedNode.type), which covers the same fallback chain (plugin store →nodeConfigs→ raw type) plus i18n priority.♻️ Proposed removal
- const schemaLabel = - plugin?.ui?.label ?? - plugin?.name ?? - nodeConfigs[selectedNode.type]?.label ?? - selectedNode.type;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/components/panels/NodeSettings.tsx` around lines 1864 - 1868, Remove the dead variable schemaLabel (computed from plugin?.ui?.label, plugin?.name, nodeConfigs[selectedNode.type]?.label, selectedNode.type) since it is never used; update NodeSettings.tsx by deleting the const schemaLabel declaration and any now-unused references to plugin/nodeConfigs solely related to that computation, and rely on the existing getNodeLabel(selectedNode.type) call already used in the JSX to provide the label and i18n fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 1948-1975: The select branches for dynamic model/VOICEVOX/regular
selects ignore field.defaultValue and use value ?? ""; update each select's
value prop to use the same fallback pattern as other inputs (value ??
field.defaultValue ?? "") so manifest-defined defaults are honored, keeping the
existing onChange handler (handleChange) and option rendering (modelOptions /
VOICEVOX options / normalized options) intact.
- Around line 1951-1958: The code assumes provider plugin models live at
providerPlugin?.config?.model?.options which hardcodes the "model" key; update
NodeSettings.tsx (around providerPlugin, getPluginById, normalizeOptions) to
read the provider-config key dynamically: if the consumer field (when key ===
"llm_model" and dependsOn === "llm_provider") exposes an optionsFromField (or
similar) use that to index providerPlugin.config, otherwise attempt a small
prioritized list of keys such as ["model","llm_model","available_models"] before
falling back to LLM_MODEL_OPTIONS; ensure normalizeOptions is run on the
discovered array and keep the same fallback behavior to LLM_MODEL_OPTIONS.
---
Outside diff comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 2090-2104: The file inputs for animation-file and model-file share
single refs (animationInputRef, modelInputRef) causing uploads to be routed to
the wrong field when multiple same-type fields exist; replace each single ref
with a keyed ref map (e.g., animationInputRefs.current: Record<string,
HTMLInputElement | null>) and set the input ref with a callback that assigns
animationInputRefs.current[field.key] = el, update the button onClick to call
animationInputRefs.current[field.key]?.click(), and keep the existing onChange
logic but ensure it reads the file from e.target.files[0] and calls
handleAnimationUpload(file, field.key) (and clears the input value) — apply the
same keyed-ref pattern for modelInputRefs and handleModelUpload in the
model-file renderer.
---
Nitpick comments:
In `@apps/web/components/panels/NodeSettings.tsx`:
- Around line 1864-1868: Remove the dead variable schemaLabel (computed from
plugin?.ui?.label, plugin?.name, nodeConfigs[selectedNode.type]?.label,
selectedNode.type) since it is never used; update NodeSettings.tsx by deleting
the const schemaLabel declaration and any now-unused references to
plugin/nodeConfigs solely related to that computation, and rely on the existing
getNodeLabel(selectedNode.type) call already used in the JSX to provide the
label and i18n fallback.
| if (field.dynamic && field.dependsOn && (field.key === "model" || field.key === "llm_model")) { | ||
| const dependsOnValue = localConfig[field.dependsOn] as string; | ||
| // Try plugin store first: provider "openai" → plugin "openai-llm" | ||
| const providerPluginId = `${dependsOnValue}-llm`; | ||
| const providerPlugin = getPluginById(providerPluginId); | ||
| const dynamicModelOptions = normalizeOptions(providerPlugin?.config?.model?.options); | ||
| // Fall back to hardcoded LLM_MODEL_OPTIONS | ||
| const modelOptions: { label: string; value: string | number }[] = | ||
| dynamicModelOptions && dynamicModelOptions.length > 0 | ||
| ? dynamicModelOptions | ||
| : LLM_MODEL_OPTIONS[dependsOnValue] || []; | ||
|
|
||
| if (modelOptions.length > 0) { | ||
| return ( | ||
| <select | ||
| value={(value ?? "") as string} | ||
| onChange={(e) => handleChange(field.key, e.target.value)} | ||
| style={inputStyle} | ||
| > | ||
| <option value="">Select a model...</option> | ||
| {modelOptions.map((opt) => ( | ||
| <option key={String(opt.value)} value={String(opt.value)}> | ||
| {opt.label} | ||
| </option> | ||
| ))} | ||
| </select> | ||
| ); | ||
| } |
There was a problem hiding this comment.
select cases ignore field.defaultValue — inconsistent with all other field types.
text, number, textarea, and checkbox all use value ?? field.defaultValue ?? <empty>, but every select branch (LLM model, VOICEVOX, and regular) uses value ?? "". For manifest-defined fields with a defaultValue on a select, the default is silently dropped and the picker renders empty until the user explicitly chooses.
🐛 Proposed fix — all three select branches
// LLM model select (line ~1963)
- value={(value ?? "") as string}
+ value={(value ?? field.defaultValue ?? "") as string}
// VOICEVOX select (line ~2024)
- value={(value ?? "") as string}
+ value={(value ?? field.defaultValue ?? "") as string}
// Regular select (line ~2043)
- value={(value ?? "") as string}
+ value={(value ?? field.defaultValue ?? "") as string}Also applies to: 2022-2037, 2041-2053
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/components/panels/NodeSettings.tsx` around lines 1948 - 1975, The
select branches for dynamic model/VOICEVOX/regular selects ignore
field.defaultValue and use value ?? ""; update each select's value prop to use
the same fallback pattern as other inputs (value ?? field.defaultValue ?? "") so
manifest-defined defaults are honored, keeping the existing onChange handler
(handleChange) and option rendering (modelOptions / VOICEVOX options /
normalized options) intact.
| const providerPluginId = `${dependsOnValue}-llm`; | ||
| const providerPlugin = getPluginById(providerPluginId); | ||
| const dynamicModelOptions = normalizeOptions(providerPlugin?.config?.model?.options); | ||
| // Fall back to hardcoded LLM_MODEL_OPTIONS | ||
| const modelOptions: { label: string; value: string | number }[] = | ||
| dynamicModelOptions && dynamicModelOptions.length > 0 | ||
| ? dynamicModelOptions | ||
| : LLM_MODEL_OPTIONS[dependsOnValue] || []; |
There was a problem hiding this comment.
providerPlugin?.config?.model hardcodes "model" — community LLM provider plugins must follow this undocumented convention.
When a consumer field has key === "llm_model" and dependsOn === "llm_provider", the dynamic options are still fetched from providerPlugin.config?.model?.options (hardcoded "model"). A community LLM plugin that registers its models under any other config field key (e.g., "llm_model" or "available_models") will silently fall back to LLM_MODEL_OPTIONS, making the manifest-driven intent unreachable.
Consider documenting the "model" field name as a required convention for LLM provider plugins, or generalise the lookup so consumer fields can declare which provider-config key to read from (e.g., an optionsFromField property on ConfigField).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/components/panels/NodeSettings.tsx` around lines 1951 - 1958, The
code assumes provider plugin models live at
providerPlugin?.config?.model?.options which hardcodes the "model" key; update
NodeSettings.tsx (around providerPlugin, getPluginById, normalizeOptions) to
read the provider-config key dynamically: if the consumer field (when key ===
"llm_model" and dependsOn === "llm_provider") exposes an optionsFromField (or
similar) use that to index providerPlugin.config, otherwise attempt a small
prioritized list of keys such as ["model","llm_model","available_models"] before
falling back to LLM_MODEL_OPTIONS; ensure normalizeOptions is run on the
discovered array and keep the same fallback behavior to LLM_MODEL_OPTIONS.
- NodeSettings.tsx: file input ref を keyed ref map に変更(複数同一型フィールド対応) - NodeSettings.tsx: 未使用の schemaLabel 変数を削除 - NodeSettings.tsx: 全 select/password の value に field.defaultValue フォールバック追加 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- プラグイン設定の動的レンダリング化 (#93) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
概要
新規プラグイン追加時にフロントエンドのコード変更が一切不要になるよう、ハードコードされた設定マップを plugin store からの動的読み込みに変更しました。
ConfigField型を拡張し、全カスタムフィールド型(prompt-builder,input-list,animation-file,model-file,png-expression-map等)をサポートconfigUtils.tsを新規作成: manifest config → NodeField 変換、showWhen 評価ユーティリティNodeSettings.tsxを plugin store 優先の動的スキーマ解決に変更(既存nodeConfigsはフォールバックとして維持)FieldSelectorNode/DataPreviewPopupの出力フィールドを plugin store から動的取得Canvas.tsxの動的ポート生成を manifest の config フィールド型による汎用判定に変更manifest.jsonにカスタムフィールド型とshowWhen条件を追加変更の動機
コミュニティからのプラグインPR(例: #82 minimax-llm)で、
plugins/フォルダ内の変更だけでなくNodeSettings.tsx,Canvas.tsx,FieldSelectorNode.tsx等の複数フロントエンドファイルへの登録追加が必要だった。この変更により、新規プラグインはmanifest.json+node.tsのみで完結する。テスト計画
npm run lintでエラーなし確認npm testで変更起因の失敗なし確認🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements