feat: MVP mark advanced input features

This commit is contained in:
Rizumu Ayaka
2026-01-20 17:33:53 +08:00
parent b5f91977c8
commit 15cd0574f2
4 changed files with 298 additions and 6 deletions

View File

@@ -68,7 +68,7 @@
<script setup lang="ts">
import type { TooltipOptions } from 'primevue'
import { computed, onErrorCaptured, ref, toValue } from 'vue'
import { computed, onErrorCaptured, provide, ref, toValue } from 'vue'
import type { Component } from 'vue'
import type {
@@ -77,6 +77,7 @@ import type {
} from '@/composables/graph/useGraphNodeManager'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { st } from '@/i18n'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex'
@@ -88,7 +89,12 @@ import {
shouldExpand,
shouldRenderAsVue
} from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'
import { app } from '@/scripts/app'
import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget'
import {
getLocatorIdFromNodeData,
getNodeByLocatorId
} from '@/utils/graphTraversalUtil'
import { cn } from '@/utils/tailwindUtil'
import InputSlot from './InputSlot.vue'
@@ -103,6 +109,18 @@ const { shouldHandleNodePointerEvents, forwardEventToCanvas } =
useCanvasInteractions()
const { bringNodeToFront } = useNodeZIndex()
// Get the actual LGraphNode for providing to child components
const lgraphNode = computed((): LGraphNode | null => {
if (!nodeData) return null
const locatorId = getLocatorIdFromNodeData(nodeData)
const graph = app.rootGraph
if (!graph) return null
return getNodeByLocatorId(graph, locatorId) ?? null
})
// Provide node to child components for accessing advanced overrides
provide<LGraphNode | null>('node', lgraphNode.value)
function handleWidgetPointerEvent(event: PointerEvent) {
if (shouldHandleNodePointerEvents.value) return
event.stopPropagation()
@@ -174,7 +192,9 @@ const processedWidgets = computed((): ProcessedWidget[] => {
label: widget.label,
nodeType: widget.nodeType,
options: widgetOptions,
spec: widget.spec
spec: widget.spec,
advanced: widget.options?.advanced,
hidden: widget.options?.hidden
}
function updateHandler(value: WidgetValue) {

View File

@@ -1,24 +1,45 @@
<script setup lang="ts">
import { inject } from 'vue'
import { computed, inject } from 'vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useAdvancedWidgetOverridesStore } from '@/stores/workspace/advancedWidgetOverridesStore'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import { cn } from '@/utils/tailwindUtil'
defineProps<{
const { widget } = defineProps<{
widget: Pick<
SimplifiedWidget<string | number | undefined>,
'name' | 'label' | 'borderStyle'
'name' | 'label' | 'borderStyle' | 'advanced'
>
}>()
const hideLayoutField = inject<boolean>('hideLayoutField', false)
const node = inject<LGraphNode | null>('node', null)
const advancedOverridesStore = useAdvancedWidgetOverridesStore()
const isAdvanced = computed(() => {
if (!node) return !!widget.advanced
return advancedOverridesStore.getAdvancedState(node, {
name: widget.name,
options: { advanced: widget.advanced }
} as any)
})
function toggleAdvanced() {
if (!node) return
advancedOverridesStore.toggleAdvanced(node, widget.name)
}
</script>
<template>
<div
class="grid grid-cols-subgrid min-w-0 justify-between gap-1 text-node-component-slot-text"
>
<div v-if="!hideLayoutField" class="truncate content-center-safe">
<div
v-if="!hideLayoutField"
class="truncate content-center-safe flex items-center gap-2"
>
<template v-if="widget.name">
{{ widget.label || widget.name }}
</template>
@@ -39,5 +60,15 @@ const hideLayoutField = inject<boolean>('hideLayoutField', false)
<slot />
</div>
</div>
<button
:class="
cn(
'absolute right-1 hover:scale-150 size-2 rounded-full p-0 m-0 ring-0 border-none z-10',
isAdvanced ? 'bg-green-500' : 'bg-gray-500'
)
"
@click.stop="toggleAdvanced"
/>
</div>
</template>

View File

@@ -0,0 +1,235 @@
import { defineStore } from 'pinia'
import { computed, ref, watch } from 'vue'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import type { NodeLocatorId } from '@/types/nodeIdentification'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
/**
* Unique identifier for a widget's advanced override.
*/
interface AdvancedWidgetOverrideId {
nodeLocatorId: NodeLocatorId
widgetName: string
}
/**
* Storage format for persisted advanced widget overrides.
* Stored in workflow.extra.advancedWidgetOverrides.
*/
interface AdvancedWidgetOverridesStorage {
overrides: AdvancedWidgetOverrideId[]
}
/**
* Store for managing advanced widget status overrides.
*
* Users can manually mark/unmark widgets as advanced, and this preference
* is stored per-workflow. This allows customization of which widgets
* appear in the advanced section.
*
* Design decisions:
* - Scope: Per-workflow (not global user preference)
* - Identifier: node locator ID + widget.name
* - Persistence: Stored in workflow.extra.advancedWidgetOverrides
* - Override logic: User override takes precedence over backend value
*/
export const useAdvancedWidgetOverridesStore = defineStore(
'advancedWidgetOverrides',
() => {
const workflowStore = useWorkflowStore()
const canvasStore = useCanvasStore()
const overriddenIds = ref<string[]>([])
/**
* Generate a unique string key for an override ID.
*/
function getOverrideKey(id: AdvancedWidgetOverrideId): string {
return JSON.stringify([id.nodeLocatorId, id.widgetName])
}
/**
* Parse an override key back into an AdvancedWidgetOverrideId.
*/
function parseOverrideKey(key: string): AdvancedWidgetOverrideId | null {
try {
const [nodeLocatorId, widgetName] = JSON.parse(key) as [string, string]
if (!nodeLocatorId || !widgetName) return null
return { nodeLocatorId, widgetName }
} catch {
return null
}
}
function createOverrideId(
node: LGraphNode,
widgetName: string
): AdvancedWidgetOverrideId {
return {
nodeLocatorId: workflowStore.nodeToNodeLocatorId(node),
widgetName
}
}
/**
* Load overrides from the current workflow's extra data.
*/
function loadFromWorkflow() {
const graph = app.rootGraph
if (!graph) {
overriddenIds.value = []
return
}
try {
const storedData = graph.extra?.advancedWidgetOverrides as
| AdvancedWidgetOverridesStorage
| undefined
if (storedData?.overrides) {
const keys = storedData.overrides
.filter((override) => override.nodeLocatorId && override.widgetName)
.map((override) => getOverrideKey(override))
overriddenIds.value = keys
} else {
overriddenIds.value = []
}
} catch (error) {
console.error(
'Failed to load advanced widget overrides from workflow:',
error
)
overriddenIds.value = []
}
}
/**
* Save overrides to the current workflow's extra data.
* Marks the workflow as modified.
*/
function saveToWorkflow() {
const graph = app.rootGraph
if (!graph) return
try {
const overrides: AdvancedWidgetOverrideId[] = overriddenIds.value
.map(parseOverrideKey)
.filter((id): id is AdvancedWidgetOverrideId => id !== null)
const data: AdvancedWidgetOverridesStorage = { overrides }
graph.extra ??= {}
graph.extra.advancedWidgetOverrides = data
canvasStore.canvas?.setDirty(true, true)
} catch (error) {
console.error(
'Failed to save advanced widget overrides to workflow:',
error
)
}
}
/**
* Get the resolved advanced state for a widget.
* Returns: user override if exists, otherwise backend value.
*/
function getAdvancedState(node: LGraphNode, widget: IBaseWidget): boolean {
const key = getOverrideKey(createOverrideId(node, widget.name))
const isOverridden = overriddenIds.value.includes(key)
if (isOverridden) {
return true
}
return !!widget.options?.advanced
}
/**
* Toggle the advanced override for a widget.
* If the widget is already marked as advanced (by backend or override),
* toggling removes the override (falls back to backend).
* If not marked as advanced, toggling adds it to the override list.
*/
function toggleAdvanced(node: LGraphNode, widgetName: string) {
const id = createOverrideId(node, widgetName)
const key = getOverrideKey(id)
if (overriddenIds.value.includes(key)) {
overriddenIds.value = overriddenIds.value.filter((k) => k !== key)
} else {
overriddenIds.value.push(key)
}
saveToWorkflow()
}
/**
* Check if a widget has an active override (user marked as advanced).
*/
function isOverridden(node: LGraphNode, widgetName: string): boolean {
const key = getOverrideKey(createOverrideId(node, widgetName))
return overriddenIds.value.includes(key)
}
/**
* Clear all overrides for the current workflow.
*/
function clearOverrides() {
overriddenIds.value = []
saveToWorkflow()
}
/**
* Remove invalid overrides (where node or widget no longer exists).
*/
function pruneInvalidOverrides() {
const graph = app.rootGraph
if (!graph) return
const validKeys: Set<string> = new Set()
graph.nodes?.forEach((node) => {
node.widgets?.forEach((widget) => {
const id = createOverrideId(node as LGraphNode, widget.name)
const key = getOverrideKey(id)
validKeys.add(key)
})
})
const filteredIds = overriddenIds.value.filter((key) =>
validKeys.has(key)
)
if (filteredIds.length !== overriddenIds.value.length) {
overriddenIds.value = filteredIds
saveToWorkflow()
}
}
watch(
() => workflowStore.activeWorkflow?.path,
() => {
loadFromWorkflow()
},
{ immediate: true }
)
return {
// State
overriddenIds: computed(() => overriddenIds.value),
// Actions
getAdvancedState,
toggleAdvanced,
isOverridden,
clearOverrides,
pruneInvalidOverrides,
loadFromWorkflow,
saveToWorkflow
}
}
)

View File

@@ -52,6 +52,12 @@ export interface SimplifiedWidget<
borderStyle?: string
/** Whether this is an advanced widget */
advanced?: boolean
/** Whether this widget is hidden */
hidden?: boolean
/** Callback fired when value changes */
callback?: (value: T) => void