mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-06-25 09:09:14 +00:00
Compare commits
22 Commits
DynamicGro
...
drjkl/now-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28f39e2672 | ||
|
|
b058be561c | ||
|
|
cac8db09cc | ||
|
|
1b815ab260 | ||
|
|
0ef3ba2d20 | ||
|
|
0e6c1bdc9e | ||
|
|
7090f4b6d0 | ||
|
|
1547db1580 | ||
|
|
a588de6b92 | ||
|
|
67442d8180 | ||
|
|
6fa43949ec | ||
|
|
b27c2cfca2 | ||
|
|
bb013a5e74 | ||
|
|
043fd943f7 | ||
|
|
ddda65a39d | ||
|
|
7992177927 | ||
|
|
e9c61493b3 | ||
|
|
bae159cd1d | ||
|
|
62284c9abc | ||
|
|
b179a3f4f3 | ||
|
|
62f58354b4 | ||
|
|
c847cf06c1 |
@@ -5,10 +5,9 @@ import type {
|
||||
LGraph,
|
||||
LGraphNode
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type {
|
||||
ComfyWorkflowJSON,
|
||||
NodeId
|
||||
} from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { NodeId, NodeIdInput } from '@/types/nodeId'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import { DefaultGraphPositions } from '@e2e/fixtures/constants/defaultGraphPositions'
|
||||
import type { Position, Size } from '@e2e/fixtures/types'
|
||||
@@ -42,11 +41,12 @@ export class NodeOperationsHelper {
|
||||
}
|
||||
|
||||
async getSelectedNodeIds(): Promise<NodeId[]> {
|
||||
return await this.page.evaluate(() => {
|
||||
const ids = await this.page.evaluate(() => {
|
||||
const selected = window.app?.canvas?.selected_nodes
|
||||
if (!selected) return []
|
||||
return Object.keys(selected).map(Number)
|
||||
return Object.keys(selected)
|
||||
})
|
||||
return ids.map(asNodeId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +114,7 @@ export class NodeOperationsHelper {
|
||||
return this.getNodeRefById(id)
|
||||
}
|
||||
|
||||
async getNodeRefById(id: NodeId): Promise<NodeReference> {
|
||||
async getNodeRefById(id: NodeIdInput): Promise<NodeReference> {
|
||||
return new NodeReference(id, this.comfyPage)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import type { SerialisableLLink } from '@/lib/litegraph/src/types/serialisation'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { NodeId, NodeIdInput } from '@/types/nodeId'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import type { Position, Size } from '@e2e/fixtures/types'
|
||||
import { VueNodeFixture } from '@e2e/fixtures/utils/vueNodeFixtures'
|
||||
@@ -298,10 +299,13 @@ class NodeWidgetReference {
|
||||
}
|
||||
}
|
||||
export class NodeReference {
|
||||
readonly id: NodeId
|
||||
constructor(
|
||||
readonly id: NodeId,
|
||||
id: NodeIdInput,
|
||||
readonly comfyPage: ComfyPage
|
||||
) {}
|
||||
) {
|
||||
this.id = asNodeId(id)
|
||||
}
|
||||
async exists(): Promise<boolean> {
|
||||
return await this.comfyPage.page.evaluate((id) => {
|
||||
const node = window.app!.canvas.graph!.getNodeById(id)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mergeTests } from '@playwright/test'
|
||||
|
||||
import type { NodeError } from '@/schemas/apiSchema'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import {
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture
|
||||
@@ -12,7 +13,7 @@ import { webSocketFixture } from '@e2e/fixtures/ws'
|
||||
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
const VALIDATION_ERROR_NODE_ID = '1'
|
||||
const VALIDATION_ERROR_NODE_ID = asNodeId('1')
|
||||
const VALIDATION_ERROR_MESSAGE = 'Required input is missing: source'
|
||||
const PARTIAL_EXECUTION_ROOT_NODE_IDS = ['1', '4']
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ test.describe('Graph', { tag: ['@smoke', '@canvas'] }, () => {
|
||||
// Count links from Switch(CFG) to node 85 cfg (should be 1, not 2)
|
||||
let cfgLinkToNode85Count = 0
|
||||
for (const link of subgraph.links.values()) {
|
||||
if (link.origin_id === 120 && link.target_id === 85)
|
||||
if (link.origin_id === '120' && link.target_id === '85')
|
||||
cfgLinkToNode85Count++
|
||||
}
|
||||
|
||||
|
||||
@@ -99,10 +99,10 @@ test.describe('Node replacement', { tag: ['@node', '@ui'] }, () => {
|
||||
expect(
|
||||
ksampler?.id,
|
||||
'Replaced node should keep the original id'
|
||||
).toBe(1)
|
||||
).toBe('1')
|
||||
|
||||
const linkFromReplacedToDecode = workflow.links?.find(
|
||||
(l) => l[1] === 1 && l[3] === 2
|
||||
(l) => l[1] === '1' && l[3] === '2'
|
||||
)
|
||||
expect(
|
||||
linkFromReplacedToDecode,
|
||||
|
||||
@@ -31,7 +31,7 @@ test.describe('Preview as Text node', () => {
|
||||
})
|
||||
|
||||
const previewEntry = Object.values(apiWorkflow).find(
|
||||
(n) => n.class_type === 'PreviewAny'
|
||||
(n) => n?.class_type === 'PreviewAny'
|
||||
)
|
||||
expect(previewEntry).toBeDefined()
|
||||
|
||||
|
||||
@@ -3,16 +3,17 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { Position } from '@e2e/fixtures/types'
|
||||
|
||||
type NodeSnapshot = { id: number } & Position
|
||||
type NodeSnapshot = { id: NodeId } & Position
|
||||
|
||||
async function getAllNodePositions(
|
||||
comfyPage: ComfyPage
|
||||
): Promise<NodeSnapshot[]> {
|
||||
return comfyPage.page.evaluate(() =>
|
||||
window.app!.graph.nodes.map((n) => ({
|
||||
id: n.id as number,
|
||||
id: n.id,
|
||||
x: n.pos[0],
|
||||
y: n.pos[1]
|
||||
}))
|
||||
@@ -21,7 +22,7 @@ async function getAllNodePositions(
|
||||
|
||||
async function getNodePosition(
|
||||
comfyPage: ComfyPage,
|
||||
nodeId: number
|
||||
nodeId: NodeId
|
||||
): Promise<Position | undefined> {
|
||||
return comfyPage.page.evaluate((targetNodeId) => {
|
||||
const node = window.app!.graph.nodes.find((n) => n.id === targetNodeId)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
import { createMockJob } from '@e2e/fixtures/helpers/AssetsHelper'
|
||||
import type { JobDetail } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
|
||||
/**
|
||||
* Expanded folder view must drop output records that resolve to the same
|
||||
@@ -12,7 +13,7 @@ import type { JobDetail } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
*/
|
||||
|
||||
const STACK_JOB_ID = 'job-output-dedupe'
|
||||
const COVER_NODE_ID = '9'
|
||||
const COVER_NODE_ID = asNodeId('9')
|
||||
const COVER_FILENAME = 'cover_00001_.png'
|
||||
const DUPLICATE_FILENAME = 'duplicate_00002_.png'
|
||||
const DISTINCT_FILENAMES = ['distinct_00003_.png', 'distinct_00004_.png']
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
JobDetail,
|
||||
RawJobListItem
|
||||
} from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
|
||||
// Legacy coverage backed by AssetsHelper's shadow backend. New assets-sidebar
|
||||
// browser coverage should use typed route mocks in assetsSidebarTab.spec.ts.
|
||||
@@ -71,7 +72,7 @@ const SAMPLE_IMPORTED_FILES = [
|
||||
const JOB_GAMMA_DETAIL: JobDetail = {
|
||||
...SAMPLE_JOBS[2],
|
||||
outputs: {
|
||||
'3': {
|
||||
[asNodeId('3')]: {
|
||||
images: [
|
||||
{
|
||||
filename: 'abstract_art.png',
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
JobDetail,
|
||||
RawJobListItem
|
||||
} from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
|
||||
const test = mergeTests(comfyPageFixture, jobsRouteFixture)
|
||||
|
||||
@@ -72,7 +73,7 @@ const multiOutputJob = createRouteMockJob({
|
||||
const multiOutputJobDetail: JobDetail = {
|
||||
...multiOutputJob,
|
||||
outputs: {
|
||||
'3': {
|
||||
[asNodeId('3')]: {
|
||||
images: [
|
||||
{
|
||||
filename: 'multi-output-a.png',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
interface SubgraphNodePosition {
|
||||
|
||||
@@ -568,10 +568,7 @@ test.describe('Subgraph Serialization', { tag: ['@subgraph'] }, () => {
|
||||
const result = await comfyPage.page.evaluate(() => {
|
||||
const graph = window.app!.canvas.graph!
|
||||
const allGraphs = [graph, ...graph.subgraphs.values()]
|
||||
const allIds = allGraphs
|
||||
.flatMap((g) => g._nodes)
|
||||
.map((n) => n.id)
|
||||
.filter((id): id is number => typeof id === 'number')
|
||||
const allIds = allGraphs.flatMap((g) => g._nodes).map((n) => n.id)
|
||||
|
||||
return { allIds, uniqueCount: new Set(allIds).size }
|
||||
})
|
||||
@@ -589,11 +586,10 @@ test.describe('Subgraph Serialization', { tag: ['@subgraph'] }, () => {
|
||||
const graph = window.app!.canvas.graph!
|
||||
return graph._nodes
|
||||
.map((n) => n.id)
|
||||
.filter((id): id is number => typeof id === 'number')
|
||||
.sort((a, b) => a - b)
|
||||
.sort((a, b) => Number(a) - Number(b))
|
||||
})
|
||||
|
||||
expect(rootIds).toEqual([1, 2, 5])
|
||||
expect(rootIds).toEqual(['1', '2', '5'])
|
||||
})
|
||||
|
||||
test('Promoted widget tuples are stable after full page reload boot path', async ({
|
||||
@@ -633,9 +629,9 @@ test.describe('Subgraph Serialization', { tag: ['@subgraph'] }, () => {
|
||||
)
|
||||
]
|
||||
|
||||
const SENTINEL_IDS = new Set([-1, -10, -20])
|
||||
const isSentinelNodeId = (id: number | string): id is number =>
|
||||
typeof id === 'number' && SENTINEL_IDS.has(id)
|
||||
const SENTINEL_IDS = new Set(['-1', '-10', '-20'])
|
||||
const isSentinelNodeId = (id: number | string): boolean =>
|
||||
SENTINEL_IDS.has(String(id))
|
||||
|
||||
const checkEndpoint = (
|
||||
label: string,
|
||||
@@ -644,7 +640,7 @@ test.describe('Subgraph Serialization', { tag: ['@subgraph'] }, () => {
|
||||
g: typeof graph
|
||||
): string | null => {
|
||||
if (isSentinelNodeId(id)) return null
|
||||
if (typeof id !== 'number' || !g._nodes_by_id[id]) {
|
||||
if (!g.getNodeById(id)) {
|
||||
return `${label}: ${kind} ${id} invalid or not found`
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Locator, Page } from '@playwright/test'
|
||||
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import {
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { Page, Request } from '@playwright/test'
|
||||
|
||||
import type {
|
||||
ComfyApiWorkflow,
|
||||
NodeId
|
||||
} from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { ComfyApiWorkflow } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
||||
import {
|
||||
comfyExpect as expect,
|
||||
@@ -146,7 +144,7 @@ test.describe('Workflow settings', { tag: '@canvas' }, () => {
|
||||
)
|
||||
}
|
||||
|
||||
function ascendingById(ids: NodeId[]): NodeId[] {
|
||||
function ascendingById<T extends string | number>(ids: T[]): T[] {
|
||||
return [...ids].sort((a, b) => Number(a) - Number(b))
|
||||
}
|
||||
|
||||
@@ -197,9 +195,7 @@ test.describe('Workflow settings', { tag: '@canvas' }, () => {
|
||||
// must enumerate the same node set regardless of the sort flag.
|
||||
const apiPrompt: ComfyApiWorkflow =
|
||||
await comfyPage.workflow.getExportedWorkflow({ api: true })
|
||||
expect(ascendingById(Object.keys(apiPrompt).map(Number))).toEqual(
|
||||
expectedIds
|
||||
)
|
||||
expect(ascendingById(Object.keys(apiPrompt))).toEqual(expectedIds)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,12 +13,13 @@ import type {
|
||||
RawJobListItem,
|
||||
zJobsListResponse
|
||||
} from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
|
||||
type JobsListResponse = z.infer<typeof zJobsListResponse>
|
||||
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
const KSAMPLER_NODE = '3'
|
||||
const KSAMPLER_NODE = asNodeId('3')
|
||||
const EXECUTING_CLASS = /outline-node-stroke-executing/
|
||||
|
||||
const QUEUE_ROUTE = /\/api\/jobs\?[^/]*status=in_progress,pending/
|
||||
|
||||
@@ -36,34 +36,34 @@ Adopt an Entity Component System architecture for the graph domain model. This A
|
||||
|
||||
Six entity kinds, each with a branded ID type:
|
||||
|
||||
| Entity Kind | Current Class(es) | Current ID | Branded ID |
|
||||
| ----------- | ------------------------------------------------- | --------------------------- | ----------------- |
|
||||
| Node | `LGraphNode` | `NodeId = number \| string` | `NodeEntityId` |
|
||||
| Link | `LLink` | `LinkId = number` | `LinkEntityId` |
|
||||
| Widget | `BaseWidget` subclasses (25+) | name + parent node | `WidgetEntityId` |
|
||||
| Slot | `SlotBase` / `INodeInputSlot` / `INodeOutputSlot` | index on parent node | `SlotEntityId` |
|
||||
| Reroute | `Reroute` | `RerouteId = number` | `RerouteEntityId` |
|
||||
| Group | `LGraphGroup` | `number` | `GroupEntityId` |
|
||||
| Entity Kind | Current Class(es) | Current ID | Branded ID |
|
||||
| ----------- | ------------------------------------------------- | --------------------------- | ----------- |
|
||||
| Node | `LGraphNode` | `NodeId = number \| string` | `NodeId` |
|
||||
| Link | `LLink` | `LinkId = number` | `LinkId` |
|
||||
| Widget | `BaseWidget` subclasses (25+) | name + parent node | `WidgetId` |
|
||||
| Slot | `SlotBase` / `INodeInputSlot` / `INodeOutputSlot` | index on parent node | `SlotId` |
|
||||
| Reroute | `Reroute` | `RerouteId = number` | `RerouteId` |
|
||||
| Group | `LGraphGroup` | `number` | `GroupId` |
|
||||
|
||||
Subgraphs are not a separate entity kind. A subgraph is a node with a `SubgraphStructure` component. See [Subgraph Boundaries and Widget Promotion](../architecture/subgraph-boundaries-and-promotion.md) for the full design rationale.
|
||||
|
||||
### Branded ID Design
|
||||
|
||||
Each entity kind gets a nominal/branded type wrapping its underlying primitive. The brand prevents accidental cross-kind usage at compile time while remaining structurally compatible with existing ID types:
|
||||
Each entity kind gets a nominal/branded type wrapping its underlying primitive, and the brand prevents accidental cross-kind usage at compile time. `NodeId` and `WidgetId` are branded `string`: node ids are canonicalized from legacy `number | string` values to `string` at load/serialization boundaries (via `asNodeId`), and widget ids are composite path strings. The remaining ids keep `number` as their underlying primitive:
|
||||
|
||||
```ts
|
||||
type NodeEntityId = number & { readonly __brand: 'NodeEntityId' }
|
||||
type LinkEntityId = number & { readonly __brand: 'LinkEntityId' }
|
||||
type WidgetEntityId = number & { readonly __brand: 'WidgetEntityId' }
|
||||
type SlotEntityId = number & { readonly __brand: 'SlotEntityId' }
|
||||
type RerouteEntityId = number & { readonly __brand: 'RerouteEntityId' }
|
||||
type GroupEntityId = number & { readonly __brand: 'GroupEntityId' }
|
||||
type NodeId = string & { readonly __brand: 'NodeId' }
|
||||
type LinkId = number & { readonly __brand: 'LinkId' }
|
||||
type WidgetId = string & { readonly __brand: 'WidgetId' }
|
||||
type SlotId = number & { readonly __brand: 'SlotId' }
|
||||
type RerouteId = number & { readonly __brand: 'RerouteId' }
|
||||
type GroupId = number & { readonly __brand: 'GroupId' }
|
||||
|
||||
// Scope identifier, not an entity ID
|
||||
type GraphId = string & { readonly __brand: 'GraphId' }
|
||||
```
|
||||
|
||||
Widgets and Slots currently lack independent IDs. The ECS will assign synthetic IDs at entity creation time via an auto-incrementing counter (matching the pattern used by `lastNodeId`, `lastLinkId`, etc. in `LGraphState`).
|
||||
Widgets and Slots currently lack independent IDs. A `WidgetId` is a composite path string derived from its `graphId`, parent `NodeId`, and widget name (stable across instances of the same node). Slots get synthetic IDs assigned at entity creation time via an auto-incrementing counter (matching the pattern used by `lastNodeId`, `lastLinkId`, etc. in `LGraphState`).
|
||||
|
||||
### Component Decomposition
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ import { useResolvedSelectedInputs } from '@/components/builder/useResolvedSelec
|
||||
import type { ResolvedSelection } from '@/components/builder/useResolvedSelectedInputs'
|
||||
import type { WidgetId } from '@/types/widgetId'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
|
||||
import {
|
||||
LGraphEventMode,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import Popover from '@/components/ui/Popover.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { extractVueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { UNASSIGNED_NODE_ID } from '@/types/nodeId'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { LGraphEventMode } from '@/lib/litegraph/src/types/globalEnums'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
@@ -75,7 +76,7 @@ const mappedSelections = computed((): WidgetEntry[] => {
|
||||
if (!matchingWidget) return []
|
||||
|
||||
matchingWidget.slotMetadata = undefined
|
||||
matchingWidget.nodeId = String(node.id)
|
||||
matchingWidget.nodeId = node.id
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -139,7 +140,7 @@ async function handleDragDrop() {
|
||||
return false
|
||||
}
|
||||
|
||||
app.dragOverNode = { id: -1, onDragDrop }
|
||||
app.dragOverNode = { id: UNASSIGNED_NODE_ID, onDragDrop }
|
||||
}
|
||||
|
||||
defineExpose({ handleDragDrop })
|
||||
|
||||
@@ -5,6 +5,7 @@ import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import ErrorOverlay from './ErrorOverlay.vue'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
||||
import type { NodeError } from '@/schemas/apiSchema'
|
||||
import type {
|
||||
@@ -153,7 +154,7 @@ describe('ErrorOverlay', () => {
|
||||
|
||||
const executionErrorStore = useExecutionErrorStore()
|
||||
executionErrorStore.lastNodeErrors = {
|
||||
'1': makeNodeError(['Only error'])
|
||||
[asNodeId('1')]: makeNodeError(['Only error'])
|
||||
}
|
||||
executionErrorStore.showErrorOverlay()
|
||||
await nextTick()
|
||||
@@ -190,7 +191,7 @@ describe('ErrorOverlay', () => {
|
||||
|
||||
const executionErrorStore = useExecutionErrorStore()
|
||||
executionErrorStore.lastNodeErrors = {
|
||||
'1': makeNodeError(['Only error'])
|
||||
[asNodeId('1')]: makeNodeError(['Only error'])
|
||||
}
|
||||
executionErrorStore.showErrorOverlay()
|
||||
await nextTick()
|
||||
|
||||
@@ -5,6 +5,7 @@ import { defineComponent, nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import { useErrorOverlayState } from './useErrorOverlayState'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
||||
import { useMissingMediaStore } from '@/platform/missingMedia/missingMediaStore'
|
||||
import type { NodeError } from '@/schemas/apiSchema'
|
||||
@@ -250,7 +251,7 @@ describe('useErrorOverlayState', () => {
|
||||
const executionErrorStore = useExecutionErrorStore()
|
||||
executionErrorStore.lastExecutionError = {
|
||||
prompt_id: 'prompt',
|
||||
node_id: 1,
|
||||
node_id: asNodeId(1),
|
||||
node_type: 'KSampler',
|
||||
executed: [],
|
||||
exception_message: 'CUDA out of memory',
|
||||
@@ -276,7 +277,7 @@ describe('useErrorOverlayState', () => {
|
||||
name: 'image.png',
|
||||
mediaType: 'image',
|
||||
representative: {
|
||||
nodeId: '1',
|
||||
nodeId: asNodeId('1'),
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -285,7 +286,7 @@ describe('useErrorOverlayState', () => {
|
||||
},
|
||||
referencingNodes: [
|
||||
{
|
||||
nodeId: '1',
|
||||
nodeId: asNodeId('1'),
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image'
|
||||
}
|
||||
@@ -312,7 +313,7 @@ describe('useErrorOverlayState', () => {
|
||||
const missingMediaStore = useMissingMediaStore()
|
||||
missingMediaStore.setMissingMedia([
|
||||
{
|
||||
nodeId: '1',
|
||||
nodeId: asNodeId('1'),
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -338,7 +339,7 @@ describe('useErrorOverlayState', () => {
|
||||
{
|
||||
name: 'missing.safetensors',
|
||||
representative: {
|
||||
nodeId: '1',
|
||||
nodeId: asNodeId('1'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
name: 'missing.safetensors',
|
||||
@@ -347,8 +348,8 @@ describe('useErrorOverlayState', () => {
|
||||
isMissing: true
|
||||
},
|
||||
referencingNodes: [
|
||||
{ nodeId: '1', widgetName: 'ckpt_name' },
|
||||
{ nodeId: '2', widgetName: 'ckpt_name' }
|
||||
{ nodeId: asNodeId('1'), widgetName: 'ckpt_name' },
|
||||
{ nodeId: asNodeId('2'), widgetName: 'ckpt_name' }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -420,7 +421,7 @@ describe('useErrorOverlayState', () => {
|
||||
{
|
||||
name: 'first.safetensors',
|
||||
representative: {
|
||||
nodeId: '1',
|
||||
nodeId: asNodeId('1'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
name: 'first.safetensors',
|
||||
@@ -428,12 +429,14 @@ describe('useErrorOverlayState', () => {
|
||||
isAssetSupported: true,
|
||||
isMissing: true
|
||||
},
|
||||
referencingNodes: [{ nodeId: '1', widgetName: 'ckpt_name' }]
|
||||
referencingNodes: [
|
||||
{ nodeId: asNodeId('1'), widgetName: 'ckpt_name' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'second.safetensors',
|
||||
representative: {
|
||||
nodeId: '2',
|
||||
nodeId: asNodeId('2'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
name: 'second.safetensors',
|
||||
@@ -441,7 +444,9 @@ describe('useErrorOverlayState', () => {
|
||||
isAssetSupported: true,
|
||||
isMissing: true
|
||||
},
|
||||
referencingNodes: [{ nodeId: '2', widgetName: 'ckpt_name' }]
|
||||
referencingNodes: [
|
||||
{ nodeId: asNodeId('2'), widgetName: 'ckpt_name' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import DomWidgets from '@/components/graph/DomWidgets.vue'
|
||||
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
|
||||
import { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraph, LGraphNode, asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import type { BaseDOMWidget } from '@/scripts/domWidget'
|
||||
@@ -21,7 +21,7 @@ function createNode(
|
||||
pos: [number, number]
|
||||
) {
|
||||
const node = new LGraphNode(title)
|
||||
node.id = id
|
||||
node.id = asNodeId(id)
|
||||
node.pos = [...pos]
|
||||
node.size = [240, 120]
|
||||
graph.add(node)
|
||||
|
||||
@@ -16,7 +16,7 @@ import { default as DOMPurify } from 'dompurify'
|
||||
import Skeleton from 'primevue/skeleton'
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
|
||||
import type { NodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { linkifyHtml, nl2br } from '@/utils/formatUtil'
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ import {
|
||||
boundsExtractor,
|
||||
useUpstreamValue
|
||||
} from '@/composables/useUpstreamValue'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { Bounds } from '@/renderer/core/layout/types'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import { cn } from '@comfyorg/tailwind-utils'
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ref } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import Load3D from '@/components/load3d/Load3D.vue'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { ComponentWidget } from '@/scripts/domWidget'
|
||||
|
||||
const { load3dState, resolveNodeMock, settingGetMock } = vi.hoisted(() => ({
|
||||
@@ -109,7 +110,8 @@ function renderLoad3D(options: RenderOptions = {}) {
|
||||
widget: (options.widget ?? {
|
||||
node: MOCK_NODE
|
||||
}) as unknown as ComponentWidget<string[]>,
|
||||
nodeId: options.nodeId
|
||||
nodeId:
|
||||
options.nodeId === undefined ? undefined : asNodeId(options.nodeId)
|
||||
},
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
@@ -168,7 +170,7 @@ describe('Load3D', () => {
|
||||
resolveNodeMock.mockReturnValue(MOCK_NODE)
|
||||
renderLoad3D({ widget: {}, nodeId: 42 })
|
||||
|
||||
expect(resolveNodeMock).toHaveBeenCalledWith(42)
|
||||
expect(resolveNodeMock).toHaveBeenCalledWith('42')
|
||||
expect(await screen.findByTestId('load3d-scene')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ import Button from '@/components/ui/button/Button.vue'
|
||||
import { useLoad3d } from '@/composables/useLoad3d'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import { resolveNode } from '@/utils/litegraphUtil'
|
||||
import type { ComponentWidget } from '@/scripts/domWidget'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
|
||||
@@ -22,6 +22,7 @@ vi.mock('@/components/load3d/Load3D.vue', () => ({
|
||||
}))
|
||||
|
||||
import Load3DAdvanced from '@/components/load3d/Load3DAdvanced.vue'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
describe('Load3DAdvanced', () => {
|
||||
it('renders the inner Load3D with all expressive features disabled', () => {
|
||||
@@ -40,7 +41,9 @@ describe('Load3DAdvanced', () => {
|
||||
|
||||
it('forwards widget and nodeId to the inner Load3D', () => {
|
||||
const widget = { node: { id: 'a', type: 'Load3DAdvanced' } }
|
||||
render(Load3DAdvanced, { props: { widget: widget as never, nodeId: 'a' } })
|
||||
render(Load3DAdvanced, {
|
||||
props: { widget: widget as never, nodeId: asNodeId('a') }
|
||||
})
|
||||
expect(lastProps.value?.widget).toEqual(widget)
|
||||
expect(lastProps.value?.nodeId).toBe('a')
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import Load3D from '@/components/load3d/Load3D.vue'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { ComponentWidget } from '@/scripts/domWidget'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import type {
|
||||
JobListItem,
|
||||
JobStatus
|
||||
@@ -154,7 +155,7 @@ export const Queued: Story = {
|
||||
value: 1,
|
||||
max: 1,
|
||||
state: 'running',
|
||||
node_id: '1',
|
||||
node_id: asNodeId('1'),
|
||||
prompt_id: 'p1'
|
||||
}
|
||||
}
|
||||
@@ -204,7 +205,7 @@ export const QueuedParallel: Story = {
|
||||
value: 1,
|
||||
max: 2,
|
||||
state: 'running',
|
||||
node_id: '1',
|
||||
node_id: asNodeId('1'),
|
||||
prompt_id: 'p1'
|
||||
}
|
||||
},
|
||||
@@ -213,7 +214,7 @@ export const QueuedParallel: Story = {
|
||||
value: 1,
|
||||
max: 2,
|
||||
state: 'running',
|
||||
node_id: '2',
|
||||
node_id: asNodeId('2'),
|
||||
prompt_id: 'p2'
|
||||
}
|
||||
}
|
||||
@@ -254,7 +255,7 @@ export const Running: Story = {
|
||||
value: 5,
|
||||
max: 10,
|
||||
state: 'running',
|
||||
node_id: '1',
|
||||
node_id: asNodeId('1'),
|
||||
prompt_id: 'p1'
|
||||
}
|
||||
}
|
||||
@@ -299,7 +300,7 @@ export const QueuedZeroAheadSingleRunning: Story = {
|
||||
value: 1,
|
||||
max: 3,
|
||||
state: 'running',
|
||||
node_id: '1',
|
||||
node_id: asNodeId('1'),
|
||||
prompt_id: 'p1'
|
||||
}
|
||||
}
|
||||
@@ -347,7 +348,7 @@ export const QueuedZeroAheadMultiRunning: Story = {
|
||||
value: 2,
|
||||
max: 5,
|
||||
state: 'running',
|
||||
node_id: '1',
|
||||
node_id: asNodeId('1'),
|
||||
prompt_id: 'p1'
|
||||
}
|
||||
},
|
||||
@@ -356,7 +357,7 @@ export const QueuedZeroAheadMultiRunning: Story = {
|
||||
value: 3,
|
||||
max: 5,
|
||||
state: 'running',
|
||||
node_id: '2',
|
||||
node_id: asNodeId('2'),
|
||||
prompt_id: 'p2'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import PrimeVue from 'primevue/config'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import TabErrors from './TabErrors.vue'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import { useMissingModelStore } from '@/platform/missingModel/missingModelStore'
|
||||
import type { MissingMediaCandidate } from '@/platform/missingMedia/types'
|
||||
import type { MissingModelCandidate } from '@/platform/missingModel/types'
|
||||
@@ -398,7 +399,7 @@ describe('TabErrors.vue', () => {
|
||||
|
||||
it('shows missing model Refresh in the section header when no model is downloadable', async () => {
|
||||
const missingModel = {
|
||||
nodeId: '1',
|
||||
nodeId: asNodeId('1'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
name: 'local-only.safetensors',
|
||||
@@ -429,7 +430,7 @@ describe('TabErrors.vue', () => {
|
||||
missingModel: {
|
||||
missingModelCandidates: [
|
||||
{
|
||||
nodeId: '1',
|
||||
nodeId: asNodeId('1'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
name: 'model-a.safetensors',
|
||||
@@ -438,7 +439,7 @@ describe('TabErrors.vue', () => {
|
||||
isAssetSupported: true
|
||||
},
|
||||
{
|
||||
nodeId: '2',
|
||||
nodeId: asNodeId('2'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
name: 'model-b.safetensors',
|
||||
@@ -460,7 +461,7 @@ describe('TabErrors.vue', () => {
|
||||
|
||||
it('renders missing model display message below the section title', () => {
|
||||
const missingModel = {
|
||||
nodeId: '1',
|
||||
nodeId: asNodeId('1'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
name: 'local-only.safetensors',
|
||||
@@ -483,7 +484,7 @@ describe('TabErrors.vue', () => {
|
||||
|
||||
it('renders missing media display message below the section title', () => {
|
||||
const missingMedia = {
|
||||
nodeId: '3',
|
||||
nodeId: asNodeId('3'),
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -519,7 +520,7 @@ describe('TabErrors.vue', () => {
|
||||
missingMedia: {
|
||||
missingMediaCandidates: [
|
||||
{
|
||||
nodeId: '3',
|
||||
nodeId: asNodeId('3'),
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -527,7 +528,7 @@ describe('TabErrors.vue', () => {
|
||||
isMissing: true
|
||||
},
|
||||
{
|
||||
nodeId: '4',
|
||||
nodeId: asNodeId('4'),
|
||||
nodeType: 'PreviewImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -595,7 +596,7 @@ describe('TabErrors.vue', () => {
|
||||
missingMedia: {
|
||||
missingMediaCandidates: [
|
||||
{
|
||||
nodeId: '3',
|
||||
nodeId: asNodeId('3'),
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -603,7 +604,7 @@ describe('TabErrors.vue', () => {
|
||||
isMissing: true
|
||||
},
|
||||
{
|
||||
nodeId: '4',
|
||||
nodeId: asNodeId('4'),
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -658,7 +659,7 @@ describe('TabErrors.vue', () => {
|
||||
|
||||
it('renders missing model Refresh in the header and Download all in the card when models are downloadable', () => {
|
||||
const missingModel = {
|
||||
nodeId: '1',
|
||||
nodeId: asNodeId('1'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
name: 'downloadable.safetensors',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { MissingNodeType } from '@/types/comfy'
|
||||
|
||||
vi.mock('@/scripts/app', () => ({
|
||||
@@ -166,7 +167,7 @@ function makeModel(
|
||||
) {
|
||||
return {
|
||||
name,
|
||||
nodeId: opts.nodeId ?? '1',
|
||||
nodeId: asNodeId(opts.nodeId ?? '1'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: opts.widgetName ?? 'ckpt_name',
|
||||
isAssetSupported: opts.isAssetSupported ?? false,
|
||||
@@ -185,7 +186,7 @@ function makeMedia(
|
||||
): MissingMediaCandidate {
|
||||
return {
|
||||
name,
|
||||
nodeId: opts.nodeId,
|
||||
nodeId: asNodeId(opts.nodeId),
|
||||
nodeType: opts.nodeType ?? 'LoadImage',
|
||||
widgetName: opts.widgetName ?? 'image',
|
||||
mediaType: 'image',
|
||||
@@ -540,7 +541,7 @@ describe('useErrorGroups', () => {
|
||||
store.lastExecutionError = {
|
||||
prompt_id: 'test-prompt',
|
||||
timestamp: Date.now(),
|
||||
node_id: 5,
|
||||
node_id: asNodeId(5),
|
||||
node_type: 'KSampler',
|
||||
executed: [],
|
||||
exception_type: 'RuntimeError',
|
||||
@@ -577,7 +578,7 @@ describe('useErrorGroups', () => {
|
||||
store.lastExecutionError = {
|
||||
prompt_id: 'test-prompt',
|
||||
timestamp: Date.now(),
|
||||
node_id: 5,
|
||||
node_id: asNodeId(5),
|
||||
node_type: 'KSampler',
|
||||
executed: [],
|
||||
exception_type: 'torch.OutOfMemoryError',
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { MaybeRefOrGetter } from 'vue'
|
||||
|
||||
import { until } from '@vueuse/core'
|
||||
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
@@ -81,7 +82,8 @@ export function useErrorReport(cardSource: MaybeRefOrGetter<ErrorCardData>) {
|
||||
exceptionType: error.exceptionType ?? FALLBACK_EXCEPTION_TYPE,
|
||||
exceptionMessage: error.message,
|
||||
traceback: error.details,
|
||||
nodeId: card.nodeId,
|
||||
nodeId:
|
||||
card.nodeId === undefined ? undefined : asNodeId(card.nodeId),
|
||||
nodeType: card.title,
|
||||
systemStats: systemStatsStore.systemStats,
|
||||
serverLogs: logs,
|
||||
|
||||
@@ -4,7 +4,8 @@ import { computed, reactive, ref, shallowRef, watch } from 'vue'
|
||||
|
||||
import CollapseToggleButton from '@/components/rightSidePanel/layout/CollapseToggleButton.vue'
|
||||
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import AsyncSearchInput from '@/components/ui/search-input/AsyncSearchInput.vue'
|
||||
|
||||
@@ -3,7 +3,8 @@ import { storeToRefs } from 'pinia'
|
||||
import { computed, reactive, ref, shallowRef, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import CollapseToggleButton from '@/components/rightSidePanel/layout/CollapseToggleButton.vue'
|
||||
import AsyncSearchInput from '@/components/ui/search-input/AsyncSearchInput.vue'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
|
||||
@@ -5,6 +5,7 @@ import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { useWidgetValueStore } from '@/stores/widgetValueStore'
|
||||
@@ -145,7 +146,7 @@ describe('WidgetItem', () => {
|
||||
const expectedOptions = {
|
||||
values: ['model_a.safetensors', 'model_b.safetensors']
|
||||
}
|
||||
const id = widgetId('test-graph-id', 1, 'ckpt_name')
|
||||
const id = widgetId('test-graph-id', asNodeId(1), 'ckpt_name')
|
||||
const widget = createMockWidget({ widgetId: id, name: 'ckpt_name' })
|
||||
useWidgetValueStore().registerWidget(id, {
|
||||
type: 'combo',
|
||||
@@ -160,7 +161,7 @@ describe('WidgetItem', () => {
|
||||
})
|
||||
|
||||
it('passes type from widget state to the widget component', () => {
|
||||
const id = widgetId('test-graph-id', 1, 'ckpt_name')
|
||||
const id = widgetId('test-graph-id', asNodeId(1), 'ckpt_name')
|
||||
const widget = createMockWidget({ widgetId: id, type: 'string' })
|
||||
useWidgetValueStore().registerWidget(id, {
|
||||
type: 'combo',
|
||||
@@ -175,7 +176,7 @@ describe('WidgetItem', () => {
|
||||
})
|
||||
|
||||
it('passes name from widget state to the widget component', () => {
|
||||
const id = widgetId('test-graph-id', 1, 'ckpt_name')
|
||||
const id = widgetId('test-graph-id', asNodeId(1), 'ckpt_name')
|
||||
const widget = createMockWidget({ widgetId: id, name: 'source_name' })
|
||||
useWidgetValueStore().registerWidget(id, {
|
||||
type: 'combo',
|
||||
@@ -190,7 +191,7 @@ describe('WidgetItem', () => {
|
||||
})
|
||||
|
||||
it('passes value from widget state to the widget component', () => {
|
||||
const id = widgetId('test-graph-id', 1, 'ckpt_name')
|
||||
const id = widgetId('test-graph-id', asNodeId(1), 'ckpt_name')
|
||||
const widget = createMockWidget({ widgetId: id, value: 'source value' })
|
||||
useWidgetValueStore().registerWidget(id, {
|
||||
type: 'combo',
|
||||
|
||||
@@ -5,7 +5,8 @@ import type { IFuseOptions } from 'fuse.js'
|
||||
|
||||
import type { Positionable } from '@/lib/litegraph/src/interfaces'
|
||||
import type { LGraphGroup } from '@/lib/litegraph/src/LGraphGroup'
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { isLGraphGroup, isLGraphNode } from '@/utils/litegraphUtil'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
|
||||
@@ -24,6 +24,7 @@ import type { PromotedSource } from '@/core/graph/subgraph/promotedInputWidget'
|
||||
import type { WidgetItem } from '@/core/graph/subgraph/promotionUtils'
|
||||
import type { PreviewExposure } from '@/core/schemas/previewExposureSchema'
|
||||
import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces'
|
||||
import { asNodeId, UNASSIGNED_NODE_ID } from '@/types/nodeId'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
@@ -69,7 +70,7 @@ function buildPromotedRows(node: SubgraphNode): PromotedRow[] {
|
||||
if (!widget) return []
|
||||
const source = promotedInputSource(node, input)
|
||||
if (!source) return []
|
||||
const sourceNode = node.subgraph._nodes_by_id[source.nodeId]
|
||||
const sourceNode = node.subgraph._nodes_by_id[asNodeId(source.nodeId)]
|
||||
if (!sourceNode) return []
|
||||
return [{ kind: 'promoted', node: sourceNode, input, widget }]
|
||||
})
|
||||
@@ -116,7 +117,8 @@ function getActivePreviewRows(node: SubgraphNode): PreviewRow[] {
|
||||
const rootGraphId = node.rootGraph.id
|
||||
const exposures = previewExposureStore.getExposures(rootGraphId, hostLocator)
|
||||
return exposures.flatMap((exposure): PreviewRow[] => {
|
||||
const sourceNode = node.subgraph._nodes_by_id[exposure.sourceNodeId]
|
||||
const sourceNode =
|
||||
node.subgraph._nodes_by_id[asNodeId(exposure.sourceNodeId)]
|
||||
if (!sourceNode) return []
|
||||
const realWidget = getPromotableWidgets(sourceNode).find(
|
||||
(candidate) => candidate.name === exposure.sourcePreviewName
|
||||
@@ -248,7 +250,7 @@ function rowDisplayName(row: ActiveRow): string {
|
||||
|
||||
function isRowLinked(row: ActiveRow): boolean {
|
||||
if (row.kind !== 'promoted') return false
|
||||
if (row.node.id === -1) return true
|
||||
if (row.node.id === UNASSIGNED_NODE_ID) return true
|
||||
const source = promotedRowSource(row)
|
||||
return (
|
||||
!!activeNode.value &&
|
||||
@@ -321,7 +323,7 @@ function showAll() {
|
||||
}
|
||||
function hideAll() {
|
||||
for (const row of filteredActive.value) {
|
||||
if (String(row.node.id) === '-1') continue
|
||||
if (row.node.id === UNASSIGNED_NODE_ID) continue
|
||||
demoteRow(row)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +225,7 @@ import {
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import AssetsSidebarGridView from '@/components/sidebar/tabs/AssetsSidebarGridView.vue'
|
||||
import AssetsSidebarListView from '@/components/sidebar/tabs/AssetsSidebarListView.vue'
|
||||
@@ -472,7 +473,7 @@ const galleryItems = computed(() => {
|
||||
filename: asset.name,
|
||||
subfolder: '',
|
||||
type: 'output',
|
||||
nodeId: '0',
|
||||
nodeId: asNodeId('0'),
|
||||
mediaType: mediaType === 'image' ? 'images' : mediaType
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { ResultItemImpl } from '@/stores/queueStore'
|
||||
|
||||
import MediaLightbox from './MediaLightbox.vue'
|
||||
@@ -63,7 +64,7 @@ describe('MediaLightbox', () => {
|
||||
filename: 'image1.jpg',
|
||||
subfolder: 'outputs',
|
||||
type: 'output',
|
||||
nodeId: '123' as NodeId,
|
||||
nodeId: asNodeId('123'),
|
||||
mediaType: 'images',
|
||||
isImage: true,
|
||||
isVideo: false,
|
||||
@@ -75,7 +76,7 @@ describe('MediaLightbox', () => {
|
||||
filename: 'image2.jpg',
|
||||
subfolder: 'outputs',
|
||||
type: 'output',
|
||||
nodeId: '456' as NodeId,
|
||||
nodeId: asNodeId('456'),
|
||||
mediaType: 'images',
|
||||
isImage: true,
|
||||
isVideo: false,
|
||||
@@ -87,7 +88,7 @@ describe('MediaLightbox', () => {
|
||||
filename: 'image3.jpg',
|
||||
subfolder: 'outputs',
|
||||
type: 'output',
|
||||
nodeId: '789' as NodeId,
|
||||
nodeId: asNodeId('789'),
|
||||
mediaType: 'images',
|
||||
isImage: true,
|
||||
isVideo: false,
|
||||
|
||||
@@ -7,7 +7,8 @@ import type { Positionable } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
LGraphEventMode,
|
||||
LGraphNode,
|
||||
Reroute
|
||||
Reroute,
|
||||
asNodeId
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import type { NodeId } from '@/renderer/core/layout/types'
|
||||
@@ -38,7 +39,7 @@ vi.mock('@/lib/litegraph/src/litegraph', async (importOriginal) => {
|
||||
// unmodified — the node accessors filter selectedItems with the real predicate.
|
||||
const makeNode = (mode: LGraphEventMode, id = 1): LGraphNode => {
|
||||
const node = new LGraphNode('Test')
|
||||
node.id = id
|
||||
node.id = asNodeId(id)
|
||||
node.mode = mode
|
||||
return node
|
||||
}
|
||||
@@ -69,7 +70,7 @@ class MockNode implements Positionable {
|
||||
) {
|
||||
this.pos = pos
|
||||
this.size = size
|
||||
this.id = 'mock-node'
|
||||
this.id = asNodeId('mock-node')
|
||||
this.boundingRect = [0, 0, 0, 0]
|
||||
}
|
||||
|
||||
|
||||
@@ -100,14 +100,16 @@ export function useSelectionToolboxPosition(
|
||||
if (item.id == null) continue
|
||||
|
||||
if (shouldRenderVueNodes.value && typeof item.id === 'string') {
|
||||
// Use layout store for Vue nodes (only works with string IDs)
|
||||
// Use layout store for Vue nodes (only works with string IDs).
|
||||
// Stored bounds.y excludes the header, so expand upward by the title
|
||||
// height to reach the visual node top (matching the fallback below).
|
||||
const layout = layoutStore.getNodeLayoutRef(item.id).value
|
||||
if (layout) {
|
||||
allBounds.push([
|
||||
layout.bounds.x,
|
||||
layout.bounds.y,
|
||||
layout.bounds.y - LiteGraph.NODE_TITLE_HEIGHT,
|
||||
layout.bounds.width,
|
||||
layout.bounds.height
|
||||
layout.bounds.height + LiteGraph.NODE_TITLE_HEIGHT
|
||||
])
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useSelectionState } from '@/composables/graph/useSelectionState'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import { TitleMode } from '@/lib/litegraph/src/types/globalEnums'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
|
||||
@@ -5,7 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { installErrorClearingHooks } from '@/composables/graph/useErrorClearingHooks'
|
||||
import { promoteValueWidgetViaSubgraphInput } from '@/core/graph/subgraph/promotionUtils'
|
||||
import { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraph, LGraphNode, asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
createTestSubgraph,
|
||||
createTestSubgraphNode
|
||||
@@ -232,7 +232,7 @@ describe('Widget change error clearing via onWidgetChanged', () => {
|
||||
seedRequiredInputMissingNodeError(store, String(node.id), 'image')
|
||||
mediaStore.setMissingMedia([
|
||||
{
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -371,7 +371,7 @@ describe('installErrorClearingHooks lifecycle', () => {
|
||||
.spyOn(missingModelScan, 'scanNodeModelCandidates')
|
||||
.mockImplementation((_rootGraph, node) => [
|
||||
{
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
nodeType: node.type,
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: false,
|
||||
@@ -448,7 +448,7 @@ describe('onNodeRemoved clears missing asset errors by execution ID', () => {
|
||||
Parameters<typeof modelStore.setMissingModels>[0][number],
|
||||
unknown
|
||||
>({
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: false,
|
||||
@@ -471,7 +471,7 @@ describe('onNodeRemoved clears missing asset errors by execution ID', () => {
|
||||
const interiorNode = new LGraphNode('CheckpointLoaderSimple')
|
||||
subgraph.add(interiorNode)
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 65 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(65) })
|
||||
const rootGraph = subgraphNode.graph as LGraph
|
||||
rootGraph.add(subgraphNode)
|
||||
|
||||
@@ -507,7 +507,7 @@ describe('onNodeRemoved clears missing asset errors by execution ID', () => {
|
||||
const interiorNode = new LGraphNode('LoadImage')
|
||||
subgraph.add(interiorNode)
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 65 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(65) })
|
||||
const rootGraph = subgraphNode.graph as LGraph
|
||||
rootGraph.add(subgraphNode)
|
||||
|
||||
@@ -565,7 +565,7 @@ describe('realtime scan verifies pending cloud candidates', () => {
|
||||
// verifyAssetSupportedCandidates resolves them against the assets store.
|
||||
vi.spyOn(missingModelScan, 'scanNodeModelCandidates').mockReturnValue([
|
||||
{
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: true,
|
||||
@@ -613,7 +613,7 @@ describe('realtime scan verifies pending cloud candidates', () => {
|
||||
vi.spyOn(missingModelScan, 'scanNodeModelCandidates').mockReturnValue([])
|
||||
vi.spyOn(missingMediaScan, 'scanNodeMediaCandidates').mockReturnValue([
|
||||
{
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -656,7 +656,7 @@ describe('realtime scan verifies pending cloud candidates', () => {
|
||||
|
||||
vi.spyOn(missingModelScan, 'scanNodeModelCandidates').mockReturnValue([
|
||||
{
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: true,
|
||||
@@ -702,7 +702,7 @@ describe('realtime verification staleness guards', () => {
|
||||
|
||||
vi.spyOn(missingModelScan, 'scanNodeModelCandidates').mockReturnValue([
|
||||
{
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: true,
|
||||
@@ -753,7 +753,7 @@ describe('realtime verification staleness guards', () => {
|
||||
vi.spyOn(missingModelScan, 'scanNodeModelCandidates').mockReturnValue([])
|
||||
vi.spyOn(missingMediaScan, 'scanNodeMediaCandidates').mockReturnValue([
|
||||
{
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
nodeType: 'LoadImage',
|
||||
widgetName: 'image',
|
||||
mediaType: 'image',
|
||||
@@ -802,7 +802,7 @@ describe('realtime verification staleness guards', () => {
|
||||
|
||||
vi.spyOn(missingModelScan, 'scanNodeModelCandidates').mockReturnValue([
|
||||
{
|
||||
nodeId: String(nodeA.id),
|
||||
nodeId: nodeA.id,
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: true,
|
||||
@@ -863,7 +863,7 @@ describe('scan skips interior of bypassed subgraph containers', () => {
|
||||
const interiorNode = new LGraphNode('CheckpointLoaderSimple')
|
||||
subgraph.add(interiorNode)
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 65 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(65) })
|
||||
subgraphNode.mode = LGraphEventMode.BYPASS
|
||||
const rootGraph = subgraphNode.graph as LGraph
|
||||
rootGraph.add(subgraphNode)
|
||||
@@ -873,7 +873,7 @@ describe('scan skips interior of bypassed subgraph containers', () => {
|
||||
// didn't short-circuit first — return a concrete missing candidate.
|
||||
vi.spyOn(missingModelScan, 'scanNodeModelCandidates').mockReturnValue([
|
||||
{
|
||||
nodeId: `${subgraphNode.id}:${interiorNode.id}`,
|
||||
nodeId: asNodeId(`${subgraphNode.id}:${interiorNode.id}`),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: false,
|
||||
@@ -902,13 +902,13 @@ describe('scan skips interior of bypassed subgraph containers', () => {
|
||||
|
||||
const innerSubgraphNode = createTestSubgraphNode(innerSubgraph, {
|
||||
parentGraph: outerSubgraph,
|
||||
id: 76
|
||||
id: asNodeId(76)
|
||||
})
|
||||
outerSubgraph.add(innerSubgraphNode)
|
||||
|
||||
const outerSubgraphNode = createTestSubgraphNode(outerSubgraph, {
|
||||
parentGraph: rootGraph,
|
||||
id: 205
|
||||
id: asNodeId(205)
|
||||
})
|
||||
rootGraph.add(outerSubgraphNode)
|
||||
|
||||
@@ -982,11 +982,11 @@ describe('clearWidgetRelatedErrors parameter routing', () => {
|
||||
it('clears promoted widget errors by interior execution id', () => {
|
||||
const subgraph = createTestSubgraph()
|
||||
const graph = subgraph.rootGraph
|
||||
const host = createTestSubgraphNode(subgraph, { id: 2 })
|
||||
const host = createTestSubgraphNode(subgraph, { id: asNodeId(2) })
|
||||
graph.add(host)
|
||||
|
||||
const interiorNode = new LGraphNode('CheckpointLoaderSimple')
|
||||
interiorNode.id = 1
|
||||
interiorNode.id = asNodeId(1)
|
||||
subgraph.add(interiorNode)
|
||||
const input = interiorNode.addInput('ckpt_name', 'COMBO')
|
||||
const widget = interiorNode.addWidget(
|
||||
@@ -1007,7 +1007,7 @@ describe('clearWidgetRelatedErrors parameter routing', () => {
|
||||
const missingModelStore = useMissingModelStore()
|
||||
missingModelStore.setMissingModels([
|
||||
{
|
||||
nodeId: '2:1',
|
||||
nodeId: asNodeId('2:1'),
|
||||
nodeType: 'CheckpointLoaderSimple',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: false,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||
import { widgetPromotedSource } from '@/core/graph/subgraph/promotedInputWidget'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import {
|
||||
LGraphEventMode,
|
||||
NodeSlotType
|
||||
@@ -308,7 +309,7 @@ function scheduleAddedNodeScan(node: LGraphNode): void {
|
||||
|
||||
function handleNodeModeChange(
|
||||
localGraph: LGraph,
|
||||
nodeId: number,
|
||||
nodeId: NodeId,
|
||||
oldMode: number,
|
||||
newMode: number
|
||||
): void {
|
||||
@@ -407,7 +408,7 @@ export function installErrorClearingHooks(graph: LGraph): () => void {
|
||||
if (event.type === 'node:property:changed' && event.property === 'mode') {
|
||||
handleNodeModeChange(
|
||||
graph,
|
||||
event.nodeId as number,
|
||||
event.nodeId,
|
||||
event.oldValue as number,
|
||||
event.newValue as number
|
||||
)
|
||||
|
||||
@@ -4,7 +4,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { computed, nextTick, watch } from 'vue'
|
||||
|
||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import { BaseWidget, LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
BaseWidget,
|
||||
LGraph,
|
||||
LGraphNode,
|
||||
asNodeId
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { widgetId } from '@/types/widgetId'
|
||||
import {
|
||||
createTestSubgraph,
|
||||
@@ -224,7 +229,7 @@ describe('Widget slotMetadata reactivity on link disconnect', () => {
|
||||
subgraph.add(interiorNode)
|
||||
subgraph.inputNode.slots[0].connect(interiorInput, interiorNode)
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 123 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(123) })
|
||||
subgraphNode._internalConfigureAfterSlots()
|
||||
const graph = subgraphNode.graph as LGraph
|
||||
graph.add(subgraphNode)
|
||||
@@ -355,7 +360,9 @@ describe('Nested promoted widget mapping', () => {
|
||||
subgraphA.add(innerNode)
|
||||
subgraphA.inputNode.slots[0].connect(innerInput, innerNode)
|
||||
|
||||
const subgraphNodeA = createTestSubgraphNode(subgraphA, { id: 11 })
|
||||
const subgraphNodeA = createTestSubgraphNode(subgraphA, {
|
||||
id: asNodeId(11)
|
||||
})
|
||||
|
||||
const subgraphB = createTestSubgraph({
|
||||
inputs: [{ name: 'b_input', type: '*' }]
|
||||
@@ -364,7 +371,9 @@ describe('Nested promoted widget mapping', () => {
|
||||
subgraphNodeA._internalConfigureAfterSlots()
|
||||
subgraphB.inputNode.slots[0].connect(subgraphNodeA.inputs[0], subgraphNodeA)
|
||||
|
||||
const subgraphNodeB = createTestSubgraphNode(subgraphB, { id: 22 })
|
||||
const subgraphNodeB = createTestSubgraphNode(subgraphB, {
|
||||
id: asNodeId(22)
|
||||
})
|
||||
const graph = subgraphNodeB.graph as LGraph
|
||||
graph.add(subgraphNodeB)
|
||||
|
||||
@@ -401,7 +410,7 @@ describe('Nested promoted widget mapping', () => {
|
||||
subgraph.add(secondNode)
|
||||
subgraph.inputNode.slots[1].connect(secondInput, secondNode)
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 100 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(100) })
|
||||
const graph = subgraphNode.graph as LGraph
|
||||
graph.add(subgraphNode)
|
||||
|
||||
@@ -444,7 +453,7 @@ describe('Promoted widget sourceExecutionId', () => {
|
||||
subgraph.add(interiorNode)
|
||||
subgraph.inputNode.slots[0].connect(interiorInput, interiorNode)
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 65 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(65) })
|
||||
subgraphNode._internalConfigureAfterSlots()
|
||||
const graph = subgraphNode.graph as LGraph
|
||||
graph.add(subgraphNode)
|
||||
@@ -591,7 +600,7 @@ describe('reconcileNodeErrorFlags (via lastNodeErrors watcher)', () => {
|
||||
interiorNode.addInput('value', 'INT')
|
||||
subgraph.add(interiorNode)
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 50 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(50) })
|
||||
const graph = subgraphNode.graph as LGraph
|
||||
graph.add(subgraphNode)
|
||||
|
||||
@@ -632,7 +641,7 @@ describe('reconcileNodeErrorFlags (via lastNodeErrors watcher)', () => {
|
||||
|
||||
missingModelStore.setMissingModels([
|
||||
{
|
||||
nodeId: String(nodeA.id),
|
||||
nodeId: nodeA.id,
|
||||
nodeType: 'CheckpointLoader',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: false,
|
||||
@@ -652,7 +661,7 @@ describe('reconcileNodeErrorFlags (via lastNodeErrors watcher)', () => {
|
||||
|
||||
missingModelStore.setMissingModels([
|
||||
{
|
||||
nodeId: String(nodeA.id),
|
||||
nodeId: nodeA.id,
|
||||
nodeType: 'CheckpointLoader',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: false,
|
||||
@@ -673,7 +682,7 @@ describe('reconcileNodeErrorFlags (via lastNodeErrors watcher)', () => {
|
||||
const interiorNode = new LGraphNode('CheckpointLoader')
|
||||
subgraph.add(interiorNode)
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 50 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(50) })
|
||||
const graph = subgraphNode.graph as LGraph
|
||||
graph.add(subgraphNode)
|
||||
|
||||
@@ -689,7 +698,7 @@ describe('reconcileNodeErrorFlags (via lastNodeErrors watcher)', () => {
|
||||
|
||||
missingModelStore.setMissingModels([
|
||||
{
|
||||
nodeId: `${subgraphNode.id}:${interiorNode.id}`,
|
||||
nodeId: asNodeId(`${subgraphNode.id}:${interiorNode.id}`),
|
||||
nodeType: 'CheckpointLoader',
|
||||
widgetName: 'ckpt_name',
|
||||
isAssetSupported: false,
|
||||
|
||||
@@ -469,7 +469,7 @@ export function extractVueNodeData(node: LGraphNode): VueNodeData {
|
||||
const badges = node.badges
|
||||
|
||||
return {
|
||||
id: String(node.id),
|
||||
id: node.id,
|
||||
title: typeof node.title === 'string' ? node.title : '',
|
||||
type: nodeType,
|
||||
mode: node.mode || 0,
|
||||
@@ -553,7 +553,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
node: LGraphNode,
|
||||
originalCallback?: (node: LGraphNode) => void
|
||||
) => {
|
||||
const id = String(node.id)
|
||||
const id = node.id
|
||||
|
||||
// Store non-reactive reference to original node
|
||||
nodeRefs.set(id, node)
|
||||
@@ -615,7 +615,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
node: LGraphNode,
|
||||
originalCallback?: (node: LGraphNode) => void
|
||||
) => {
|
||||
const id = String(node.id)
|
||||
const id = node.id
|
||||
|
||||
// Remove node from layout store
|
||||
setSource(LayoutSource.Canvas)
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import type {
|
||||
LGraphGroup,
|
||||
LGraphNode,
|
||||
NodeId
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { getExtraOptionsForWidget } from '@/services/litegraphService'
|
||||
import { isLGraphGroup } from '@/utils/litegraphUtil'
|
||||
|
||||
@@ -6,7 +6,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useNodeMenuOptions } from '@/composables/graph/useNodeMenuOptions'
|
||||
import type { Positionable } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphEventMode, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
LGraphEventMode,
|
||||
LGraphNode,
|
||||
asNodeId
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
|
||||
// canvasStore transitively imports the app singleton; stub it so the real
|
||||
@@ -45,7 +49,7 @@ const i18n = createI18n({
|
||||
|
||||
const nodeWithMode = (mode: LGraphEventMode, id = 1): LGraphNode => {
|
||||
const node = new LGraphNode('Test')
|
||||
node.id = id
|
||||
node.id = asNodeId(id)
|
||||
node.mode = mode
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { shallowRef, watch } from 'vue'
|
||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
@@ -29,7 +30,7 @@ function useVueNodeLifecycleIndividual() {
|
||||
|
||||
// Initialize layout system with existing nodes from active graph
|
||||
const nodes = activeGraph._nodes.map((node: LGraphNode) => ({
|
||||
id: node.id.toString(),
|
||||
id: node.id,
|
||||
pos: [node.pos[0], node.pos[1]] as [number, number],
|
||||
size: [node.size[0], node.size[1]] as [number, number]
|
||||
}))
|
||||
@@ -47,9 +48,9 @@ function useVueNodeLifecycleIndividual() {
|
||||
for (const link of activeGraph._links.values()) {
|
||||
layoutMutations.createLink(
|
||||
link.id,
|
||||
link.origin_id,
|
||||
asNodeId(link.origin_id),
|
||||
link.origin_slot,
|
||||
link.target_id,
|
||||
asNodeId(link.target_id),
|
||||
link.target_slot
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { setActivePinia } from 'pinia'
|
||||
import { reactive } from 'vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphNode, asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
createTestSubgraph,
|
||||
createTestSubgraphNode
|
||||
@@ -58,7 +58,7 @@ function addInteriorNode(
|
||||
} = { id: 10 }
|
||||
): LGraphNode {
|
||||
const node = new LGraphNode('test')
|
||||
node.id = options.id
|
||||
node.id = asNodeId(options.id)
|
||||
if (options.previewMediaType) {
|
||||
node.previewMediaType = options.previewMediaType
|
||||
}
|
||||
@@ -69,7 +69,7 @@ function addInteriorNode(
|
||||
function seedOutputs(subgraphId: string, nodeIds: Array<number | string>) {
|
||||
const store = useNodeOutputStore()
|
||||
for (const nodeId of nodeIds) {
|
||||
const locatorId = createNodeLocatorId(subgraphId, nodeId)
|
||||
const locatorId = createNodeLocatorId(subgraphId, asNodeId(nodeId))
|
||||
store.nodeOutputs[locatorId] = {
|
||||
images: [{ filename: 'output.png' }]
|
||||
}
|
||||
@@ -82,7 +82,7 @@ function seedPreviewImages(
|
||||
) {
|
||||
const store = useNodeOutputStore()
|
||||
for (const { nodeId, urls } of entries) {
|
||||
const locatorId = createNodeLocatorId(subgraphId, nodeId)
|
||||
const locatorId = createNodeLocatorId(subgraphId, asNodeId(nodeId))
|
||||
store.nodePreviewImages[locatorId] = urls
|
||||
}
|
||||
}
|
||||
@@ -281,7 +281,9 @@ describe(usePromotedPreviews, () => {
|
||||
})
|
||||
|
||||
const outerSetup = createSetup()
|
||||
const innerHost = createTestSubgraphNode(innerSetup.subgraph, { id: 20 })
|
||||
const innerHost = createTestSubgraphNode(innerSetup.subgraph, {
|
||||
id: asNodeId(20)
|
||||
})
|
||||
outerSetup.subgraph.add(innerHost)
|
||||
|
||||
const store = usePreviewExposureStore()
|
||||
@@ -329,10 +331,16 @@ describe(usePromotedPreviews, () => {
|
||||
})
|
||||
|
||||
const outerSetup = createSetup()
|
||||
const innerHost = createTestSubgraphNode(innerSetup.subgraph, { id: 20 })
|
||||
const innerHost = createTestSubgraphNode(innerSetup.subgraph, {
|
||||
id: asNodeId(20)
|
||||
})
|
||||
outerSetup.subgraph.add(innerHost)
|
||||
const firstHost = createTestSubgraphNode(outerSetup.subgraph, { id: 11 })
|
||||
const secondHost = createTestSubgraphNode(outerSetup.subgraph, { id: 12 })
|
||||
const firstHost = createTestSubgraphNode(outerSetup.subgraph, {
|
||||
id: asNodeId(11)
|
||||
})
|
||||
const secondHost = createTestSubgraphNode(outerSetup.subgraph, {
|
||||
id: asNodeId(12)
|
||||
})
|
||||
const firstHostLocator = String(firstHost.id)
|
||||
const secondHostLocator = String(secondHost.id)
|
||||
const firstNestedLocator = `${firstHostLocator}:${innerHost.id}`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { MaybeRefOrGetter } from 'vue'
|
||||
import { computed, toValue } from 'vue'
|
||||
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import type { UUID } from '@/utils/uuid'
|
||||
@@ -43,7 +44,7 @@ export function usePromotedPreviews(
|
||||
): string[] | undefined {
|
||||
const locatorId = createNodeLocatorId(
|
||||
leafHost.subgraph.id,
|
||||
leafSourceNodeId
|
||||
asNodeId(leafSourceNodeId)
|
||||
)
|
||||
const reactiveOutputs = nodeOutputStore.nodeOutputs[locatorId]
|
||||
const reactivePreviews = nodeOutputStore.nodePreviewImages[locatorId]
|
||||
|
||||
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { useResultGallery } from '@/composables/queue/useResultGallery'
|
||||
import type { JobListItem as JobListViewItem } from '@/composables/queue/useJobList'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { JobListItem } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import { ResultItemImpl, TaskItemImpl } from '@/stores/queueStore'
|
||||
|
||||
@@ -14,7 +15,7 @@ const createResultItem = (
|
||||
filename: url,
|
||||
subfolder: '',
|
||||
type: 'output',
|
||||
nodeId: 'node-1',
|
||||
nodeId: asNodeId('node-1'),
|
||||
mediaType: supportsPreview ? 'images' : 'unknown'
|
||||
})
|
||||
// Override url getter for test matching
|
||||
|
||||
@@ -9,7 +9,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import WidgetImageCrop from '@/components/imagecrop/WidgetImageCrop.vue'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import {
|
||||
createMockLGraphNode,
|
||||
@@ -83,7 +84,7 @@ const ImageCropHarness = defineComponent({
|
||||
modelValue,
|
||||
imageEl,
|
||||
containerEl,
|
||||
...useImageCrop(props.nodeId as NodeId, {
|
||||
...useImageCrop(asNodeId(props.nodeId), {
|
||||
imageEl,
|
||||
containerEl,
|
||||
modelValue
|
||||
@@ -183,7 +184,7 @@ function setupImageLayout(vm: CropVm, nw: number, nh: number) {
|
||||
|
||||
const harnessCleanups: Array<() => void> = []
|
||||
|
||||
async function mountHarness(nodeId: NodeId = 2 as NodeId) {
|
||||
async function mountHarness(nodeId: NodeId = asNodeId(2)) {
|
||||
const el = document.createElement('div')
|
||||
document.body.appendChild(el)
|
||||
const app = createApp(ImageCropHarness, { nodeId: Number(nodeId) })
|
||||
@@ -657,7 +658,7 @@ describe('WidgetImageCrop', () => {
|
||||
container: attach,
|
||||
props: {
|
||||
widget,
|
||||
nodeId: 2 as NodeId,
|
||||
nodeId: asNodeId(2),
|
||||
modelValue: { x: 0, y: 0, width: 100, height: 100 }
|
||||
},
|
||||
global: {
|
||||
@@ -689,7 +690,7 @@ describe('WidgetImageCrop', () => {
|
||||
container: attach,
|
||||
props: {
|
||||
widget,
|
||||
nodeId: 2 as NodeId,
|
||||
nodeId: asNodeId(2),
|
||||
modelValue: { x: 0, y: 0, width: 200, height: 200 }
|
||||
},
|
||||
global: {
|
||||
@@ -733,7 +734,7 @@ describe('WidgetImageCrop', () => {
|
||||
container: attach,
|
||||
props: {
|
||||
widget,
|
||||
nodeId: 2 as NodeId,
|
||||
nodeId: asNodeId(2),
|
||||
modelValue: { x: 0, y: 0, width: 200, height: 200 }
|
||||
},
|
||||
global: {
|
||||
@@ -779,7 +780,7 @@ describe('WidgetImageCrop', () => {
|
||||
container: attach,
|
||||
props: {
|
||||
widget,
|
||||
nodeId: 2 as NodeId,
|
||||
nodeId: asNodeId(2),
|
||||
modelValue: { x: 0, y: 0, width: 100, height: 100 }
|
||||
},
|
||||
global: {
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useResizeObserver } from '@vueuse/core'
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { Bounds } from '@/renderer/core/layout/types'
|
||||
import { useNodeOutputStore } from '@/stores/nodeOutputStore'
|
||||
import { resolveNode } from '@/utils/litegraphUtil'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import type { WidgetState } from '@/types/widgetState'
|
||||
|
||||
import { boundsExtractor, singleValueExtractor } from './useUpstreamValue'
|
||||
@@ -10,7 +10,7 @@ function widget(name: string, value: unknown): WidgetState {
|
||||
name,
|
||||
type: 'INPUT',
|
||||
value,
|
||||
nodeId: '1' as NodeId,
|
||||
nodeId: asNodeId('1'),
|
||||
options: {},
|
||||
y: 0
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useWidgetValueStore } from '@/stores/widgetValueStore'
|
||||
import type { Bounds } from '@/renderer/core/layout/types'
|
||||
@@ -23,7 +24,10 @@ export function useUpstreamValue<T>(
|
||||
if (!upstream) return undefined
|
||||
const graphId = canvasStore.canvas?.graph?.rootGraph.id
|
||||
if (!graphId) return undefined
|
||||
const widgets = widgetValueStore.getNodeWidgets(graphId, upstream.nodeId)
|
||||
const widgets = widgetValueStore.getNodeWidgets(
|
||||
graphId,
|
||||
asNodeId(upstream.nodeId)
|
||||
)
|
||||
return extractValue(widgets, upstream.outputName)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import type {
|
||||
} from '@/core/schemas/proxyWidgetQuarantineSchema'
|
||||
import { parseProxyWidgetErrorQuarantine } from '@/core/schemas/proxyWidgetQuarantineSchema'
|
||||
import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces'
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import { nextUniqueName } from '@/lib/litegraph/src/strings'
|
||||
import type { Subgraph } from '@/lib/litegraph/src/subgraph/Subgraph'
|
||||
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'
|
||||
@@ -262,7 +264,7 @@ function collectTargetsStrict(
|
||||
const link = subgraph.links.get(linkId)
|
||||
if (!link) return undefined
|
||||
targets.push({
|
||||
targetNodeId: link.target_id,
|
||||
targetNodeId: asNodeId(link.target_id),
|
||||
targetSlot: link.target_slot
|
||||
})
|
||||
}
|
||||
@@ -278,7 +280,12 @@ function collectTargetsSkippingDangling(
|
||||
return linkIds.flatMap((linkId) => {
|
||||
const link = subgraph.links.get(linkId)
|
||||
return link
|
||||
? [{ targetNodeId: link.target_id, targetSlot: link.target_slot }]
|
||||
? [
|
||||
{
|
||||
targetNodeId: asNodeId(link.target_id),
|
||||
targetSlot: link.target_slot
|
||||
}
|
||||
]
|
||||
: []
|
||||
})
|
||||
}
|
||||
@@ -603,7 +610,7 @@ function repairPrimitive(
|
||||
.filter((l): l is NonNullable<typeof l> => l !== undefined)
|
||||
.map((l) => ({
|
||||
primitiveSlot: l.origin_slot,
|
||||
targetNodeId: l.target_id,
|
||||
targetNodeId: asNodeId(l.target_id),
|
||||
targetSlot: l.target_slot
|
||||
}))
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import { resolveConcretePromotedWidget } from '@/core/graph/subgraph/resolveConcretePromotedWidget'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphNode, asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import {
|
||||
createTestSubgraph,
|
||||
@@ -23,7 +23,7 @@ vi.mock('@/services/litegraphService', () => ({
|
||||
}))
|
||||
|
||||
function createHostNode(id: number): SubgraphNode {
|
||||
return createTestSubgraphNode(createTestSubgraph(), { id })
|
||||
return createTestSubgraphNode(createTestSubgraph(), { id: asNodeId(id) })
|
||||
}
|
||||
|
||||
function addNodeToHost(host: SubgraphNode, title: string): LGraphNode {
|
||||
@@ -71,7 +71,9 @@ describe('resolveConcretePromotedWidget', () => {
|
||||
innerSubgraph.add(leaf)
|
||||
innerSubgraph.inputNode.slots[0].connect(leafInput, leaf)
|
||||
|
||||
const innerNode = createTestSubgraphNode(innerSubgraph, { id: 11 })
|
||||
const innerNode = createTestSubgraphNode(innerSubgraph, {
|
||||
id: asNodeId(11)
|
||||
})
|
||||
|
||||
const outerSubgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'y', type: '*' }]
|
||||
@@ -80,7 +82,9 @@ describe('resolveConcretePromotedWidget', () => {
|
||||
innerNode._internalConfigureAfterSlots()
|
||||
outerSubgraph.inputNode.slots[0].connect(innerNode.inputs[0], innerNode)
|
||||
|
||||
const outerNode = createTestSubgraphNode(outerSubgraph, { id: 22 })
|
||||
const outerNode = createTestSubgraphNode(outerSubgraph, {
|
||||
id: asNodeId(22)
|
||||
})
|
||||
|
||||
const result = resolveConcretePromotedWidget(
|
||||
outerNode,
|
||||
@@ -129,13 +133,13 @@ describe('resolveConcretePromotedWidget', () => {
|
||||
for (let index = 0; index < subgraphs.length - 1; index++) {
|
||||
const current = subgraphs[index]
|
||||
const next = subgraphs[index + 1]
|
||||
const nextNode = createTestSubgraphNode(next, { id: index + 1 })
|
||||
const nextNode = createTestSubgraphNode(next, { id: asNodeId(index + 1) })
|
||||
current.add(nextNode)
|
||||
nextNode._internalConfigureAfterSlots()
|
||||
current.inputNode.slots[0].connect(nextNode.inputs[0], nextNode)
|
||||
}
|
||||
|
||||
const host = createTestSubgraphNode(subgraphs[0], { id: 200 })
|
||||
const host = createTestSubgraphNode(subgraphs[0], { id: asNodeId(200) })
|
||||
|
||||
const result = resolveConcretePromotedWidget(host, '1', 'x')
|
||||
expect(result).toEqual({
|
||||
|
||||
@@ -4,7 +4,7 @@ import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import { resolveSubgraphInputLink } from '@/core/graph/subgraph/resolveSubgraphInputLink'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphNode, asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
createTestSubgraph,
|
||||
createTestSubgraphNode,
|
||||
@@ -30,7 +30,7 @@ function createSubgraphSetup(inputName: string): {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: inputName, type: '*' }]
|
||||
})
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 1 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(1) })
|
||||
return { subgraph, subgraphNode }
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import { resolveSubgraphInputTarget } from '@/core/graph/subgraph/resolveSubgraphInputTarget'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphNode, asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
createTestSubgraph,
|
||||
createTestSubgraphNode
|
||||
@@ -28,7 +28,9 @@ function createOuterSubgraphSetup(inputNames: string[]): {
|
||||
const outerSubgraph = createTestSubgraph({
|
||||
inputs: inputNames.map((name) => ({ name, type: '*' }))
|
||||
})
|
||||
const outerSubgraphNode = createTestSubgraphNode(outerSubgraph, { id: 1 })
|
||||
const outerSubgraphNode = createTestSubgraphNode(outerSubgraph, {
|
||||
id: asNodeId(1)
|
||||
})
|
||||
return { outerSubgraph, outerSubgraphNode }
|
||||
}
|
||||
|
||||
@@ -41,7 +43,9 @@ function addLinkedNestedSubgraphNode(
|
||||
const innerSubgraph = createTestSubgraph({
|
||||
inputs: [{ name: linkedInputName, type: '*' }]
|
||||
})
|
||||
const innerSubgraphNode = createTestSubgraphNode(innerSubgraph, { id: 819 })
|
||||
const innerSubgraphNode = createTestSubgraphNode(innerSubgraph, {
|
||||
id: asNodeId(819)
|
||||
})
|
||||
outerSubgraph.add(innerSubgraphNode)
|
||||
|
||||
const inputSlot = outerSubgraph.inputNode.slots.find(
|
||||
@@ -139,7 +143,7 @@ describe('resolveSubgraphInputTarget', () => {
|
||||
(slot) => slot.name === 'seed'
|
||||
)!
|
||||
const node = new LGraphNode('Interior-seed')
|
||||
node.id = 42
|
||||
node.id = asNodeId(42)
|
||||
const input = node.addInput('seed_input', '*')
|
||||
node.addWidget('number', 'seed', 0, () => undefined)
|
||||
input.widget = { name: 'seed' }
|
||||
@@ -185,7 +189,7 @@ describe('resolveSubgraphInputTarget', () => {
|
||||
]
|
||||
})
|
||||
const innerSubgraphNode = createTestSubgraphNode(innerSubgraph, {
|
||||
id: 820
|
||||
id: asNodeId(820)
|
||||
})
|
||||
outerSubgraph.add(innerSubgraphNode)
|
||||
|
||||
@@ -224,7 +228,7 @@ describe('resolveSubgraphInputTarget', () => {
|
||||
inputs: [{ name: 'seed', type: '*' }]
|
||||
})
|
||||
const concreteNode = new LGraphNode('ConcreteNode')
|
||||
concreteNode.id = 900
|
||||
concreteNode.id = asNodeId(900)
|
||||
const concreteInput = concreteNode.addInput('seed_input', '*')
|
||||
concreteNode.addWidget('number', 'seed', 0, () => undefined)
|
||||
concreteInput.widget = { name: 'seed' }
|
||||
@@ -235,7 +239,7 @@ describe('resolveSubgraphInputTarget', () => {
|
||||
inputs: [{ name: 'seed', type: '*' }]
|
||||
})
|
||||
const innerSubgraphNode = createTestSubgraphNode(innerSubgraph, {
|
||||
id: 901
|
||||
id: asNodeId(901)
|
||||
})
|
||||
middleSubgraph.add(innerSubgraphNode)
|
||||
const middleInput = innerSubgraphNode.addInput('seed', '*')
|
||||
@@ -247,7 +251,7 @@ describe('resolveSubgraphInputTarget', () => {
|
||||
'seed'
|
||||
])
|
||||
const middleSubgraphNode = createTestSubgraphNode(middleSubgraph, {
|
||||
id: 902
|
||||
id: asNodeId(902)
|
||||
})
|
||||
outerSubgraph.add(middleSubgraphNode)
|
||||
const outerInput = middleSubgraphNode.addInput('seed', '*')
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { SerialisedLLinkArray } from '@/lib/litegraph/src/LLink'
|
||||
import type { ComfyNode } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
|
||||
import type { GroupNodeWorkflowData } from './groupNode'
|
||||
|
||||
@@ -15,7 +16,7 @@ import { GroupNodeConfig, replaceLegacySeparators } from './groupNode'
|
||||
|
||||
function makeNode(type: string): ComfyNode {
|
||||
return {
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
type,
|
||||
pos: [0, 0],
|
||||
size: [1, 1],
|
||||
@@ -66,8 +67,22 @@ describe('GroupNodeConfig.getLinks', () => {
|
||||
}
|
||||
|
||||
it('indexes outgoing links by [origin index][origin slot]', () => {
|
||||
const clip = [1, 1, 2, 0, 4, 'CLIP'] satisfies SerialisedLLinkArray
|
||||
const model = [1, 0, 4, 0, 4, 'MODEL'] satisfies SerialisedLLinkArray
|
||||
const clip = [
|
||||
1,
|
||||
asNodeId(1),
|
||||
2,
|
||||
asNodeId(0),
|
||||
4,
|
||||
'CLIP'
|
||||
] satisfies SerialisedLLinkArray
|
||||
const model = [
|
||||
1,
|
||||
asNodeId(0),
|
||||
4,
|
||||
asNodeId(0),
|
||||
4,
|
||||
'MODEL'
|
||||
] satisfies SerialisedLLinkArray
|
||||
const config = configFrom([clip, model])
|
||||
|
||||
expect(config.linksFrom[1][1]).toEqual([clip])
|
||||
@@ -75,8 +90,22 @@ describe('GroupNodeConfig.getLinks', () => {
|
||||
})
|
||||
|
||||
it('indexes incoming links by [target index][target slot]', () => {
|
||||
const clip = [1, 1, 2, 0, 4, 'CLIP'] satisfies SerialisedLLinkArray
|
||||
const cond = [2, 0, 4, 1, 6, 'CONDITIONING'] satisfies SerialisedLLinkArray
|
||||
const clip = [
|
||||
1,
|
||||
asNodeId(1),
|
||||
2,
|
||||
asNodeId(0),
|
||||
4,
|
||||
'CLIP'
|
||||
] satisfies SerialisedLLinkArray
|
||||
const cond = [
|
||||
2,
|
||||
asNodeId(0),
|
||||
4,
|
||||
asNodeId(1),
|
||||
6,
|
||||
'CONDITIONING'
|
||||
] satisfies SerialisedLLinkArray
|
||||
const config = configFrom([clip, cond])
|
||||
|
||||
expect(config.linksTo[2][0]).toEqual(clip)
|
||||
@@ -84,15 +113,36 @@ describe('GroupNodeConfig.getLinks', () => {
|
||||
})
|
||||
|
||||
it('accumulates multiple fan-out links from the same origin slot', () => {
|
||||
const toPos = [1, 1, 2, 0, 4, 'CLIP'] satisfies SerialisedLLinkArray
|
||||
const toNeg = [1, 1, 3, 0, 5, 'CLIP'] satisfies SerialisedLLinkArray
|
||||
const toPos = [
|
||||
1,
|
||||
asNodeId(1),
|
||||
2,
|
||||
asNodeId(0),
|
||||
4,
|
||||
'CLIP'
|
||||
] satisfies SerialisedLLinkArray
|
||||
const toNeg = [
|
||||
1,
|
||||
asNodeId(1),
|
||||
3,
|
||||
asNodeId(0),
|
||||
5,
|
||||
'CLIP'
|
||||
] satisfies SerialisedLLinkArray
|
||||
const config = configFrom([toPos, toNeg])
|
||||
|
||||
expect(config.linksFrom[1][1]).toEqual([toPos, toNeg])
|
||||
})
|
||||
|
||||
it('skips links that have a null endpoint', () => {
|
||||
const valid = [1, 1, 2, 0, 4, 'CLIP'] satisfies SerialisedLLinkArray
|
||||
const valid = [
|
||||
1,
|
||||
asNodeId(1),
|
||||
2,
|
||||
asNodeId(0),
|
||||
4,
|
||||
'CLIP'
|
||||
] satisfies SerialisedLLinkArray
|
||||
const broken = [null, 1, 2, 0, 4, 'CLIP'] as unknown as SerialisedLLinkArray
|
||||
const config = configFrom([valid, broken])
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
INodeOutputSlot,
|
||||
LLink
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { createMockLLink } from '@/utils/__tests__/litegraphTestUtils'
|
||||
@@ -39,7 +40,7 @@ function createTargetNode(
|
||||
id = 7
|
||||
): Pick<LGraphNode, 'id' | 'inputs' | 'widgets'> {
|
||||
return fromPartial<Pick<LGraphNode, 'id' | 'inputs' | 'widgets'>>({
|
||||
id,
|
||||
id: asNodeId(id),
|
||||
inputs: [
|
||||
fromPartial<INodeInputSlot>({
|
||||
widget: { name: widget.name }
|
||||
|
||||
@@ -2,8 +2,9 @@ import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { NodeId, Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
asNodeId,
|
||||
LGraph,
|
||||
LGraphNode,
|
||||
LiteGraph,
|
||||
@@ -297,7 +298,7 @@ describe('Graph Clearing and Callbacks', () => {
|
||||
})
|
||||
|
||||
const widgetValueStore = useWidgetValueStore()
|
||||
const seedWidgetId = widgetId(graphId, '10' as NodeId, 'seed')
|
||||
const seedWidgetId = widgetId(graphId, asNodeId('10'), 'seed')
|
||||
widgetValueStore.registerWidget(seedWidgetId, {
|
||||
type: 'number',
|
||||
value: 1,
|
||||
@@ -483,7 +484,7 @@ describe('ensureGlobalIdUniqueness', () => {
|
||||
|
||||
expect(subNode.id).not.toBe(rootNode.id)
|
||||
expect(subgraph._nodes_by_id[subNode.id]).toBe(subNode)
|
||||
expect(subgraph._nodes_by_id[rootNode.id as number]).toBeUndefined()
|
||||
expect(subgraph._nodes_by_id[rootNode.id]).toBeUndefined()
|
||||
})
|
||||
|
||||
it('preserves root graph node IDs as canonical', () => {
|
||||
@@ -519,7 +520,7 @@ describe('ensureGlobalIdUniqueness', () => {
|
||||
rootGraph.ensureGlobalIdUniqueness()
|
||||
|
||||
expect(rootGraph.state.lastNodeId).toBeGreaterThanOrEqual(
|
||||
subNode.id as number
|
||||
Number(subNode.id)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -536,7 +537,7 @@ describe('ensureGlobalIdUniqueness', () => {
|
||||
subgraph._nodes_by_id[subNodeA.id] = subNodeA
|
||||
|
||||
const subNodeB = new DummyNode()
|
||||
subNodeB.id = 999
|
||||
subNodeB.id = asNodeId(999)
|
||||
subgraph._nodes.push(subNodeB)
|
||||
subgraph._nodes_by_id[subNodeB.id] = subNodeB
|
||||
|
||||
@@ -555,13 +556,14 @@ describe('ensureGlobalIdUniqueness', () => {
|
||||
const subgraph = createSubgraphOnGraph(rootGraph)
|
||||
|
||||
const subNode = new DummyNode()
|
||||
subNode.id = 42
|
||||
subNode.id = asNodeId(42)
|
||||
subgraph._nodes.push(subNode)
|
||||
subgraph._nodes_by_id[subNode.id] = subNode
|
||||
|
||||
rootGraph.ensureGlobalIdUniqueness([42])
|
||||
|
||||
expect(subNode.id).not.toBe(42)
|
||||
expect(subNode.id).not.toBe(asNodeId(42))
|
||||
expect(subgraph._nodes_by_id[asNodeId(42)]).toBeUndefined()
|
||||
expect(subgraph._nodes_by_id[subNode.id]).toBe(subNode)
|
||||
})
|
||||
|
||||
@@ -867,7 +869,7 @@ describe('Subgraph Unpacking', () => {
|
||||
|
||||
const firstInstance = createTestSubgraphNode(subgraph, { pos: [100, 100] })
|
||||
const secondInstance = createTestSubgraphNode(subgraph, { pos: [300, 100] })
|
||||
secondInstance.id = 2
|
||||
secondInstance.id = asNodeId(2)
|
||||
rootGraph.add(firstInstance)
|
||||
rootGraph.add(secondInstance)
|
||||
|
||||
@@ -915,7 +917,7 @@ describe('deduplicateSubgraphNodeIds (via configure)', () => {
|
||||
const idsB = nodeIdSet(graph, SUBGRAPH_B)
|
||||
|
||||
for (const id of SHARED_NODE_IDS) {
|
||||
expect(idsA.has(id as NodeId)).toBe(true)
|
||||
expect(idsA.has(asNodeId(id))).toBe(true)
|
||||
}
|
||||
for (const id of idsA) {
|
||||
expect(idsB.has(id)).toBe(false)
|
||||
@@ -927,8 +929,8 @@ describe('deduplicateSubgraphNodeIds (via configure)', () => {
|
||||
const idsB = nodeIdSet(graph, SUBGRAPH_B)
|
||||
|
||||
for (const link of graph.subgraphs.get(SUBGRAPH_B)!.links.values()) {
|
||||
expect(idsB.has(link.origin_id)).toBe(true)
|
||||
expect(idsB.has(link.target_id)).toBe(true)
|
||||
expect(idsB.has(asNodeId(link.origin_id))).toBe(true)
|
||||
expect(idsB.has(asNodeId(link.target_id))).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -951,14 +953,14 @@ describe('deduplicateSubgraphNodeIds (via configure)', () => {
|
||||
graph.subgraphs.get(SUBGRAPH_B)!.nodes.map((n) => String(n.id))
|
||||
)
|
||||
|
||||
const pw102 = graph.getNodeById(102 as NodeId)?.properties?.proxyWidgets
|
||||
const pw102 = graph.getNodeById(asNodeId(102))?.properties?.proxyWidgets
|
||||
expect(Array.isArray(pw102)).toBe(true)
|
||||
for (const entry of pw102 as unknown[][]) {
|
||||
expect(Array.isArray(entry)).toBe(true)
|
||||
expect(idsA.has(String(entry[0]))).toBe(true)
|
||||
}
|
||||
|
||||
const pw103 = graph.getNodeById(103 as NodeId)?.properties?.proxyWidgets
|
||||
const pw103 = graph.getNodeById(asNodeId(103))?.properties?.proxyWidgets
|
||||
expect(Array.isArray(pw103)).toBe(true)
|
||||
for (const entry of pw103 as unknown[][]) {
|
||||
expect(Array.isArray(entry)).toBe(true)
|
||||
@@ -976,7 +978,7 @@ describe('deduplicateSubgraphNodeIds (via configure)', () => {
|
||||
|
||||
const innerNode = graph.subgraphs
|
||||
.get(SUBGRAPH_A)!
|
||||
.nodes.find((n) => n.id === (50 as NodeId))
|
||||
.nodes.find((n) => n.id === asNodeId(50))
|
||||
const pw = innerNode?.properties?.proxyWidgets
|
||||
expect(Array.isArray(pw)).toBe(true)
|
||||
for (const entry of pw as unknown[][]) {
|
||||
@@ -1016,7 +1018,7 @@ describe('deduplicateSubgraphNodeIds (via configure)', () => {
|
||||
expect(migrationCall).toBeDefined()
|
||||
expect(migrationCall![1]).toEqual(
|
||||
expect.objectContaining({
|
||||
hostNodeId: expect.any(Number),
|
||||
hostNodeId: expect.any(String),
|
||||
proxyWidgets: expect.anything()
|
||||
})
|
||||
)
|
||||
@@ -1038,8 +1040,12 @@ describe('deduplicateSubgraphNodeIds (via configure)', () => {
|
||||
const graph = new LGraph()
|
||||
graph.configure(structuredClone(uniqueSubgraphNodeIds))
|
||||
|
||||
expect(nodeIdSet(graph, SUBGRAPH_A)).toEqual(new Set([10, 11, 12]))
|
||||
expect(nodeIdSet(graph, SUBGRAPH_B)).toEqual(new Set([20, 21, 22]))
|
||||
expect(nodeIdSet(graph, SUBGRAPH_A)).toEqual(
|
||||
new Set([asNodeId(10), asNodeId(11), asNodeId(12)])
|
||||
)
|
||||
expect(nodeIdSet(graph, SUBGRAPH_B)).toEqual(
|
||||
new Set([asNodeId(20), asNodeId(21), asNodeId(22)])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { toString } from 'es-toolkit/compat'
|
||||
|
||||
import {
|
||||
SUBGRAPH_INPUT_ID,
|
||||
SUBGRAPH_OUTPUT_ID
|
||||
} from '@/lib/litegraph/src/constants'
|
||||
import { isNodeBindable } from '@/lib/litegraph/src/utils/type'
|
||||
import type { UUID } from '@/utils/uuid'
|
||||
import { createUuidv4, zeroUuid } from '@/utils/uuid'
|
||||
@@ -87,6 +83,18 @@ import type {
|
||||
SerialisableReroute
|
||||
} from './types/serialisation'
|
||||
import { getAllNestedItems } from './utils/collections'
|
||||
import {
|
||||
SUBGRAPH_INPUT_NODE_ID,
|
||||
SUBGRAPH_OUTPUT_NODE_ID,
|
||||
asNodeId,
|
||||
isFloatingNodeId,
|
||||
isNumericNodeId,
|
||||
isSubgraphInputNodeId,
|
||||
isSubgraphOutputNodeId,
|
||||
isUnassignedNodeId,
|
||||
nodeIdToNumber
|
||||
} from '@/types/nodeId'
|
||||
import type { NodeIdInput } from '@/types/nodeId'
|
||||
import {
|
||||
deduplicateSubgraphNodeIds,
|
||||
topologicalSortSubgraphs
|
||||
@@ -632,7 +640,7 @@ export class LGraph
|
||||
const S: LGraphNode[] = []
|
||||
const M: Dictionary<LGraphNode> = {}
|
||||
// to avoid repeating links
|
||||
const visited_links: Record<NodeId, boolean> = {}
|
||||
const visited_links: Record<LinkId, boolean> = {}
|
||||
const remaining_links: Record<NodeId, number> = {}
|
||||
|
||||
// search for the nodes without inputs (starting nodes)
|
||||
@@ -949,11 +957,11 @@ export class LGraph
|
||||
}
|
||||
|
||||
// nodes
|
||||
if (node.id != -1 && this._nodes_by_id[node.id] != null) {
|
||||
if (!isUnassignedNodeId(node.id) && this._nodes_by_id[node.id] != null) {
|
||||
console.warn(
|
||||
'LiteGraph: there is already a node with this ID, changing it'
|
||||
)
|
||||
node.id = ++state.lastNodeId
|
||||
node.id = asNodeId(++state.lastNodeId)
|
||||
}
|
||||
|
||||
if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) {
|
||||
@@ -961,10 +969,13 @@ export class LGraph
|
||||
}
|
||||
|
||||
// give him an id
|
||||
if (node.id == null || node.id == -1) {
|
||||
node.id = ++state.lastNodeId
|
||||
} else if (typeof node.id === 'number' && state.lastNodeId < node.id) {
|
||||
state.lastNodeId = node.id
|
||||
if (isUnassignedNodeId(node.id)) {
|
||||
node.id = asNodeId(++state.lastNodeId)
|
||||
} else if (
|
||||
isNumericNodeId(node.id) &&
|
||||
state.lastNodeId < nodeIdToNumber(node.id)
|
||||
) {
|
||||
state.lastNodeId = nodeIdToNumber(node.id)
|
||||
}
|
||||
|
||||
// Set ghost flag before registration so VueNodeData picks it up
|
||||
@@ -1128,8 +1139,8 @@ export class LGraph
|
||||
/**
|
||||
* Returns a node by its id.
|
||||
*/
|
||||
getNodeById(id: NodeId | null | undefined): LGraphNode | null {
|
||||
return id != null ? this._nodes_by_id[id] : null
|
||||
getNodeById(id: NodeIdInput | null | undefined): LGraphNode | null {
|
||||
return id != null ? this._nodes_by_id[asNodeId(id)] : null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1389,10 +1400,9 @@ export class LGraph
|
||||
}
|
||||
this.floatingLinksInternal.set(link.id, link)
|
||||
|
||||
const slot =
|
||||
link.target_id !== -1
|
||||
? this.getNodeById(link.target_id)?.inputs?.[link.target_slot]
|
||||
: this.getNodeById(link.origin_id)?.outputs?.[link.origin_slot]
|
||||
const slot = !isFloatingNodeId(link.target_id)
|
||||
? this.getNodeById(link.target_id)?.inputs?.[link.target_slot]
|
||||
: this.getNodeById(link.origin_id)?.outputs?.[link.origin_slot]
|
||||
if (slot) {
|
||||
slot._floatingLinks ??= new Set()
|
||||
slot._floatingLinks.add(link)
|
||||
@@ -1412,10 +1422,9 @@ export class LGraph
|
||||
removeFloatingLink(link: LLink): void {
|
||||
this.floatingLinksInternal.delete(link.id)
|
||||
|
||||
const slot =
|
||||
link.target_id !== -1
|
||||
? this.getNodeById(link.target_id)?.inputs?.[link.target_slot]
|
||||
: this.getNodeById(link.origin_id)?.outputs?.[link.origin_slot]
|
||||
const slot = !isFloatingNodeId(link.target_id)
|
||||
? this.getNodeById(link.target_id)?.inputs?.[link.target_slot]
|
||||
: this.getNodeById(link.origin_id)?.outputs?.[link.origin_slot]
|
||||
if (slot) {
|
||||
slot._floatingLinks?.delete(link)
|
||||
}
|
||||
@@ -1715,11 +1724,11 @@ export class LGraph
|
||||
id: createUuidv4(),
|
||||
name: 'New Subgraph',
|
||||
inputNode: {
|
||||
id: SUBGRAPH_INPUT_ID,
|
||||
id: SUBGRAPH_INPUT_NODE_ID,
|
||||
bounding: [0, 0, 75, 100]
|
||||
},
|
||||
outputNode: {
|
||||
id: SUBGRAPH_OUTPUT_ID,
|
||||
id: SUBGRAPH_OUTPUT_NODE_ID,
|
||||
bounding: [0, 0, 75, 100]
|
||||
},
|
||||
inputs,
|
||||
@@ -1816,7 +1825,7 @@ export class LGraph
|
||||
|
||||
// Special handling: Subgraph input node
|
||||
i++
|
||||
if (link.origin_id === SUBGRAPH_INPUT_ID) {
|
||||
if (isSubgraphInputNodeId(link.origin_id)) {
|
||||
link.target_id = subgraphNode.id
|
||||
link.target_slot = i - 1
|
||||
if (subgraphInput instanceof SubgraphInput) {
|
||||
@@ -1857,7 +1866,7 @@ export class LGraph
|
||||
for (const connection of connections) {
|
||||
const { input, inputNode, link, subgraphOutput } = connection
|
||||
// Special handling: Subgraph output node
|
||||
if (link.target_id === SUBGRAPH_OUTPUT_ID) {
|
||||
if (isSubgraphOutputNodeId(link.target_id)) {
|
||||
link.origin_id = subgraphNode.id
|
||||
link.origin_slot = i - 1
|
||||
this.links.set(link.id, link)
|
||||
@@ -1962,9 +1971,10 @@ export class LGraph
|
||||
}
|
||||
}
|
||||
|
||||
nodeIdMap.set(n_info.id, ++this.last_node_id)
|
||||
node.id = this.last_node_id
|
||||
n_info.id = this.last_node_id
|
||||
const newId = asNodeId(++this.last_node_id)
|
||||
nodeIdMap.set(n_info.id, newId)
|
||||
node.id = newId
|
||||
n_info.id = newId
|
||||
|
||||
// Strip links from serialized data before configure to prevent
|
||||
// onConnectionsChange from resolving subgraph-internal link IDs
|
||||
@@ -2029,7 +2039,7 @@ export class LGraph
|
||||
}[] = []
|
||||
for (const [, link] of subgraphNode.subgraph._links) {
|
||||
let externalParentId: RerouteId | undefined
|
||||
if (link.origin_id === SUBGRAPH_INPUT_ID) {
|
||||
if (isSubgraphInputNodeId(link.origin_id)) {
|
||||
const outerLinkId = subgraphNode.inputs[link.origin_slot].link
|
||||
if (!outerLinkId) {
|
||||
console.error('Missing Link ID when unpacking')
|
||||
@@ -2047,7 +2057,7 @@ export class LGraph
|
||||
}
|
||||
link.origin_id = origin_id
|
||||
}
|
||||
if (link.target_id === SUBGRAPH_OUTPUT_ID) {
|
||||
if (isSubgraphOutputNodeId(link.target_id)) {
|
||||
for (const linkId of subgraphNode.outputs[link.target_slot].links ??
|
||||
[]) {
|
||||
const sublink = this.links[linkId]
|
||||
@@ -2098,7 +2108,7 @@ export class LGraph
|
||||
const linkIdMap = new Map<LinkId, LinkId[]>()
|
||||
for (const newLink of dedupedNewLinks) {
|
||||
let created: LLink | null | undefined
|
||||
if (newLink.oid == SUBGRAPH_INPUT_ID) {
|
||||
if (isSubgraphInputNodeId(newLink.oid)) {
|
||||
if (!(this instanceof Subgraph)) {
|
||||
console.error('Ignoring link to subgraph outside subgraph')
|
||||
continue
|
||||
@@ -2108,7 +2118,7 @@ export class LGraph
|
||||
tnode.inputs[newLink.tslot],
|
||||
tnode
|
||||
)
|
||||
} else if (newLink.tid == SUBGRAPH_OUTPUT_ID) {
|
||||
} else if (isSubgraphOutputNodeId(newLink.tid)) {
|
||||
if (!(this instanceof Subgraph)) {
|
||||
console.error('Ignoring link to subgraph outside subgraph')
|
||||
continue
|
||||
@@ -2499,15 +2509,18 @@ export class LGraph
|
||||
if (subgraphs) {
|
||||
const reservedNodeIds = new Set<number>()
|
||||
for (const node of this._nodes) {
|
||||
if (typeof node.id === 'number') reservedNodeIds.add(node.id)
|
||||
if (isNumericNodeId(node.id))
|
||||
reservedNodeIds.add(nodeIdToNumber(node.id))
|
||||
}
|
||||
for (const sg of this.subgraphs.values()) {
|
||||
for (const node of sg.nodes) {
|
||||
if (typeof node.id === 'number') reservedNodeIds.add(node.id)
|
||||
if (isNumericNodeId(node.id))
|
||||
reservedNodeIds.add(nodeIdToNumber(node.id))
|
||||
}
|
||||
}
|
||||
for (const n of nodesData ?? []) {
|
||||
if (typeof n.id === 'number') reservedNodeIds.add(n.id)
|
||||
if (n.id != null && isNumericNodeId(n.id))
|
||||
reservedNodeIds.add(nodeIdToNumber(n.id))
|
||||
}
|
||||
|
||||
const deduplicated = this.isRootGraph
|
||||
@@ -2554,7 +2567,7 @@ export class LGraph
|
||||
}
|
||||
|
||||
// id it or it will create a new id
|
||||
node.id = n_info.id
|
||||
if (n_info.id != null) node.id = asNodeId(n_info.id)
|
||||
// add before configure, otherwise configure cannot create links
|
||||
this.add(node, true)
|
||||
nodeDataMap.set(node.id, n_info)
|
||||
@@ -2676,24 +2689,24 @@ export class LGraph
|
||||
const remappedIds = new Map<NodeId, NodeId>()
|
||||
|
||||
for (const node of graph._nodes) {
|
||||
if (typeof node.id !== 'number') continue
|
||||
if (!isNumericNodeId(node.id)) continue
|
||||
|
||||
if (usedNodeIds.has(node.id)) {
|
||||
const numericId = nodeIdToNumber(node.id)
|
||||
if (usedNodeIds.has(numericId)) {
|
||||
const oldId = node.id
|
||||
while (usedNodeIds.has(++state.lastNodeId));
|
||||
const newId = state.lastNodeId
|
||||
const newId = asNodeId(state.lastNodeId)
|
||||
delete graph._nodes_by_id[oldId]
|
||||
node.id = newId
|
||||
graph._nodes_by_id[newId] = node
|
||||
usedNodeIds.add(newId)
|
||||
usedNodeIds.add(state.lastNodeId)
|
||||
remappedIds.set(oldId, newId)
|
||||
console.warn(
|
||||
`LiteGraph: duplicate node ID ${oldId} reassigned to ${newId} in graph ${graph.id}`
|
||||
)
|
||||
} else {
|
||||
usedNodeIds.add(node.id as number)
|
||||
if ((node.id as number) > state.lastNodeId)
|
||||
state.lastNodeId = node.id as number
|
||||
usedNodeIds.add(numericId)
|
||||
if (numericId > state.lastNodeId) state.lastNodeId = numericId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2877,16 +2890,16 @@ export class Subgraph
|
||||
*/
|
||||
private _repairIOSlotLinkIds(): void {
|
||||
for (const [slotIndex, slot] of this.inputs.entries()) {
|
||||
this._repairSlotLinkIds(slot.linkIds, SUBGRAPH_INPUT_ID, slotIndex)
|
||||
this._repairSlotLinkIds(slot.linkIds, SUBGRAPH_INPUT_NODE_ID, slotIndex)
|
||||
}
|
||||
for (const [slotIndex, slot] of this.outputs.entries()) {
|
||||
this._repairSlotLinkIds(slot.linkIds, SUBGRAPH_OUTPUT_ID, slotIndex)
|
||||
this._repairSlotLinkIds(slot.linkIds, SUBGRAPH_OUTPUT_NODE_ID, slotIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private _repairSlotLinkIds(
|
||||
linkIds: LinkId[],
|
||||
ioNodeId: number,
|
||||
ioNodeId: NodeId,
|
||||
slotIndex: number
|
||||
): void {
|
||||
const repaired = linkIds.map((id) =>
|
||||
@@ -2900,7 +2913,7 @@ export class Subgraph
|
||||
}
|
||||
|
||||
private _findLinkBySlot(
|
||||
nodeId: number,
|
||||
nodeId: NodeId,
|
||||
slotIndex: number
|
||||
): LLink | undefined {
|
||||
for (const link of this._links.values()) {
|
||||
@@ -3104,10 +3117,10 @@ function patchLinkNodeIds(
|
||||
remappedIds: Map<NodeId, NodeId>
|
||||
): void {
|
||||
for (const link of links.values()) {
|
||||
const newOrigin = remappedIds.get(link.origin_id)
|
||||
const newOrigin = remappedIds.get(asNodeId(link.origin_id))
|
||||
if (newOrigin !== undefined) link.origin_id = newOrigin
|
||||
|
||||
const newTarget = remappedIds.get(link.target_id)
|
||||
const newTarget = remappedIds.get(asNodeId(link.target_id))
|
||||
if (newTarget !== undefined) link.target_id = newTarget
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ import { flushProxyWidgetMigration } from '@/core/graph/subgraph/migration/proxy
|
||||
import { autoExposeKnownPreviewNodes } from '@/core/graph/subgraph/promotionUtils'
|
||||
import type { Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
asNodeId,
|
||||
LGraph,
|
||||
LGraphCanvas,
|
||||
LGraphNode,
|
||||
LiteGraph,
|
||||
SubgraphNode,
|
||||
UNASSIGNED_NODE_ID,
|
||||
createUuidv4
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { remapClipboardSubgraphNodeIds } from '@/lib/litegraph/src/LGraphCanvas'
|
||||
@@ -19,6 +21,7 @@ import type {
|
||||
ExportedSubgraph,
|
||||
ISerialisedNode
|
||||
} from '@/lib/litegraph/src/types/serialisation'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import { usePreviewExposureStore } from '@/stores/previewExposureStore'
|
||||
import { createMockCanvasRenderingContext2D } from '@/utils/__tests__/litegraphTestUtils'
|
||||
|
||||
@@ -35,7 +38,7 @@ function createSerialisedNode(
|
||||
proxyWidgets?: Array<[string, string]>
|
||||
): ISerialisedNode {
|
||||
return {
|
||||
id,
|
||||
id: asNodeId(id),
|
||||
type,
|
||||
pos: [0, 0],
|
||||
size: [140, 80],
|
||||
@@ -52,7 +55,7 @@ describe('remapClipboardSubgraphNodeIds', () => {
|
||||
it('remaps pasted subgraph interior IDs and proxyWidgets references', () => {
|
||||
const rootGraph = new LGraph()
|
||||
const existingNode = new LGraphNode('existing')
|
||||
existingNode.id = 1
|
||||
existingNode.id = asNodeId(1)
|
||||
rootGraph.add(existingNode)
|
||||
|
||||
const subgraphId = createUuidv4()
|
||||
@@ -69,11 +72,11 @@ describe('remapClipboardSubgraphNodeIds', () => {
|
||||
config: {},
|
||||
name: 'Pasted Subgraph',
|
||||
inputNode: {
|
||||
id: -10,
|
||||
id: asNodeId(-10),
|
||||
bounding: [0, 0, 10, 10]
|
||||
},
|
||||
outputNode: {
|
||||
id: -20,
|
||||
id: asNodeId(-20),
|
||||
bounding: [0, 0, 10, 10]
|
||||
},
|
||||
inputs: [],
|
||||
@@ -84,9 +87,9 @@ describe('remapClipboardSubgraphNodeIds', () => {
|
||||
{
|
||||
id: 1,
|
||||
type: '*',
|
||||
origin_id: 1,
|
||||
origin_id: asNodeId(1),
|
||||
origin_slot: 0,
|
||||
target_id: 1,
|
||||
target_id: asNodeId(1),
|
||||
target_slot: 0
|
||||
}
|
||||
],
|
||||
@@ -121,10 +124,85 @@ describe('remapClipboardSubgraphNodeIds', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('remaps legacy numeric interior IDs, links, and proxyWidgets references', () => {
|
||||
const rootGraph = new LGraph()
|
||||
const existingNode = new LGraphNode('existing')
|
||||
existingNode.id = asNodeId(1)
|
||||
rootGraph.add(existingNode)
|
||||
|
||||
const subgraphId = createUuidv4()
|
||||
// Legacy clipboard JSON written by a pre-branding frontend: numeric ids.
|
||||
const legacyNumericId = 1 as unknown as NodeId
|
||||
const pastedSubgraph: ExportedSubgraph = {
|
||||
id: subgraphId,
|
||||
version: 1,
|
||||
revision: 0,
|
||||
state: { lastNodeId: 0, lastLinkId: 0, lastGroupId: 0, lastRerouteId: 0 },
|
||||
config: {},
|
||||
name: 'Pasted Subgraph',
|
||||
inputNode: { id: asNodeId(-10), bounding: [0, 0, 10, 10] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [0, 0, 10, 10] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [],
|
||||
nodes: [
|
||||
{
|
||||
id: legacyNumericId,
|
||||
type: 'test/node',
|
||||
pos: [0, 0],
|
||||
size: [140, 80],
|
||||
flags: {},
|
||||
order: 0,
|
||||
mode: 0,
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
properties: {}
|
||||
}
|
||||
],
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
type: '*',
|
||||
origin_id: 1 as unknown as NodeId,
|
||||
origin_slot: 0,
|
||||
target_id: 1 as unknown as NodeId,
|
||||
target_slot: 0
|
||||
}
|
||||
],
|
||||
groups: []
|
||||
}
|
||||
|
||||
const hostInfo = createSerialisedNode(99, subgraphId, [
|
||||
[1 as unknown as string, 'seed']
|
||||
])
|
||||
|
||||
const parsed: ClipboardItems = {
|
||||
nodes: [hostInfo],
|
||||
groups: [],
|
||||
reroutes: [],
|
||||
links: [],
|
||||
subgraphs: [pastedSubgraph]
|
||||
}
|
||||
|
||||
remapClipboardSubgraphNodeIds(parsed, rootGraph)
|
||||
|
||||
const remappedInteriorId = parsed.subgraphs?.[0]?.nodes?.[0]?.id
|
||||
expect(remappedInteriorId).not.toBe(1)
|
||||
expect(remappedInteriorId).not.toBe('1')
|
||||
|
||||
const remappedLink = parsed.subgraphs?.[0]?.links?.[0]
|
||||
expect(remappedLink?.origin_id).toBe(remappedInteriorId)
|
||||
expect(remappedLink?.target_id).toBe(remappedInteriorId)
|
||||
|
||||
expect(parsed.nodes?.[0]?.properties?.proxyWidgets).toStrictEqual([
|
||||
[String(remappedInteriorId), 'seed']
|
||||
])
|
||||
})
|
||||
|
||||
it('remaps pasted SubgraphNode previewExposures sourceNodeId references', () => {
|
||||
const rootGraph = new LGraph()
|
||||
const existingNode = new LGraphNode('existing')
|
||||
existingNode.id = 1
|
||||
existingNode.id = asNodeId(1)
|
||||
rootGraph.add(existingNode)
|
||||
|
||||
const subgraphId = createUuidv4()
|
||||
@@ -140,8 +218,8 @@ describe('remapClipboardSubgraphNodeIds', () => {
|
||||
},
|
||||
config: {},
|
||||
name: 'Pasted Subgraph',
|
||||
inputNode: { id: -10, bounding: [0, 0, 10, 10] },
|
||||
outputNode: { id: -20, bounding: [0, 0, 10, 10] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [0, 0, 10, 10] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [0, 0, 10, 10] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [],
|
||||
@@ -209,7 +287,7 @@ describe('_deserializeItems paste-time migration & auto-expose', () => {
|
||||
class TestSubgraphNode extends SubgraphNode {
|
||||
constructor() {
|
||||
super(rootGraph, subgraph as Subgraph, {
|
||||
id: -1,
|
||||
id: UNASSIGNED_NODE_ID,
|
||||
type: subgraph.id,
|
||||
pos: [0, 0],
|
||||
size: [100, 100],
|
||||
@@ -264,14 +342,14 @@ describe('_deserializeItems paste-time migration & auto-expose', () => {
|
||||
},
|
||||
config: {},
|
||||
name: 'Pasted Subgraph',
|
||||
inputNode: { id: -10, bounding: [0, 0, 10, 10] },
|
||||
outputNode: { id: -20, bounding: [0, 0, 10, 10] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [0, 0, 10, 10] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [0, 0, 10, 10] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [],
|
||||
nodes: [
|
||||
{
|
||||
id: interiorId,
|
||||
id: asNodeId(interiorId),
|
||||
type: 'test/inner',
|
||||
pos: [0, 0],
|
||||
size: [140, 80],
|
||||
@@ -288,7 +366,7 @@ describe('_deserializeItems paste-time migration & auto-expose', () => {
|
||||
}
|
||||
|
||||
const hostInfo: ISerialisedNode = {
|
||||
id: 99,
|
||||
id: asNodeId(99),
|
||||
type: subgraphId,
|
||||
pos: [0, 0],
|
||||
size: [140, 80],
|
||||
@@ -340,14 +418,14 @@ describe('_deserializeItems paste-time migration & auto-expose', () => {
|
||||
},
|
||||
config: {},
|
||||
name: 'Pasted Subgraph',
|
||||
inputNode: { id: -10, bounding: [0, 0, 10, 10] },
|
||||
outputNode: { id: -20, bounding: [0, 0, 10, 10] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [0, 0, 10, 10] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [0, 0, 10, 10] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [],
|
||||
nodes: [
|
||||
{
|
||||
id: interiorPreviewId,
|
||||
id: asNodeId(interiorPreviewId),
|
||||
type: 'PreviewImage',
|
||||
pos: [0, 0],
|
||||
size: [140, 80],
|
||||
@@ -364,7 +442,7 @@ describe('_deserializeItems paste-time migration & auto-expose', () => {
|
||||
}
|
||||
|
||||
const hostInfo: ISerialisedNode = {
|
||||
id: 99,
|
||||
id: asNodeId(99),
|
||||
type: subgraphId,
|
||||
pos: [0, 0],
|
||||
size: [140, 80],
|
||||
|
||||
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
import type { NodeLayout } from '@/renderer/core/layout/types'
|
||||
import type { NodeId, NodeLayout } from '@/renderer/core/layout/types'
|
||||
|
||||
import {
|
||||
LGraph,
|
||||
@@ -74,7 +74,7 @@ function createCanvas(graph: LGraph): LGraphCanvas {
|
||||
}
|
||||
|
||||
function createLayoutEntry(node: LGraphNode, zIndex: number) {
|
||||
const nodeId = String(node.id)
|
||||
const nodeId = node.id
|
||||
const layout: NodeLayout = {
|
||||
id: nodeId,
|
||||
position: { x: node.pos[0], y: node.pos[1] },
|
||||
@@ -99,7 +99,7 @@ function createLayoutEntry(node: LGraphNode, zIndex: number) {
|
||||
})
|
||||
}
|
||||
|
||||
function setZIndex(nodeId: string, zIndex: number, previousZIndex: number) {
|
||||
function setZIndex(nodeId: NodeId, zIndex: number, previousZIndex: number) {
|
||||
layoutStore.applyOperation({
|
||||
type: 'setNodeZIndex',
|
||||
entity: 'node',
|
||||
@@ -145,7 +145,7 @@ describe('cloned node z-index in Vue renderer', () => {
|
||||
originalNode.size = [200, 100]
|
||||
graph.add(originalNode)
|
||||
|
||||
const originalNodeId = String(originalNode.id)
|
||||
const originalNodeId = originalNode.id
|
||||
|
||||
setZIndex(originalNodeId, 5, 0)
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('cloned node z-index in Vue renderer', () => {
|
||||
expect(result!.created.length).toBe(1)
|
||||
|
||||
const clonedNode = result!.created[0] as LGraphNode
|
||||
const clonedNodeId = String(clonedNode.id)
|
||||
const clonedNodeId = clonedNode.id
|
||||
|
||||
// The cloned node should have a z-index higher than the original
|
||||
const clonedLayout = layoutStore.getNodeLayoutRef(clonedNodeId).value
|
||||
@@ -171,13 +171,13 @@ describe('cloned node z-index in Vue renderer', () => {
|
||||
nodeA.pos = [100, 100]
|
||||
nodeA.size = [200, 100]
|
||||
graph.add(nodeA)
|
||||
setZIndex(String(nodeA.id), 3, 0)
|
||||
setZIndex(nodeA.id, 3, 0)
|
||||
|
||||
const nodeB = new TestNode()
|
||||
nodeB.pos = [400, 100]
|
||||
nodeB.size = [200, 100]
|
||||
graph.add(nodeB)
|
||||
setZIndex(String(nodeB.id), 7, 0)
|
||||
setZIndex(nodeB.id, 7, 0)
|
||||
|
||||
const result = LGraphCanvas.cloneNodes([nodeA, nodeB])
|
||||
expect(result).toBeDefined()
|
||||
@@ -185,8 +185,8 @@ describe('cloned node z-index in Vue renderer', () => {
|
||||
|
||||
const clonedA = result!.created[0] as LGraphNode
|
||||
const clonedB = result!.created[1] as LGraphNode
|
||||
const layoutA = layoutStore.getNodeLayoutRef(String(clonedA.id)).value!
|
||||
const layoutB = layoutStore.getNodeLayoutRef(String(clonedB.id)).value!
|
||||
const layoutA = layoutStore.getNodeLayoutRef(clonedA.id).value!
|
||||
const layoutB = layoutStore.getNodeLayoutRef(clonedB.id).value!
|
||||
|
||||
// Both cloned nodes should be above the highest original (z-index 7)
|
||||
expect(layoutA.zIndex).toBeGreaterThan(7)
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('LGraphCanvas slot hit detection', () => {
|
||||
|
||||
// Mock the slot query to return our node's slot
|
||||
vi.mocked(layoutStore.querySlotAtPoint).mockReturnValue({
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
index: 0,
|
||||
type: 'output',
|
||||
position: { x: 252, y: 120 },
|
||||
@@ -188,7 +188,7 @@ describe('LGraphCanvas slot hit detection', () => {
|
||||
expect(node.isPointInside(clickX, clickY)).toBe(false)
|
||||
|
||||
vi.mocked(layoutStore.querySlotAtPoint).mockReturnValue({
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
index: 0,
|
||||
type: 'input',
|
||||
position: { x: 98, y: 140 },
|
||||
|
||||
@@ -112,6 +112,13 @@ import type { NeverNever, PickNevers } from './types/utility'
|
||||
import type { IBaseWidget, TWidgetValue } from './types/widgets'
|
||||
import { alignNodes, distributeNodes, getBoundaryNodes } from './utils/arrange'
|
||||
import { findFirstNode, getAllNestedItems } from './utils/collections'
|
||||
import type { NodeIdInput } from '@/types/nodeId'
|
||||
import {
|
||||
asNodeId,
|
||||
isNumericNodeId,
|
||||
nodeIdToNumber,
|
||||
UNASSIGNED_NODE_ID
|
||||
} from '@/types/nodeId'
|
||||
import { resolveConnectingLinkColor } from './utils/linkColors'
|
||||
import { createUuidv4 } from '@/utils/uuid'
|
||||
import { BaseWidget } from './widgets/BaseWidget'
|
||||
@@ -4216,7 +4223,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
}
|
||||
|
||||
nodes.set(info.id, node)
|
||||
info.id = -1
|
||||
info.id = UNASSIGNED_NODE_ID
|
||||
|
||||
graph.add(node)
|
||||
node.configure(info)
|
||||
@@ -4309,7 +4316,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
const newPositions = created
|
||||
.filter((item): item is LGraphNode => item instanceof LGraphNode)
|
||||
.map((node) => ({
|
||||
nodeId: String(node.id),
|
||||
nodeId: node.id,
|
||||
bounds: {
|
||||
x: node.pos[0],
|
||||
y: node.pos[1],
|
||||
@@ -8980,26 +8987,26 @@ function patchLinkNodeIds(
|
||||
if (!links?.length) return
|
||||
|
||||
for (const link of links) {
|
||||
const newOriginId = remappedIds.get(link.origin_id)
|
||||
const newOriginId = remappedIds.get(asNodeId(link.origin_id))
|
||||
if (newOriginId !== undefined) link.origin_id = newOriginId
|
||||
|
||||
const newTargetId = remappedIds.get(link.target_id)
|
||||
const newTargetId = remappedIds.get(asNodeId(link.target_id))
|
||||
if (newTargetId !== undefined) link.target_id = newTargetId
|
||||
}
|
||||
}
|
||||
|
||||
function isNodeIdValue(value: unknown): value is NodeIdInput {
|
||||
return typeof value === 'string' || typeof value === 'number'
|
||||
}
|
||||
|
||||
/** Looks up a remap, tolerating legacy numeric ids and skipping sentinels. */
|
||||
function remapNodeId(
|
||||
nodeId: string,
|
||||
nodeId: NodeIdInput,
|
||||
remappedIds: Map<NodeId, NodeId>
|
||||
): NodeId | undefined {
|
||||
const directMatch = remappedIds.get(nodeId)
|
||||
if (directMatch !== undefined) return directMatch
|
||||
if (!/^-?\d+$/.test(nodeId)) return undefined
|
||||
|
||||
const numericId = Number(nodeId)
|
||||
if (!Number.isSafeInteger(numericId)) return undefined
|
||||
|
||||
return remappedIds.get(numericId)
|
||||
const normalized = asNodeId(nodeId)
|
||||
if (normalized === UNASSIGNED_NODE_ID) return undefined
|
||||
return remappedIds.get(normalized)
|
||||
}
|
||||
|
||||
function remapProxyWidgets(
|
||||
@@ -9015,21 +9022,21 @@ function remapProxyWidgets(
|
||||
if (!Array.isArray(entry)) continue
|
||||
|
||||
const [nodeId] = entry
|
||||
if (typeof nodeId !== 'string' || nodeId === '-1') continue
|
||||
if (!isNodeIdValue(nodeId)) continue
|
||||
|
||||
const remappedNodeId = remapNodeId(nodeId, remappedIds)
|
||||
if (remappedNodeId !== undefined) entry[0] = String(remappedNodeId)
|
||||
}
|
||||
}
|
||||
|
||||
function hasStringSourceNodeId(
|
||||
function hasSourceNodeId(
|
||||
value: unknown
|
||||
): value is { sourceNodeId: string } {
|
||||
): value is { sourceNodeId: NodeIdInput } {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
'sourceNodeId' in value &&
|
||||
typeof value.sourceNodeId === 'string'
|
||||
isNodeIdValue(value.sourceNodeId)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9043,7 +9050,7 @@ function remapPreviewExposures(
|
||||
if (!Array.isArray(previewExposures)) return
|
||||
|
||||
for (const entry of previewExposures) {
|
||||
if (!hasStringSourceNodeId(entry) || entry.sourceNodeId === '-1') continue
|
||||
if (!hasSourceNodeId(entry)) continue
|
||||
|
||||
const remappedNodeId = remapNodeId(entry.sourceNodeId, remappedIds)
|
||||
if (remappedNodeId !== undefined)
|
||||
@@ -9057,17 +9064,18 @@ export function remapClipboardSubgraphNodeIds(
|
||||
): void {
|
||||
const usedNodeIds = new Set<number>()
|
||||
forEachNode(rootGraph, (node) => {
|
||||
if (typeof node.id !== 'number') return
|
||||
usedNodeIds.add(node.id)
|
||||
if (rootGraph.state.lastNodeId < node.id)
|
||||
rootGraph.state.lastNodeId = node.id
|
||||
if (!isNumericNodeId(node.id)) return
|
||||
const numericId = nodeIdToNumber(node.id)
|
||||
usedNodeIds.add(numericId)
|
||||
if (rootGraph.state.lastNodeId < numericId)
|
||||
rootGraph.state.lastNodeId = numericId
|
||||
})
|
||||
|
||||
function nextUniqueNodeId() {
|
||||
function nextUniqueNodeId(): NodeId {
|
||||
while (usedNodeIds.has(++rootGraph.state.lastNodeId));
|
||||
const nextId = rootGraph.state.lastNodeId
|
||||
usedNodeIds.add(nextId)
|
||||
return nextId
|
||||
return asNodeId(nextId)
|
||||
}
|
||||
|
||||
const subgraphNodeIdMap = new Map<SubgraphId, Map<NodeId, NodeId>>()
|
||||
@@ -9076,19 +9084,20 @@ export function remapClipboardSubgraphNodeIds(
|
||||
const interiorNodes = subgraphInfo.nodes ?? []
|
||||
|
||||
for (const nodeInfo of interiorNodes) {
|
||||
if (typeof nodeInfo.id !== 'number') continue
|
||||
if (!isNumericNodeId(nodeInfo.id)) continue
|
||||
|
||||
if (usedNodeIds.has(nodeInfo.id)) {
|
||||
const oldId = nodeInfo.id
|
||||
const numericId = nodeIdToNumber(nodeInfo.id)
|
||||
if (usedNodeIds.has(numericId)) {
|
||||
const oldId = asNodeId(nodeInfo.id)
|
||||
const newId = nextUniqueNodeId()
|
||||
remappedIds.set(oldId, newId)
|
||||
nodeInfo.id = newId
|
||||
continue
|
||||
}
|
||||
|
||||
usedNodeIds.add(nodeInfo.id)
|
||||
if (rootGraph.state.lastNodeId < nodeInfo.id)
|
||||
rootGraph.state.lastNodeId = nodeInfo.id
|
||||
usedNodeIds.add(numericId)
|
||||
if (rootGraph.state.lastNodeId < numericId)
|
||||
rootGraph.state.lastNodeId = numericId
|
||||
}
|
||||
|
||||
if (remappedIds.size > 0) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type { Rect } from '@/lib/litegraph/src/interfaces'
|
||||
import {
|
||||
asNodeId,
|
||||
LGraphNode,
|
||||
LiteGraph,
|
||||
LGraph,
|
||||
@@ -107,7 +108,7 @@ describe('LGraphNode', () => {
|
||||
const node = new LGraphNode('TestNode')
|
||||
node.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 0,
|
||||
id: asNodeId(0),
|
||||
inputs: [{ name: 'TestInput', type: 'number', link: null }]
|
||||
})
|
||||
)
|
||||
@@ -117,8 +118,8 @@ describe('LGraphNode', () => {
|
||||
expect(node.inputs[0]).instanceOf(NodeInputSlot)
|
||||
|
||||
// Should not override existing inputs
|
||||
node.configure(getMockISerialisedNode({ id: 1 }))
|
||||
expect(node.id).toEqual(1)
|
||||
node.configure(getMockISerialisedNode({ id: asNodeId(1) }))
|
||||
expect(node.id).toEqual(asNodeId(1))
|
||||
expect(node.inputs.length).toEqual(1)
|
||||
})
|
||||
|
||||
@@ -126,7 +127,7 @@ describe('LGraphNode', () => {
|
||||
const node = new LGraphNode('TestNode')
|
||||
node.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 0,
|
||||
id: asNodeId(0),
|
||||
outputs: [{ name: 'TestOutput', type: 'number', links: [] }]
|
||||
})
|
||||
)
|
||||
@@ -137,16 +138,16 @@ describe('LGraphNode', () => {
|
||||
expect(node.outputs[0]).instanceOf(NodeOutputSlot)
|
||||
|
||||
// Should not override existing outputs
|
||||
node.configure(getMockISerialisedNode({ id: 1 }))
|
||||
expect(node.id).toEqual(1)
|
||||
node.configure(getMockISerialisedNode({ id: asNodeId(1) }))
|
||||
expect(node.id).toEqual(asNodeId(1))
|
||||
expect(node.outputs.length).toEqual(1)
|
||||
})
|
||||
test('should not allow configuring id to -1', () => {
|
||||
const graph = new LGraph()
|
||||
const node = new LGraphNode('TestNode')
|
||||
graph.add(node)
|
||||
node.configure(getMockISerialisedNode({ id: -1 }))
|
||||
expect(node.id).not.toBe(-1)
|
||||
node.configure(getMockISerialisedNode({ id: asNodeId(-1) }))
|
||||
expect(node.id).not.toBe(asNodeId(-1))
|
||||
})
|
||||
|
||||
describe('Disconnect I/O Slots', () => {
|
||||
@@ -157,13 +158,13 @@ describe('LGraphNode', () => {
|
||||
// Configure nodes with input/output slots
|
||||
node1.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
outputs: [{ name: 'Output1', type: 'number', links: [] }]
|
||||
})
|
||||
)
|
||||
node2.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 2,
|
||||
id: asNodeId(2),
|
||||
inputs: [{ name: 'Input1', type: 'number', link: null }]
|
||||
})
|
||||
)
|
||||
@@ -209,7 +210,7 @@ describe('LGraphNode', () => {
|
||||
// Configure nodes with input/output slots
|
||||
sourceNode.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
outputs: [
|
||||
{ name: 'Output1', type: 'number', links: [] },
|
||||
{ name: 'Output2', type: 'number', links: [] }
|
||||
@@ -218,13 +219,13 @@ describe('LGraphNode', () => {
|
||||
)
|
||||
targetNode1.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 2,
|
||||
id: asNodeId(2),
|
||||
inputs: [{ name: 'Input1', type: 'number', link: null }]
|
||||
})
|
||||
)
|
||||
targetNode2.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 3,
|
||||
id: asNodeId(3),
|
||||
inputs: [{ name: 'Input1', type: 'number', link: null }]
|
||||
})
|
||||
)
|
||||
@@ -319,7 +320,7 @@ describe('LGraphNode', () => {
|
||||
node.updateArea()
|
||||
node.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
inputs: [{ name: 'Input1', type: 'number', link: null }],
|
||||
outputs: [{ name: 'Output1', type: 'number', links: [] }]
|
||||
})
|
||||
@@ -342,7 +343,7 @@ describe('LGraphNode', () => {
|
||||
node.size = [100, 100]
|
||||
node.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
inputs: [{ name: 'Input1', type: 'number', link: null }],
|
||||
outputs: [{ name: 'Output1', type: 'number', links: [] }]
|
||||
})
|
||||
@@ -363,7 +364,7 @@ describe('LGraphNode', () => {
|
||||
node.size = [100, 100]
|
||||
node.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
inputs: [{ name: 'Input1', type: 'number', link: null }],
|
||||
outputs: [{ name: 'Output1', type: 'number', links: [] }]
|
||||
})
|
||||
@@ -382,7 +383,7 @@ describe('LGraphNode', () => {
|
||||
node.updateArea()
|
||||
node.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
inputs: [
|
||||
{ name: 'Input1', type: 'number', link: null },
|
||||
{ name: 'Input2', type: 'string', link: null }
|
||||
@@ -408,7 +409,7 @@ describe('LGraphNode', () => {
|
||||
node.updateArea()
|
||||
node.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
outputs: [
|
||||
{ name: 'Output1', type: 'number', links: [] },
|
||||
{ name: 'Output2', type: 'string', links: [] }
|
||||
@@ -435,7 +436,7 @@ describe('LGraphNode', () => {
|
||||
node.updateArea()
|
||||
node.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
inputs: [{ name: 'Input1', type: 'number', link: null }],
|
||||
outputs: [{ name: 'Output1', type: 'number', links: [] }]
|
||||
})
|
||||
@@ -575,7 +576,7 @@ describe('LGraphNode', () => {
|
||||
node.widgets![0].serialize = false
|
||||
node.configure(
|
||||
getMockISerialisedNode({
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
type: 'TestNode',
|
||||
pos: [100, 100],
|
||||
size: [100, 100],
|
||||
|
||||
@@ -16,8 +16,15 @@ import {
|
||||
toClass
|
||||
} from '@/lib/litegraph/src/utils/type'
|
||||
|
||||
import { SUBGRAPH_OUTPUT_ID } from '@/lib/litegraph/src/constants'
|
||||
import { cachedMeasureText } from '@/lib/litegraph/src/utils/textMeasureCache'
|
||||
import {
|
||||
UNASSIGNED_NODE_ID,
|
||||
asNodeId,
|
||||
isSubgraphInputNodeId,
|
||||
isSubgraphOutputNodeId,
|
||||
isUnassignedNodeId
|
||||
} from '@/types/nodeId'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { DragAndScale } from './DragAndScale'
|
||||
import type { LGraph } from './LGraph'
|
||||
import { BadgePosition, LGraphBadge } from './LGraphBadge'
|
||||
@@ -98,7 +105,7 @@ import type { WidgetTypeMap } from './widgets/widgetMap'
|
||||
|
||||
// #region Types
|
||||
|
||||
export type NodeId = number | string
|
||||
export type { NodeId } from '@/types/nodeId'
|
||||
|
||||
export type NodeProperty = string | number | boolean | object
|
||||
|
||||
@@ -501,7 +508,7 @@ export class LGraphNode
|
||||
|
||||
const mutations = useLayoutMutations()
|
||||
mutations.setSource(LayoutSource.Canvas)
|
||||
mutations.moveNode(String(this.id), { x: value[0], y: value[1] })
|
||||
mutations.moveNode(this.id, { x: value[0], y: value[1] })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -523,7 +530,7 @@ export class LGraphNode
|
||||
|
||||
const mutations = useLayoutMutations()
|
||||
mutations.setSource(LayoutSource.Canvas)
|
||||
mutations.resizeNode(String(this.id), {
|
||||
mutations.resizeNode(this.id, {
|
||||
width: value[0],
|
||||
height: value[1]
|
||||
})
|
||||
@@ -810,7 +817,7 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
constructor(title: string, type?: string) {
|
||||
this.id = -1
|
||||
this.id = UNASSIGNED_NODE_ID
|
||||
this.title = title || 'Unnamed'
|
||||
this.type = type ?? ''
|
||||
this.size = [LiteGraph.NODE_WIDTH, 60]
|
||||
@@ -833,7 +840,7 @@ export class LGraphNode
|
||||
if (this.graph) {
|
||||
this.graph.incrementVersion()
|
||||
}
|
||||
if (info.id === -1) info.id = this.id
|
||||
if (isUnassignedNodeId(info.id)) info.id = this.id
|
||||
for (const j in info) {
|
||||
if (j == 'properties') {
|
||||
// i don't want to clone properties, I want to reuse the old container
|
||||
@@ -864,6 +871,8 @@ export class LGraphNode
|
||||
}
|
||||
}
|
||||
|
||||
if (!isUnassignedNodeId(this.id)) this.id = asNodeId(this.id)
|
||||
|
||||
if (!info.title) {
|
||||
this.title = this.constructor.title
|
||||
}
|
||||
@@ -1999,9 +2008,9 @@ export class LGraphNode
|
||||
this._widgetSlotsDirty = true
|
||||
|
||||
// Only register with store if node has a valid ID (is already in a graph).
|
||||
// If the node isn't in a graph yet (id === -1), registration happens
|
||||
// If the node isn't in a graph yet, registration happens
|
||||
// when the node is added via LGraph.add() -> node.onAdded.
|
||||
if (this.id !== -1 && isNodeBindable(widget)) {
|
||||
if (!isUnassignedNodeId(this.id) && isNodeBindable(widget)) {
|
||||
widget.setNodeId(this.id)
|
||||
}
|
||||
|
||||
@@ -3162,7 +3171,7 @@ export class LGraphNode
|
||||
const link_info = graph._links.get(link_id)
|
||||
if (!link_info) continue
|
||||
if (
|
||||
link_info.target_id === SUBGRAPH_OUTPUT_ID &&
|
||||
isSubgraphOutputNodeId(link_info.target_id) &&
|
||||
graph instanceof Subgraph
|
||||
) {
|
||||
const targetSlot = graph.outputNode.slots[link_info.target_slot]
|
||||
@@ -3272,7 +3281,10 @@ export class LGraphNode
|
||||
const link_info = graph._links.get(link_id)
|
||||
if (link_info) {
|
||||
// Let SubgraphInput do the disconnect.
|
||||
if (link_info.origin_id === -10 && 'inputNode' in graph) {
|
||||
if (
|
||||
isSubgraphInputNodeId(link_info.origin_id) &&
|
||||
'inputNode' in graph
|
||||
) {
|
||||
graph.inputNode._disconnectNodeInput(this, input, link_info)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { asNodeId, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { ISerialisedNode } from '@/lib/litegraph/src/types/serialisation'
|
||||
import { sortWidgetValuesByInputOrder } from '@/workbench/utils/nodeDefOrderingUtil'
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('LGraphNode widget ordering', () => {
|
||||
|
||||
// Configure with widget values
|
||||
const info: ISerialisedNode = {
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
type: 'TestNode',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
@@ -50,7 +50,7 @@ describe('LGraphNode widget ordering', () => {
|
||||
|
||||
// Widget values are in input_order: [steps, seed, prompt]
|
||||
const info: ISerialisedNode = {
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
type: 'TestNode',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
@@ -81,7 +81,7 @@ describe('LGraphNode widget ordering', () => {
|
||||
node.addWidget('number', 'seed', 0, null, {})
|
||||
|
||||
const info: ISerialisedNode = {
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
type: 'TestNode',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
|
||||
@@ -1,17 +1,80 @@
|
||||
import { describe, expect } from 'vitest'
|
||||
|
||||
import { LLink } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
asNodeId,
|
||||
UNASSIGNED_NODE_ID,
|
||||
LLink,
|
||||
SUBGRAPH_INPUT_ID,
|
||||
SUBGRAPH_INPUT_NODE_ID,
|
||||
SUBGRAPH_OUTPUT_ID,
|
||||
SUBGRAPH_OUTPUT_NODE_ID
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { test } from './__fixtures__/testExtensions'
|
||||
|
||||
describe('LLink', () => {
|
||||
test('matches previous snapshot', () => {
|
||||
const link = new LLink(1, 'float', 4, 2, 5, 3)
|
||||
const link = new LLink(1, 'float', asNodeId(4), 2, asNodeId(5), 3)
|
||||
expect(link.serialize()).toMatchSnapshot('Basic')
|
||||
})
|
||||
|
||||
test('serializes to the previous snapshot', () => {
|
||||
const link = new LLink(1, 'float', 4, 2, 5, 3)
|
||||
const link = new LLink(1, 'float', asNodeId(4), 2, asNodeId(5), 3)
|
||||
expect(link.serialize()).toMatchSnapshot('Basic')
|
||||
})
|
||||
|
||||
describe('node-id branding at the edges', () => {
|
||||
test('brands numeric endpoints into NodeId at construction', () => {
|
||||
const link = new LLink(1, 'float', 4, 2, 5, 3)
|
||||
expect(link.origin_id).toBe(asNodeId(4))
|
||||
expect(link.target_id).toBe(asNodeId(5))
|
||||
expect(typeof link.origin_id).toBe('string')
|
||||
})
|
||||
|
||||
test('brands the floating sentinel into UNASSIGNED_NODE_ID', () => {
|
||||
const link = new LLink(1, 'float', -1, -1, asNodeId(5), 3)
|
||||
expect(link.origin_id).toBe(UNASSIGNED_NODE_ID)
|
||||
expect(link.isFloatingOutput).toBe(true)
|
||||
})
|
||||
|
||||
test('brands subgraph IO sentinels into branded sentinels', () => {
|
||||
const link = new LLink(
|
||||
1,
|
||||
'float',
|
||||
SUBGRAPH_INPUT_ID,
|
||||
0,
|
||||
SUBGRAPH_OUTPUT_ID,
|
||||
0
|
||||
)
|
||||
expect(link.origin_id).toBe(SUBGRAPH_INPUT_NODE_ID)
|
||||
expect(link.target_id).toBe(SUBGRAPH_OUTPUT_NODE_ID)
|
||||
expect(link.originIsIoNode).toBe(true)
|
||||
expect(link.targetIsIoNode).toBe(true)
|
||||
})
|
||||
|
||||
test('serializes sentinels as branded string ids', () => {
|
||||
const floating = new LLink(1, 'float', -1, -1, asNodeId(5), 3)
|
||||
expect(floating.asSerialisable().origin_id).toBe(UNASSIGNED_NODE_ID)
|
||||
expect(floating.serialize()[1]).toBe(UNASSIGNED_NODE_ID)
|
||||
|
||||
const io = new LLink(
|
||||
2,
|
||||
'float',
|
||||
SUBGRAPH_INPUT_ID,
|
||||
0,
|
||||
SUBGRAPH_OUTPUT_ID,
|
||||
0
|
||||
)
|
||||
const serialised = io.asSerialisable()
|
||||
expect(serialised.origin_id).toBe(SUBGRAPH_INPUT_NODE_ID)
|
||||
expect(serialised.target_id).toBe(SUBGRAPH_OUTPUT_NODE_ID)
|
||||
})
|
||||
|
||||
test('serializes real node ids as branded strings (round-trips)', () => {
|
||||
const link = new LLink(1, 'float', 4, 2, 5, 3)
|
||||
const restored = LLink.create(link.asSerialisable())
|
||||
expect(restored.origin_id).toBe(asNodeId(4))
|
||||
expect(restored.target_id).toBe(asNodeId(5))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import {
|
||||
SUBGRAPH_INPUT_ID,
|
||||
SUBGRAPH_OUTPUT_ID
|
||||
} from '@/lib/litegraph/src/constants'
|
||||
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'
|
||||
import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput'
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
|
||||
import type { LGraphNode, NodeId } from './LGraphNode'
|
||||
import type { NodeIdInput } from '@/types/nodeId'
|
||||
import {
|
||||
UNASSIGNED_NODE_ID,
|
||||
asNodeId,
|
||||
isFloatingNodeId,
|
||||
isSubgraphInputNodeId,
|
||||
isSubgraphOutputNodeId
|
||||
} from '@/types/nodeId'
|
||||
import type { Reroute, RerouteId } from './Reroute'
|
||||
import type {
|
||||
CanvasColour,
|
||||
@@ -130,11 +134,11 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
}
|
||||
|
||||
public get isFloatingOutput(): boolean {
|
||||
return this.origin_id === -1 && this.origin_slot === -1
|
||||
return isFloatingNodeId(this.origin_id) && this.origin_slot === -1
|
||||
}
|
||||
|
||||
public get isFloatingInput(): boolean {
|
||||
return this.target_id === -1 && this.target_slot === -1
|
||||
return isFloatingNodeId(this.target_id) && this.target_slot === -1
|
||||
}
|
||||
|
||||
public get isFloating(): boolean {
|
||||
@@ -143,28 +147,28 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
|
||||
/** `true` if this link is connected to a subgraph input node (the actual origin is in a different graph). */
|
||||
get originIsIoNode(): boolean {
|
||||
return this.origin_id === SUBGRAPH_INPUT_ID
|
||||
return isSubgraphInputNodeId(this.origin_id)
|
||||
}
|
||||
|
||||
/** `true` if this link is connected to a subgraph output node (the actual target is in a different graph). */
|
||||
get targetIsIoNode(): boolean {
|
||||
return this.target_id === SUBGRAPH_OUTPUT_ID
|
||||
return isSubgraphOutputNodeId(this.target_id)
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: LinkId,
|
||||
type: ISlotType,
|
||||
origin_id: NodeId,
|
||||
origin_id: NodeIdInput,
|
||||
origin_slot: number,
|
||||
target_id: NodeId,
|
||||
target_id: NodeIdInput,
|
||||
target_slot: number,
|
||||
parentId?: RerouteId
|
||||
) {
|
||||
this.id = id
|
||||
this.type = type
|
||||
this.origin_id = origin_id
|
||||
this.origin_id = asNodeId(origin_id)
|
||||
this.origin_slot = origin_slot
|
||||
this.target_id = target_id
|
||||
this.target_id = asNodeId(target_id)
|
||||
this.target_slot = target_slot
|
||||
this.parentId = parentId
|
||||
|
||||
@@ -307,10 +311,9 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
* it is recommended to use simpler methods where appropriate.
|
||||
*/
|
||||
resolve(network: BasicReadonlyNetwork): ResolvedConnection {
|
||||
const inputNode =
|
||||
this.target_id === -1
|
||||
? undefined
|
||||
: (network.getNodeById(this.target_id) ?? undefined)
|
||||
const inputNode = isFloatingNodeId(this.target_id)
|
||||
? undefined
|
||||
: (network.getNodeById(this.target_id) ?? undefined)
|
||||
const input = inputNode?.inputs[this.target_slot]
|
||||
const subgraphInput = this.originIsIoNode
|
||||
? network.inputNode?.slots[this.origin_slot]
|
||||
@@ -319,10 +322,9 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
return { inputNode, input, subgraphInput, link: this }
|
||||
}
|
||||
|
||||
const outputNode =
|
||||
this.origin_id === -1
|
||||
? undefined
|
||||
: (network.getNodeById(this.origin_id) ?? undefined)
|
||||
const outputNode = isFloatingNodeId(this.origin_id)
|
||||
? undefined
|
||||
: (network.getNodeById(this.origin_id) ?? undefined)
|
||||
const output = outputNode?.outputs[this.origin_slot]
|
||||
const subgraphOutput = this.targetIsIoNode
|
||||
? network.outputNode?.slots[this.target_slot]
|
||||
@@ -351,17 +353,17 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
configure(o: LLink | SerialisedLLinkArray) {
|
||||
if (Array.isArray(o)) {
|
||||
this.id = o[0]
|
||||
this.origin_id = o[1]
|
||||
this.origin_id = asNodeId(o[1])
|
||||
this.origin_slot = o[2]
|
||||
this.target_id = o[3]
|
||||
this.target_id = asNodeId(o[3])
|
||||
this.target_slot = o[4]
|
||||
this.type = o[5]
|
||||
} else {
|
||||
this.id = o.id
|
||||
this.type = o.type
|
||||
this.origin_id = o.origin_id
|
||||
this.origin_id = asNodeId(o.origin_id)
|
||||
this.origin_slot = o.origin_slot
|
||||
this.target_id = o.target_id
|
||||
this.target_id = asNodeId(o.target_id)
|
||||
this.target_slot = o.target_slot
|
||||
this.parentId = o.parentId
|
||||
}
|
||||
@@ -399,10 +401,10 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
exported.parentId = parentId
|
||||
|
||||
if (slotType === 'input') {
|
||||
exported.origin_id = -1
|
||||
exported.origin_id = UNASSIGNED_NODE_ID
|
||||
exported.origin_slot = -1
|
||||
} else {
|
||||
exported.target_id = -1
|
||||
exported.target_id = UNASSIGNED_NODE_ID
|
||||
exported.target_slot = -1
|
||||
}
|
||||
|
||||
@@ -432,12 +434,12 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
newLink.id = -1
|
||||
|
||||
if (keepReroutes === 'input') {
|
||||
newLink.origin_id = -1
|
||||
newLink.origin_id = UNASSIGNED_NODE_ID
|
||||
newLink.origin_slot = -1
|
||||
|
||||
lastReroute.floating = { slotType: 'input' }
|
||||
} else {
|
||||
newLink.target_id = -1
|
||||
newLink.target_id = UNASSIGNED_NODE_ID
|
||||
newLink.target_slot = -1
|
||||
|
||||
lastReroute.floating = { slotType: 'output' }
|
||||
|
||||
@@ -3,6 +3,7 @@ import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
|
||||
import { LGraphBadge } from './LGraphBadge'
|
||||
import type { LGraphNode, NodeId } from './LGraphNode'
|
||||
import { isFloatingNodeId } from '@/types/nodeId'
|
||||
import { LLink } from './LLink'
|
||||
import type { LinkId } from './LLink'
|
||||
import type {
|
||||
@@ -372,7 +373,7 @@ export class Reroute
|
||||
|
||||
for (const linkId of this.floatingLinkIds) {
|
||||
const link = floatingLinks.get(linkId)
|
||||
if (link?.[idProp] === -1) out.push(link)
|
||||
if (link && isFloatingNodeId(link[idProp])) out.push(link)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -807,7 +808,7 @@ function getNextPos(
|
||||
if (linkPos) return linkPos
|
||||
|
||||
// Floating link with no input to find
|
||||
if (link.target_id === -1 || link.target_slot === -1) return
|
||||
if (isFloatingNodeId(link.target_id) || link.target_slot === -1) return
|
||||
|
||||
return network.getNodeById(link.target_id)?.getInputPos(link.target_slot)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const oldSchemaGraph: ISerialisedGraph = {
|
||||
title: 'A group to test with'
|
||||
}
|
||||
],
|
||||
nodes: [{ id: 1 } as Partial<ISerialisedNode> as ISerialisedNode],
|
||||
nodes: [{ id: 1 } as unknown as ISerialisedNode],
|
||||
links: []
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SerialisableGraph } from '@/lib/litegraph/src/types/serialisation'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
/**
|
||||
* Root graph with two nodes (Source, Target) connected by one valid link
|
||||
@@ -17,7 +18,7 @@ export const duplicateLinksRoot: SerialisableGraph = {
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
type: 'test/DupTestNode',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
@@ -29,7 +30,7 @@ export const duplicateLinksRoot: SerialisableGraph = {
|
||||
properties: {}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
id: asNodeId(2),
|
||||
type: 'test/DupTestNode',
|
||||
pos: [300, 0],
|
||||
size: [200, 100],
|
||||
@@ -44,25 +45,25 @@ export const duplicateLinksRoot: SerialisableGraph = {
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
origin_id: 1,
|
||||
origin_id: asNodeId(1),
|
||||
origin_slot: 0,
|
||||
target_id: 2,
|
||||
target_id: asNodeId(2),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
origin_id: 1,
|
||||
origin_id: asNodeId(1),
|
||||
origin_slot: 0,
|
||||
target_id: 2,
|
||||
target_id: asNodeId(2),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
origin_id: 1,
|
||||
origin_id: asNodeId(1),
|
||||
origin_slot: 0,
|
||||
target_id: 2,
|
||||
target_id: asNodeId(2),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
}
|
||||
@@ -87,7 +88,7 @@ export const duplicateLinksSlotShift: SerialisableGraph = {
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
type: 'test/DupTestNode',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
@@ -99,7 +100,7 @@ export const duplicateLinksSlotShift: SerialisableGraph = {
|
||||
properties: {}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
id: asNodeId(2),
|
||||
type: 'test/DupTestNode',
|
||||
pos: [300, 0],
|
||||
size: [200, 100],
|
||||
@@ -117,17 +118,17 @@ export const duplicateLinksSlotShift: SerialisableGraph = {
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
origin_id: 1,
|
||||
origin_id: asNodeId(1),
|
||||
origin_slot: 0,
|
||||
target_id: 2,
|
||||
target_id: asNodeId(2),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
origin_id: 1,
|
||||
origin_id: asNodeId(1),
|
||||
origin_slot: 0,
|
||||
target_id: 2,
|
||||
target_id: asNodeId(2),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
}
|
||||
@@ -151,7 +152,7 @@ export const duplicateLinksSubgraph: SerialisableGraph = {
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
type: 'dd111111-1111-4111-8111-111111111111',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
@@ -175,14 +176,14 @@ export const duplicateLinksSubgraph: SerialisableGraph = {
|
||||
},
|
||||
name: 'Subgraph With Duplicates',
|
||||
config: {},
|
||||
inputNode: { id: -10, bounding: [0, 100, 120, 60] },
|
||||
outputNode: { id: -20, bounding: [500, 100, 120, 60] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [0, 100, 120, 60] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [500, 100, 120, 60] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [],
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
id: asNodeId(1),
|
||||
type: 'test/Source',
|
||||
pos: [100, 100],
|
||||
size: [200, 100],
|
||||
@@ -194,7 +195,7 @@ export const duplicateLinksSubgraph: SerialisableGraph = {
|
||||
properties: {}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
id: asNodeId(2),
|
||||
type: 'test/Target',
|
||||
pos: [400, 100],
|
||||
size: [200, 100],
|
||||
@@ -210,25 +211,25 @@ export const duplicateLinksSubgraph: SerialisableGraph = {
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
origin_id: 1,
|
||||
origin_id: asNodeId(1),
|
||||
origin_slot: 0,
|
||||
target_id: 2,
|
||||
target_id: asNodeId(2),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
origin_id: 1,
|
||||
origin_id: asNodeId(1),
|
||||
origin_slot: 0,
|
||||
target_id: 2,
|
||||
target_id: asNodeId(2),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
origin_id: 1,
|
||||
origin_id: asNodeId(1),
|
||||
origin_slot: 0,
|
||||
target_id: 2,
|
||||
target_id: asNodeId(2),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SerialisableGraph } from '@/lib/litegraph/src/types/serialisation'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
/**
|
||||
* Workflow with two subgraph definitions whose internal nodes share
|
||||
@@ -20,7 +21,7 @@ export const duplicateSubgraphNodeIds = {
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 102,
|
||||
id: asNodeId(102),
|
||||
type: '11111111-1111-4111-8111-111111111111',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
@@ -30,7 +31,7 @@ export const duplicateSubgraphNodeIds = {
|
||||
properties: { proxyWidgets: [['3', 'seed']] }
|
||||
},
|
||||
{
|
||||
id: 103,
|
||||
id: asNodeId(103),
|
||||
type: '22222222-2222-4222-8222-222222222222',
|
||||
pos: [300, 0],
|
||||
size: [200, 100],
|
||||
@@ -54,14 +55,14 @@ export const duplicateSubgraphNodeIds = {
|
||||
},
|
||||
name: 'SubgraphA',
|
||||
config: {},
|
||||
inputNode: { id: -10, bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: -20, bounding: [400, 100, 140, 126] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [400, 100, 140, 126] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [{ id: 3, name: 'seed' }],
|
||||
widgets: [{ id: asNodeId(3), name: 'seed' }],
|
||||
nodes: [
|
||||
{
|
||||
id: 3,
|
||||
id: asNodeId(3),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -70,7 +71,7 @@ export const duplicateSubgraphNodeIds = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
id: asNodeId(8),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -79,7 +80,7 @@ export const duplicateSubgraphNodeIds = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 37,
|
||||
id: asNodeId(37),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -91,9 +92,9 @@ export const duplicateSubgraphNodeIds = {
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
origin_id: 3,
|
||||
origin_id: asNodeId(3),
|
||||
origin_slot: 0,
|
||||
target_id: 8,
|
||||
target_id: asNodeId(8),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
}
|
||||
@@ -112,14 +113,14 @@ export const duplicateSubgraphNodeIds = {
|
||||
},
|
||||
name: 'SubgraphB',
|
||||
config: {},
|
||||
inputNode: { id: -10, bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: -20, bounding: [400, 100, 140, 126] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [400, 100, 140, 126] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [{ id: 8, name: 'prompt' }],
|
||||
widgets: [{ id: asNodeId(8), name: 'prompt' }],
|
||||
nodes: [
|
||||
{
|
||||
id: 3,
|
||||
id: asNodeId(3),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -128,7 +129,7 @@ export const duplicateSubgraphNodeIds = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
id: asNodeId(8),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -137,7 +138,7 @@ export const duplicateSubgraphNodeIds = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 37,
|
||||
id: asNodeId(37),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -149,9 +150,9 @@ export const duplicateSubgraphNodeIds = {
|
||||
links: [
|
||||
{
|
||||
id: 2,
|
||||
origin_id: 3,
|
||||
origin_id: asNodeId(3),
|
||||
origin_slot: 0,
|
||||
target_id: 37,
|
||||
target_id: asNodeId(37),
|
||||
target_slot: 0,
|
||||
type: 'string'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SerialisableGraph } from '@/lib/litegraph/src/types/serialisation'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
/**
|
||||
* Workflow where SubgraphA contains a nested SubgraphNode referencing
|
||||
@@ -24,7 +25,7 @@ export const nestedSubgraphProxyWidgets = {
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 102,
|
||||
id: asNodeId(102),
|
||||
type: '11111111-1111-4111-8111-111111111111',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
@@ -34,7 +35,7 @@ export const nestedSubgraphProxyWidgets = {
|
||||
properties: { proxyWidgets: [['3', 'seed']] }
|
||||
},
|
||||
{
|
||||
id: 103,
|
||||
id: asNodeId(103),
|
||||
type: '22222222-2222-4222-8222-222222222222',
|
||||
pos: [300, 0],
|
||||
size: [200, 100],
|
||||
@@ -58,14 +59,14 @@ export const nestedSubgraphProxyWidgets = {
|
||||
},
|
||||
name: 'SubgraphA',
|
||||
config: {},
|
||||
inputNode: { id: -10, bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: -20, bounding: [400, 100, 140, 126] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [400, 100, 140, 126] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [{ id: 3, name: 'seed' }],
|
||||
widgets: [{ id: asNodeId(3), name: 'seed' }],
|
||||
nodes: [
|
||||
{
|
||||
id: 3,
|
||||
id: asNodeId(3),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -74,7 +75,7 @@ export const nestedSubgraphProxyWidgets = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
id: asNodeId(8),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -83,7 +84,7 @@ export const nestedSubgraphProxyWidgets = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 37,
|
||||
id: asNodeId(37),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -92,7 +93,7 @@ export const nestedSubgraphProxyWidgets = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 50,
|
||||
id: asNodeId(50),
|
||||
type: '22222222-2222-4222-8222-222222222222',
|
||||
pos: [200, 0],
|
||||
size: [100, 50],
|
||||
@@ -105,9 +106,9 @@ export const nestedSubgraphProxyWidgets = {
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
origin_id: 3,
|
||||
origin_id: asNodeId(3),
|
||||
origin_slot: 0,
|
||||
target_id: 8,
|
||||
target_id: asNodeId(8),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
}
|
||||
@@ -126,14 +127,14 @@ export const nestedSubgraphProxyWidgets = {
|
||||
},
|
||||
name: 'SubgraphB',
|
||||
config: {},
|
||||
inputNode: { id: -10, bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: -20, bounding: [400, 100, 140, 126] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [400, 100, 140, 126] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [{ id: 8, name: 'prompt' }],
|
||||
widgets: [{ id: asNodeId(8), name: 'prompt' }],
|
||||
nodes: [
|
||||
{
|
||||
id: 3,
|
||||
id: asNodeId(3),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -142,7 +143,7 @@ export const nestedSubgraphProxyWidgets = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
id: asNodeId(8),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -151,7 +152,7 @@ export const nestedSubgraphProxyWidgets = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 37,
|
||||
id: asNodeId(37),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -163,9 +164,9 @@ export const nestedSubgraphProxyWidgets = {
|
||||
links: [
|
||||
{
|
||||
id: 2,
|
||||
origin_id: 3,
|
||||
origin_id: asNodeId(3),
|
||||
origin_slot: 0,
|
||||
target_id: 37,
|
||||
target_id: asNodeId(37),
|
||||
target_slot: 0,
|
||||
type: 'string'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SerialisableGraph } from '@/lib/litegraph/src/types/serialisation'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
/**
|
||||
* Workflow where lastNodeId is near the MAX_NODE_ID ceiling (100_000_000)
|
||||
@@ -20,7 +21,7 @@ export const nodeIdSpaceExhausted = {
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 102,
|
||||
id: asNodeId(102),
|
||||
type: '11111111-1111-4111-8111-111111111111',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
@@ -30,7 +31,7 @@ export const nodeIdSpaceExhausted = {
|
||||
properties: { proxyWidgets: [['3', 'seed']] }
|
||||
},
|
||||
{
|
||||
id: 103,
|
||||
id: asNodeId(103),
|
||||
type: '22222222-2222-4222-8222-222222222222',
|
||||
pos: [300, 0],
|
||||
size: [200, 100],
|
||||
@@ -40,7 +41,7 @@ export const nodeIdSpaceExhausted = {
|
||||
properties: { proxyWidgets: [['8', 'prompt']] }
|
||||
},
|
||||
{
|
||||
id: 100_000_000,
|
||||
id: asNodeId(100_000_000),
|
||||
type: 'dummy',
|
||||
pos: [600, 0],
|
||||
size: [100, 50],
|
||||
@@ -63,14 +64,14 @@ export const nodeIdSpaceExhausted = {
|
||||
},
|
||||
name: 'SubgraphA',
|
||||
config: {},
|
||||
inputNode: { id: -10, bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: -20, bounding: [400, 100, 140, 126] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [400, 100, 140, 126] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [{ id: 3, name: 'seed' }],
|
||||
widgets: [{ id: asNodeId(3), name: 'seed' }],
|
||||
nodes: [
|
||||
{
|
||||
id: 3,
|
||||
id: asNodeId(3),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -79,7 +80,7 @@ export const nodeIdSpaceExhausted = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
id: asNodeId(8),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -88,7 +89,7 @@ export const nodeIdSpaceExhausted = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 37,
|
||||
id: asNodeId(37),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -100,9 +101,9 @@ export const nodeIdSpaceExhausted = {
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
origin_id: 3,
|
||||
origin_id: asNodeId(3),
|
||||
origin_slot: 0,
|
||||
target_id: 8,
|
||||
target_id: asNodeId(8),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
}
|
||||
@@ -121,14 +122,14 @@ export const nodeIdSpaceExhausted = {
|
||||
},
|
||||
name: 'SubgraphB',
|
||||
config: {},
|
||||
inputNode: { id: -10, bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: -20, bounding: [400, 100, 140, 126] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [400, 100, 140, 126] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [{ id: 8, name: 'prompt' }],
|
||||
widgets: [{ id: asNodeId(8), name: 'prompt' }],
|
||||
nodes: [
|
||||
{
|
||||
id: 3,
|
||||
id: asNodeId(3),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -137,7 +138,7 @@ export const nodeIdSpaceExhausted = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
id: asNodeId(8),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -146,7 +147,7 @@ export const nodeIdSpaceExhausted = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 37,
|
||||
id: asNodeId(37),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -158,9 +159,9 @@ export const nodeIdSpaceExhausted = {
|
||||
links: [
|
||||
{
|
||||
id: 2,
|
||||
origin_id: 3,
|
||||
origin_id: asNodeId(3),
|
||||
origin_slot: 0,
|
||||
target_id: 37,
|
||||
target_id: asNodeId(37),
|
||||
target_slot: 0,
|
||||
type: 'string'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SerialisableGraph } from '@/lib/litegraph/src/types/serialisation'
|
||||
import { asNodeId } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
/**
|
||||
* Workflow with two subgraph definitions whose internal nodes already
|
||||
@@ -20,7 +21,7 @@ export const uniqueSubgraphNodeIds = {
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 102,
|
||||
id: asNodeId(102),
|
||||
type: '11111111-1111-4111-8111-111111111111',
|
||||
pos: [0, 0],
|
||||
size: [200, 100],
|
||||
@@ -30,7 +31,7 @@ export const uniqueSubgraphNodeIds = {
|
||||
properties: { proxyWidgets: [['10', 'seed']] }
|
||||
},
|
||||
{
|
||||
id: 103,
|
||||
id: asNodeId(103),
|
||||
type: '22222222-2222-4222-8222-222222222222',
|
||||
pos: [300, 0],
|
||||
size: [200, 100],
|
||||
@@ -54,14 +55,14 @@ export const uniqueSubgraphNodeIds = {
|
||||
},
|
||||
name: 'SubgraphA',
|
||||
config: {},
|
||||
inputNode: { id: -10, bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: -20, bounding: [400, 100, 140, 126] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [400, 100, 140, 126] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [{ id: 10, name: 'seed' }],
|
||||
widgets: [{ id: asNodeId(10), name: 'seed' }],
|
||||
nodes: [
|
||||
{
|
||||
id: 10,
|
||||
id: asNodeId(10),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -70,7 +71,7 @@ export const uniqueSubgraphNodeIds = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
id: asNodeId(11),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -79,7 +80,7 @@ export const uniqueSubgraphNodeIds = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
id: asNodeId(12),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -91,9 +92,9 @@ export const uniqueSubgraphNodeIds = {
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
origin_id: 10,
|
||||
origin_id: asNodeId(10),
|
||||
origin_slot: 0,
|
||||
target_id: 11,
|
||||
target_id: asNodeId(11),
|
||||
target_slot: 0,
|
||||
type: 'number'
|
||||
}
|
||||
@@ -112,14 +113,14 @@ export const uniqueSubgraphNodeIds = {
|
||||
},
|
||||
name: 'SubgraphB',
|
||||
config: {},
|
||||
inputNode: { id: -10, bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: -20, bounding: [400, 100, 140, 126] },
|
||||
inputNode: { id: asNodeId(-10), bounding: [10, 100, 150, 126] },
|
||||
outputNode: { id: asNodeId(-20), bounding: [400, 100, 140, 126] },
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [{ id: 21, name: 'prompt' }],
|
||||
widgets: [{ id: asNodeId(21), name: 'prompt' }],
|
||||
nodes: [
|
||||
{
|
||||
id: 20,
|
||||
id: asNodeId(20),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -128,7 +129,7 @@ export const uniqueSubgraphNodeIds = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
id: asNodeId(21),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -137,7 +138,7 @@ export const uniqueSubgraphNodeIds = {
|
||||
mode: 0
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
id: asNodeId(22),
|
||||
type: 'dummy',
|
||||
pos: [0, 0],
|
||||
size: [100, 50],
|
||||
@@ -149,9 +150,9 @@ export const uniqueSubgraphNodeIds = {
|
||||
links: [
|
||||
{
|
||||
id: 2,
|
||||
origin_id: 20,
|
||||
origin_id: asNodeId(20),
|
||||
origin_slot: 0,
|
||||
target_id: 22,
|
||||
target_id: asNodeId(22),
|
||||
target_slot: 0,
|
||||
type: 'string'
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
exports[`LLink > matches previous snapshot > Basic 1`] = `
|
||||
[
|
||||
1,
|
||||
4,
|
||||
"4",
|
||||
2,
|
||||
5,
|
||||
"5",
|
||||
3,
|
||||
"float",
|
||||
]
|
||||
@@ -14,9 +14,9 @@ exports[`LLink > matches previous snapshot > Basic 1`] = `
|
||||
exports[`LLink > serializes to the previous snapshot > Basic 1`] = `
|
||||
[
|
||||
1,
|
||||
4,
|
||||
"4",
|
||||
2,
|
||||
5,
|
||||
"5",
|
||||
3,
|
||||
"float",
|
||||
]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import {
|
||||
UNASSIGNED_NODE_ID,
|
||||
SUBGRAPH_INPUT_NODE_ID,
|
||||
SUBGRAPH_OUTPUT_NODE_ID,
|
||||
isFloatingNodeId
|
||||
} from '@/types/nodeId'
|
||||
import type { LLink } from '@/lib/litegraph/src/LLink'
|
||||
import type { Reroute } from '@/lib/litegraph/src/Reroute'
|
||||
import {
|
||||
SUBGRAPH_INPUT_ID,
|
||||
SUBGRAPH_OUTPUT_ID
|
||||
} from '@/lib/litegraph/src/constants'
|
||||
import type { CustomEventTarget } from '@/lib/litegraph/src/infrastructure/CustomEventTarget'
|
||||
import type { LinkConnectorEventMap } from '@/lib/litegraph/src/infrastructure/LinkConnectorEventMap'
|
||||
import type {
|
||||
@@ -37,13 +39,13 @@ export class FloatingRenderLink implements RenderLink {
|
||||
readonly fromDirection: LinkDirection
|
||||
readonly fromSlotIndex: SlotIndex
|
||||
|
||||
readonly outputNodeId: NodeId = -1
|
||||
readonly outputNodeId: NodeId = UNASSIGNED_NODE_ID
|
||||
readonly outputNode?: LGraphNode
|
||||
readonly outputSlot?: INodeOutputSlot
|
||||
readonly outputIndex: number = -1
|
||||
readonly outputPos?: Point
|
||||
|
||||
readonly inputNodeId: NodeId = -1
|
||||
readonly inputNodeId: NodeId = UNASSIGNED_NODE_ID
|
||||
readonly inputNode?: LGraphNode
|
||||
readonly inputSlot?: INodeInputSlot
|
||||
readonly inputIndex: number = -1
|
||||
@@ -63,7 +65,7 @@ export class FloatingRenderLink implements RenderLink {
|
||||
target_slot: inputIndex
|
||||
} = link
|
||||
|
||||
if (outputNodeId !== -1) {
|
||||
if (!isFloatingNodeId(outputNodeId)) {
|
||||
// Output connected
|
||||
const outputNode = network.getNodeById(outputNodeId) ?? undefined
|
||||
if (!outputNode)
|
||||
@@ -175,7 +177,7 @@ export class FloatingRenderLink implements RenderLink {
|
||||
_events?: CustomEventTarget<LinkConnectorEventMap>
|
||||
): void {
|
||||
const floatingLink = this.link
|
||||
floatingLink.origin_id = SUBGRAPH_INPUT_ID
|
||||
floatingLink.origin_id = SUBGRAPH_INPUT_NODE_ID
|
||||
floatingLink.origin_slot = input.parent.slots.indexOf(input)
|
||||
|
||||
this.fromSlot._floatingLinks?.delete(floatingLink)
|
||||
@@ -188,7 +190,7 @@ export class FloatingRenderLink implements RenderLink {
|
||||
_events?: CustomEventTarget<LinkConnectorEventMap>
|
||||
): void {
|
||||
const floatingLink = this.link
|
||||
floatingLink.origin_id = SUBGRAPH_OUTPUT_ID
|
||||
floatingLink.origin_id = SUBGRAPH_OUTPUT_NODE_ID
|
||||
floatingLink.origin_slot = output.parent.slots.indexOf(output)
|
||||
|
||||
this.fromSlot._floatingLinks?.delete(floatingLink)
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
ISlotType
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
asNodeId,
|
||||
LGraph,
|
||||
LGraphNode,
|
||||
LLink,
|
||||
@@ -74,7 +75,7 @@ const test = baseTest.extend<TestContext>({
|
||||
createTestNode: async ({ network }, use) => {
|
||||
await use((id: number): LGraphNode => {
|
||||
const node = new LGraphNode('test')
|
||||
node.id = id
|
||||
node.id = asNodeId(id)
|
||||
network.add(node)
|
||||
return node
|
||||
})
|
||||
@@ -87,7 +88,14 @@ const test = baseTest.extend<TestContext>({
|
||||
targetId: number,
|
||||
slotType: ISlotType = 'number'
|
||||
): LLink => {
|
||||
const link = new LLink(id, slotType, sourceId, 0, targetId, 0)
|
||||
const link = new LLink(
|
||||
id,
|
||||
slotType,
|
||||
asNodeId(sourceId),
|
||||
0,
|
||||
asNodeId(targetId),
|
||||
0
|
||||
)
|
||||
network.links.set(link.id, link)
|
||||
return link
|
||||
}
|
||||
@@ -121,7 +129,7 @@ describe('LinkConnector', () => {
|
||||
sourceNode.addOutput('out', slotType)
|
||||
targetNode.addInput('in', slotType)
|
||||
|
||||
const link = new LLink(1, slotType, 1, 0, 2, 0)
|
||||
const link = new LLink(1, slotType, asNodeId(1), 0, asNodeId(2), 0)
|
||||
network.links.set(link.id, link)
|
||||
targetNode.inputs[0].link = link.id
|
||||
|
||||
@@ -158,7 +166,7 @@ describe('LinkConnector', () => {
|
||||
sourceNode.addOutput('out', slotType)
|
||||
targetNode.addInput('in', slotType)
|
||||
|
||||
const link = new LLink(1, slotType, 1, 0, 2, 0)
|
||||
const link = new LLink(1, slotType, asNodeId(1), 0, asNodeId(2), 0)
|
||||
network.links.set(link.id, link)
|
||||
sourceNode.outputs[0].links = [link.id]
|
||||
|
||||
@@ -261,7 +269,7 @@ describe('LinkConnector', () => {
|
||||
connector.state.multi = true
|
||||
connector.state.draggingExistingLinks = true
|
||||
|
||||
const link = new LLink(1, 'number', 1, 0, 2, 0)
|
||||
const link = new LLink(1, 'number', asNodeId(1), 0, asNodeId(2), 0)
|
||||
link._dragging = true
|
||||
connector.inputLinks.push(link)
|
||||
|
||||
@@ -302,7 +310,7 @@ describe('LinkConnector', () => {
|
||||
fromPos: [0, 0],
|
||||
fromDirection: LinkDirection.RIGHT,
|
||||
toType: 'input',
|
||||
link: new LLink(1, 'number', 1, 0, 2, 0)
|
||||
link: new LLink(1, 'number', asNodeId(1), 0, asNodeId(2), 0)
|
||||
} as MovingInputLink
|
||||
|
||||
connector.events.dispatch('input-moved', mockRenderLink)
|
||||
@@ -319,7 +327,7 @@ describe('LinkConnector', () => {
|
||||
connector.state.connectingTo = 'input'
|
||||
connector.state.multi = true
|
||||
|
||||
const link = new LLink(1, 'number', 1, 0, 2, 0)
|
||||
const link = new LLink(1, 'number', asNodeId(1), 0, asNodeId(2), 0)
|
||||
connector.inputLinks.push(link)
|
||||
|
||||
const exported = connector.export(network)
|
||||
|
||||
@@ -8,10 +8,16 @@ import type {
|
||||
CanvasPointerEvent,
|
||||
RerouteId
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphNode, LLink, LinkConnector } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
asNodeId,
|
||||
LGraphNode,
|
||||
LLink,
|
||||
LinkConnector
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { test as baseTest } from '../__fixtures__/testExtensions'
|
||||
import type { ConnectingLink } from '@/lib/litegraph/src/interfaces'
|
||||
import { UNASSIGNED_NODE_ID, isFloatingNodeId } from '@/types/nodeId'
|
||||
import {
|
||||
createMockCanvasPointerEvent,
|
||||
createMockCanvasRenderingContext2D
|
||||
@@ -59,7 +65,7 @@ const test = baseTest.extend<TestContext>({
|
||||
createTestNode: async ({ graph }, use) => {
|
||||
await use((id): LGraphNode => {
|
||||
const node = new LGraphNode('test')
|
||||
node.id = id
|
||||
node.id = asNodeId(id)
|
||||
graph.add(node)
|
||||
return node
|
||||
})
|
||||
@@ -112,12 +118,12 @@ const test = baseTest.extend<TestContext>({
|
||||
const link = graph.floatingLinks.get(linkId)
|
||||
expect(link).toBeDefined()
|
||||
|
||||
if (link!.target_id === -1) {
|
||||
expect(link!.origin_id).not.toBe(-1)
|
||||
if (isFloatingNodeId(link!.target_id)) {
|
||||
expect(link!.origin_id).not.toBe(UNASSIGNED_NODE_ID)
|
||||
expect(link!.origin_slot).not.toBe(-1)
|
||||
expect(link!.target_slot).toBe(-1)
|
||||
} else {
|
||||
expect(link!.origin_id).toBe(-1)
|
||||
expect(link!.origin_id).toBe(UNASSIGNED_NODE_ID)
|
||||
expect(link!.origin_slot).toBe(-1)
|
||||
expect(link!.target_slot).not.toBe(-1)
|
||||
}
|
||||
@@ -150,8 +156,8 @@ const test = baseTest.extend<TestContext>({
|
||||
}
|
||||
|
||||
for (const link of graph.floatingLinks.values()) {
|
||||
if (link.target_id === -1) {
|
||||
expect(link.origin_id).not.toBe(-1)
|
||||
if (isFloatingNodeId(link.target_id)) {
|
||||
expect(link.origin_id).not.toBe(UNASSIGNED_NODE_ID)
|
||||
expect(link.origin_slot).not.toBe(-1)
|
||||
expect(link.target_slot).toBe(-1)
|
||||
const outputFloatingLinks = graph.getNodeById(link.origin_id)
|
||||
@@ -159,7 +165,7 @@ const test = baseTest.extend<TestContext>({
|
||||
expect(outputFloatingLinks).toBeDefined()
|
||||
expect(outputFloatingLinks).toContain(link)
|
||||
} else {
|
||||
expect(link.origin_id).toBe(-1)
|
||||
expect(link.origin_id).toBe(UNASSIGNED_NODE_ID)
|
||||
expect(link.origin_slot).toBe(-1)
|
||||
expect(link.target_slot).not.toBe(-1)
|
||||
const inputFloatingLinks = graph.getNodeById(link.target_id)?.inputs[
|
||||
|
||||
@@ -3,10 +3,7 @@ import { remove } from 'es-toolkit'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { LLink } from '@/lib/litegraph/src/LLink'
|
||||
import type { Reroute } from '@/lib/litegraph/src/Reroute'
|
||||
import {
|
||||
SUBGRAPH_INPUT_ID,
|
||||
SUBGRAPH_OUTPUT_ID
|
||||
} from '@/lib/litegraph/src/constants'
|
||||
import { isSubgraphInputNodeId, isSubgraphOutputNodeId } from '@/types/nodeId'
|
||||
import { CustomEventTarget } from '@/lib/litegraph/src/infrastructure/CustomEventTarget'
|
||||
import type { LinkConnectorEventMap } from '@/lib/litegraph/src/infrastructure/LinkConnectorEventMap'
|
||||
import type {
|
||||
@@ -183,7 +180,7 @@ export class LinkConnector {
|
||||
if (!link) return
|
||||
|
||||
// Special handling for links from subgraph input nodes
|
||||
if (link.origin_id === SUBGRAPH_INPUT_ID) {
|
||||
if (isSubgraphInputNodeId(link.origin_id)) {
|
||||
// For subgraph input links, we need to handle them differently
|
||||
// since they don't have a regular output node
|
||||
const subgraphInput = network.inputNode?.slots[link.origin_slot]
|
||||
@@ -325,7 +322,7 @@ export class LinkConnector {
|
||||
this.outputLinks.push(link)
|
||||
|
||||
try {
|
||||
if (link.target_id === SUBGRAPH_OUTPUT_ID) {
|
||||
if (isSubgraphOutputNodeId(link.target_id)) {
|
||||
if (!(network instanceof Subgraph)) {
|
||||
console.warn(
|
||||
'Subgraph output link found in non-subgraph network.'
|
||||
@@ -482,7 +479,7 @@ export class LinkConnector {
|
||||
return
|
||||
}
|
||||
|
||||
if (link.origin_id === SUBGRAPH_INPUT_ID) {
|
||||
if (isSubgraphInputNodeId(link.origin_id)) {
|
||||
if (!(network instanceof Subgraph)) {
|
||||
console.warn('Subgraph input link found in non-subgraph network.')
|
||||
return
|
||||
@@ -546,7 +543,7 @@ export class LinkConnector {
|
||||
return
|
||||
}
|
||||
|
||||
if (link.target_id === SUBGRAPH_OUTPUT_ID) {
|
||||
if (isSubgraphOutputNodeId(link.target_id)) {
|
||||
if (!(network instanceof Subgraph)) {
|
||||
console.warn('Subgraph output link found in non-subgraph network.')
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
asNodeId,
|
||||
LinkConnector,
|
||||
MovingOutputLink,
|
||||
ToOutputRenderLink,
|
||||
@@ -197,7 +198,7 @@ describe('LinkConnector SubgraphInput connection validation', () => {
|
||||
// Create a minimal valid setup
|
||||
const subgraph = createTestSubgraph()
|
||||
const node = new LGraphNode('TestNode')
|
||||
node.id = 1
|
||||
node.id = asNodeId(1)
|
||||
node.addInput('test_in', 'number')
|
||||
subgraph.add(node)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import type { NodeLike } from '@/lib/litegraph/src/types/NodeLike'
|
||||
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
|
||||
import type { SubgraphIO } from '@/lib/litegraph/src/types/serialisation'
|
||||
|
||||
import { isSubgraphOutputNodeId } from '@/types/nodeId'
|
||||
|
||||
import type { RenderLink } from './RenderLink'
|
||||
|
||||
/** Connecting TO an output slot. */
|
||||
@@ -55,7 +57,7 @@ export class ToOutputFromIoNodeLink implements RenderLink {
|
||||
}
|
||||
|
||||
canConnectToReroute(reroute: Reroute): boolean {
|
||||
if (reroute.origin_id === this.node.id) return false
|
||||
if (isSubgraphOutputNodeId(reroute.origin_id)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { TWidgetValue } from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
import type { ContextMenu } from './ContextMenu'
|
||||
import type { LGraphNode, NodeId, NodeProperty } from './LGraphNode'
|
||||
import type { NodeIdInput } from '@/types/nodeId'
|
||||
import type { LLink, LinkId } from './LLink'
|
||||
import type { Reroute, RerouteId } from './Reroute'
|
||||
import type { SubgraphInput } from './subgraph/SubgraphInput'
|
||||
@@ -162,7 +163,7 @@ export interface ReadonlyLinkNetwork {
|
||||
readonly links: ReadonlyMap<LinkId, LLink>
|
||||
readonly reroutes: ReadonlyMap<RerouteId, Reroute>
|
||||
readonly floatingLinks: ReadonlyMap<LinkId, LLink>
|
||||
getNodeById(id: NodeId | null | undefined): LGraphNode | null
|
||||
getNodeById(id: NodeIdInput | null | undefined): LGraphNode | null
|
||||
getLink(id: null | undefined): undefined
|
||||
getLink(id: LinkId | null | undefined): LLink | undefined
|
||||
getReroute(parentId: null | undefined): undefined
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { LGraphNode, NodeId } from './LGraphNode'
|
||||
import type { LLink, LinkId } from './LLink'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
|
||||
/** Generates a unique string key for a link's connection tuple. */
|
||||
function linkTupleKey(link: LLink): string {
|
||||
@@ -51,7 +52,7 @@ export function purgeOrphanedLinks(
|
||||
const link = links.get(id)
|
||||
if (!link) continue
|
||||
|
||||
const originNode = getNodeById(link.origin_id)
|
||||
const originNode = getNodeById(asNodeId(link.origin_id))
|
||||
const output = originNode?.outputs?.[link.origin_slot]
|
||||
if (output?.links) {
|
||||
for (let i = output.links.length - 1; i >= 0; i--) {
|
||||
|
||||
@@ -83,7 +83,7 @@ export { LinkConnector } from './canvas/LinkConnector'
|
||||
export { isOverNodeInput, isOverNodeOutput } from './canvas/measureSlots'
|
||||
export { CanvasPointer } from './CanvasPointer'
|
||||
export * as Constants from './constants'
|
||||
export { SUBGRAPH_INPUT_ID } from './constants'
|
||||
export { SUBGRAPH_INPUT_ID, SUBGRAPH_OUTPUT_ID } from './constants'
|
||||
export { ContextMenu } from './ContextMenu'
|
||||
|
||||
export { DragAndScale } from './DragAndScale'
|
||||
@@ -118,6 +118,19 @@ export { BadgePosition, LGraphBadge } from './LGraphBadge'
|
||||
export { LGraphCanvas } from './LGraphCanvas'
|
||||
export { LGraphGroup, type GroupId } from './LGraphGroup'
|
||||
export { LGraphNode, type NodeId } from './LGraphNode'
|
||||
export {
|
||||
asNodeId,
|
||||
isFloatingNodeId,
|
||||
isNumericNodeId,
|
||||
isSubgraphInputNodeId,
|
||||
isSubgraphOutputNodeId,
|
||||
isUnassignedNodeId,
|
||||
nodeIdToNumber,
|
||||
SUBGRAPH_INPUT_NODE_ID,
|
||||
SUBGRAPH_OUTPUT_NODE_ID,
|
||||
UNASSIGNED_NODE_ID
|
||||
} from '@/types/nodeId'
|
||||
export type { NodeIdInput } from '@/types/nodeId'
|
||||
export { LLink } from './LLink'
|
||||
export { createBounds } from './measure'
|
||||
export { Reroute, type RerouteId } from './Reroute'
|
||||
|
||||
@@ -3,6 +3,7 @@ import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
asNodeId,
|
||||
LGraph,
|
||||
LGraphNode,
|
||||
LGraphEventMode,
|
||||
@@ -41,9 +42,9 @@ describe('ExecutableNodeDTO Creation', () => {
|
||||
it('should create DTO with subgraph path', () => {
|
||||
const graph = new LGraph()
|
||||
const node = new LGraphNode('Inner Node')
|
||||
node.id = 42
|
||||
node.id = asNodeId(42)
|
||||
graph.add(node)
|
||||
const subgraphPath = ['10', '20'] as const
|
||||
const subgraphPath = [asNodeId('10'), asNodeId('20')] as const
|
||||
|
||||
const dto = new ExecutableNodeDTO(node, subgraphPath, new Map(), undefined)
|
||||
|
||||
@@ -115,7 +116,7 @@ describe('ExecutableNodeDTO Path-Based IDs', () => {
|
||||
it('should generate simple ID for root node', () => {
|
||||
const graph = new LGraph()
|
||||
const node = new LGraphNode('Root Node')
|
||||
node.id = 5
|
||||
node.id = asNodeId(5)
|
||||
graph.add(node)
|
||||
|
||||
const dto = new ExecutableNodeDTO(node, [], new Map(), undefined)
|
||||
@@ -126,9 +127,9 @@ describe('ExecutableNodeDTO Path-Based IDs', () => {
|
||||
it('should generate path-based ID for nested node', () => {
|
||||
const graph = new LGraph()
|
||||
const node = new LGraphNode('Nested Node')
|
||||
node.id = 3
|
||||
node.id = asNodeId(3)
|
||||
graph.add(node)
|
||||
const path = ['1', '2'] as const
|
||||
const path = [asNodeId('1'), asNodeId('2')] as const
|
||||
|
||||
const dto = new ExecutableNodeDTO(node, path, new Map(), undefined)
|
||||
|
||||
@@ -138,9 +139,15 @@ describe('ExecutableNodeDTO Path-Based IDs', () => {
|
||||
it('should handle deep nesting paths', () => {
|
||||
const graph = new LGraph()
|
||||
const node = new LGraphNode('Deep Node')
|
||||
node.id = 99
|
||||
node.id = asNodeId(99)
|
||||
graph.add(node)
|
||||
const path = ['1', '2', '3', '4', '5'] as const
|
||||
const path = [
|
||||
asNodeId('1'),
|
||||
asNodeId('2'),
|
||||
asNodeId('3'),
|
||||
asNodeId('4'),
|
||||
asNodeId('5')
|
||||
] as const
|
||||
|
||||
const dto = new ExecutableNodeDTO(node, path, new Map(), undefined)
|
||||
|
||||
@@ -150,15 +157,25 @@ describe('ExecutableNodeDTO Path-Based IDs', () => {
|
||||
it('should handle string and number IDs consistently', () => {
|
||||
const graph = new LGraph()
|
||||
const node1 = new LGraphNode('Node 1')
|
||||
node1.id = 10
|
||||
node1.id = asNodeId(10)
|
||||
graph.add(node1)
|
||||
|
||||
const node2 = new LGraphNode('Node 2')
|
||||
node2.id = 20
|
||||
node2.id = asNodeId(20)
|
||||
graph.add(node2)
|
||||
|
||||
const dto1 = new ExecutableNodeDTO(node1, ['5'], new Map(), undefined)
|
||||
const dto2 = new ExecutableNodeDTO(node2, ['5'], new Map(), undefined)
|
||||
const dto1 = new ExecutableNodeDTO(
|
||||
node1,
|
||||
[asNodeId('5')],
|
||||
new Map(),
|
||||
undefined
|
||||
)
|
||||
const dto2 = new ExecutableNodeDTO(
|
||||
node2,
|
||||
[asNodeId('5')],
|
||||
new Map(),
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(dto1.id).toBe('5:10')
|
||||
expect(dto2.id).toBe('5:20')
|
||||
@@ -199,7 +216,12 @@ describe('ExecutableNodeDTO Input Resolution', () => {
|
||||
|
||||
// Get the inner node and create DTO
|
||||
const innerNode = subgraph.nodes[0]
|
||||
const dto = new ExecutableNodeDTO(innerNode, ['1'], new Map(), subgraphNode)
|
||||
const dto = new ExecutableNodeDTO(
|
||||
innerNode,
|
||||
[asNodeId('1')],
|
||||
new Map(),
|
||||
subgraphNode
|
||||
)
|
||||
|
||||
// Should return undefined for unconnected input
|
||||
const resolved = dto.resolveInput(0)
|
||||
@@ -234,7 +256,12 @@ describe('ExecutableNodeDTO Output Resolution', () => {
|
||||
|
||||
// Get the inner node and create DTO
|
||||
const innerNode = subgraph.nodes[0]
|
||||
const dto = new ExecutableNodeDTO(innerNode, ['1'], new Map(), subgraphNode)
|
||||
const dto = new ExecutableNodeDTO(
|
||||
innerNode,
|
||||
[asNodeId('1')],
|
||||
new Map(),
|
||||
subgraphNode
|
||||
)
|
||||
|
||||
const resolved = dto.resolveOutput(0, 'string', new Set())
|
||||
|
||||
@@ -487,12 +514,17 @@ describe('ExecutableNodeDTO Properties', () => {
|
||||
it('should provide access to basic properties', () => {
|
||||
const graph = new LGraph()
|
||||
const node = new LGraphNode('Test Node')
|
||||
node.id = 42
|
||||
node.id = asNodeId(42)
|
||||
node.addInput('input', 'number')
|
||||
node.addOutput('output', 'string')
|
||||
graph.add(node)
|
||||
|
||||
const dto = new ExecutableNodeDTO(node, ['1', '2'], new Map(), undefined)
|
||||
const dto = new ExecutableNodeDTO(
|
||||
node,
|
||||
[asNodeId('1'), asNodeId('2')],
|
||||
new Map(),
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(dto.id).toBe('1:2:42')
|
||||
expect(dto.type).toBe(node.type)
|
||||
@@ -528,7 +560,12 @@ describe('ExecutableNodeDTO Memory Efficiency', () => {
|
||||
node.addOutput('out2', 'string')
|
||||
graph.add(node)
|
||||
|
||||
const dto = new ExecutableNodeDTO(node, ['1'], new Map(), undefined)
|
||||
const dto = new ExecutableNodeDTO(
|
||||
node,
|
||||
[asNodeId('1')],
|
||||
new Map(),
|
||||
undefined
|
||||
)
|
||||
|
||||
// DTO should be lightweight - only essential properties
|
||||
expect(dto.node).toBe(node) // Reference, not copy
|
||||
@@ -549,9 +586,14 @@ describe('ExecutableNodeDTO Memory Efficiency', () => {
|
||||
// Create DTOs
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const node = new LGraphNode(`Node ${i}`)
|
||||
node.id = i
|
||||
node.id = asNodeId(i)
|
||||
graph.add(node)
|
||||
const dto = new ExecutableNodeDTO(node, ['parent'], new Map(), undefined)
|
||||
const dto = new ExecutableNodeDTO(
|
||||
node,
|
||||
[asNodeId('parent')],
|
||||
new Map(),
|
||||
undefined
|
||||
)
|
||||
nodes.push(dto)
|
||||
}
|
||||
|
||||
@@ -570,7 +612,12 @@ describe('ExecutableNodeDTO Memory Efficiency', () => {
|
||||
const subgraphNode = createTestSubgraphNode(subgraph)
|
||||
const innerNode = subgraph.nodes[0]
|
||||
|
||||
const dto = new ExecutableNodeDTO(innerNode, ['1'], new Map(), subgraphNode)
|
||||
const dto = new ExecutableNodeDTO(
|
||||
innerNode,
|
||||
[asNodeId('1')],
|
||||
new Map(),
|
||||
subgraphNode
|
||||
)
|
||||
|
||||
// Should hold necessary references
|
||||
expect(dto.node).toBe(innerNode)
|
||||
@@ -619,20 +666,20 @@ describe('ExecutableNodeDTO Integration', () => {
|
||||
it('should preserve original node properties through DTO', () => {
|
||||
const graph = new LGraph()
|
||||
const originalNode = new LGraphNode('Original')
|
||||
originalNode.id = 123
|
||||
originalNode.id = asNodeId(123)
|
||||
originalNode.addInput('test', 'number')
|
||||
originalNode.properties = { value: 42 }
|
||||
graph.add(originalNode)
|
||||
|
||||
const dto = new ExecutableNodeDTO(
|
||||
originalNode,
|
||||
['parent'],
|
||||
[asNodeId('parent')],
|
||||
new Map(),
|
||||
undefined
|
||||
)
|
||||
|
||||
// DTO should provide access to original node properties
|
||||
expect(dto.node.id).toBe(123)
|
||||
expect(dto.node.id).toBe(asNodeId(123))
|
||||
expect(dto.node.inputs).toHaveLength(1)
|
||||
expect(dto.node.properties.value).toBe(42)
|
||||
|
||||
@@ -642,21 +689,21 @@ describe('ExecutableNodeDTO Integration', () => {
|
||||
|
||||
it('should handle execution context correctly', () => {
|
||||
const subgraph = createTestSubgraph({ nodeCount: 1 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: 99 })
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, { id: asNodeId(99) })
|
||||
const innerNode = subgraph.nodes[0]
|
||||
innerNode.id = 55
|
||||
innerNode.id = asNodeId(55)
|
||||
|
||||
const dto = new ExecutableNodeDTO(
|
||||
innerNode,
|
||||
['99'],
|
||||
[asNodeId('99')],
|
||||
new Map(),
|
||||
subgraphNode
|
||||
)
|
||||
|
||||
// DTO provides execution context
|
||||
expect(dto.id).toBe('99:55') // Path-based execution ID
|
||||
expect(dto.node.id).toBe(55) // Original node ID preserved
|
||||
expect(dto.subgraphNode?.id).toBe(99) // Subgraph context
|
||||
expect(dto.node.id).toBe(asNodeId(55)) // Original node ID preserved
|
||||
expect(dto.subgraphNode?.id).toBe(asNodeId(99)) // Subgraph context
|
||||
})
|
||||
})
|
||||
|
||||
@@ -669,11 +716,16 @@ describe('ExecutableNodeDTO Scale Testing', () => {
|
||||
// Create DTOs to test performance
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const node = new LGraphNode(`Node ${i}`)
|
||||
node.id = i
|
||||
node.id = asNodeId(i)
|
||||
node.addInput('in', 'number')
|
||||
graph.add(node)
|
||||
|
||||
const dto = new ExecutableNodeDTO(node, ['parent'], new Map(), undefined)
|
||||
const dto = new ExecutableNodeDTO(
|
||||
node,
|
||||
[asNodeId('parent')],
|
||||
new Map(),
|
||||
undefined
|
||||
)
|
||||
dtos.push(dto)
|
||||
}
|
||||
|
||||
@@ -692,7 +744,7 @@ describe('ExecutableNodeDTO Scale Testing', () => {
|
||||
it('should handle complex path generation correctly', () => {
|
||||
const graph = new LGraph()
|
||||
const node = new LGraphNode('Deep Node')
|
||||
node.id = 999
|
||||
node.id = asNodeId(999)
|
||||
graph.add(node)
|
||||
|
||||
// Test deterministic path generation behavior
|
||||
@@ -705,7 +757,7 @@ describe('ExecutableNodeDTO Scale Testing', () => {
|
||||
|
||||
for (const testCase of testCases) {
|
||||
const path = Array.from({ length: testCase.depth }, (_, i) =>
|
||||
(i + 1).toString()
|
||||
asNodeId(i + 1)
|
||||
)
|
||||
const dto = new ExecutableNodeDTO(node, path, new Map(), undefined)
|
||||
expect(dto.id).toBe(testCase.expectedId)
|
||||
|
||||
@@ -11,7 +11,12 @@ import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import type { LGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { createUuidv4, Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
createUuidv4,
|
||||
Subgraph,
|
||||
SUBGRAPH_INPUT_NODE_ID,
|
||||
SUBGRAPH_OUTPUT_NODE_ID
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { subgraphTest } from './__fixtures__/subgraphFixtures'
|
||||
import {
|
||||
assertSubgraphStructure,
|
||||
@@ -41,8 +46,8 @@ describe('Subgraph Construction', () => {
|
||||
)
|
||||
expect(subgraph.inputNode).toBeDefined()
|
||||
expect(subgraph.outputNode).toBeDefined()
|
||||
expect(subgraph.inputNode.id).toBe(-10)
|
||||
expect(subgraph.outputNode.id).toBe(-20)
|
||||
expect(subgraph.inputNode.id).toBe(SUBGRAPH_INPUT_NODE_ID)
|
||||
expect(subgraph.outputNode.id).toBe(SUBGRAPH_OUTPUT_NODE_ID)
|
||||
})
|
||||
|
||||
it('should require a root graph', () => {
|
||||
|
||||
@@ -8,7 +8,12 @@ import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
|
||||
import { LGraph, LGraphNode, Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
asNodeId,
|
||||
LGraph,
|
||||
LGraphNode,
|
||||
Subgraph
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import {
|
||||
createNestedSubgraphs,
|
||||
@@ -28,8 +33,8 @@ describe('SubgraphEdgeCases - Recursion Detection', () => {
|
||||
const sub2 = createTestSubgraph({ name: 'Sub2' })
|
||||
|
||||
// Create circular reference
|
||||
const node1 = createTestSubgraphNode(sub1, { id: 1 })
|
||||
const node2 = createTestSubgraphNode(sub2, { id: 2 })
|
||||
const node1 = createTestSubgraphNode(sub1, { id: asNodeId(1) })
|
||||
const node2 = createTestSubgraphNode(sub2, { id: asNodeId(2) })
|
||||
|
||||
// Current limitation: adding a circular reference overflows recursion depth.
|
||||
sub1.add(node2)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { ToInputFromIoNodeLink } from '@/lib/litegraph/src/canvas/ToInputFromIoNodeLink'
|
||||
import { LinkDirection } from '@/lib/litegraph/src//types/globalEnums'
|
||||
import { isSubgraphInputNodeId } from '@/types/nodeId'
|
||||
|
||||
import { subgraphTest } from './__fixtures__/subgraphFixtures'
|
||||
import {
|
||||
@@ -451,7 +452,7 @@ describe('SubgraphIO - Empty Slot Connection', () => {
|
||||
expect(link).toBeDefined()
|
||||
expect(link.target_id).toBe(internalNode.id)
|
||||
expect(link.target_slot).toBe(0)
|
||||
expect(link.origin_id).toBe(subgraph.inputNode.id)
|
||||
expect(isSubgraphInputNodeId(link.origin_id)).toBe(true)
|
||||
expect(link.origin_slot).toBe(1) // Should be the second slot
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { NodeId } from '@/types/nodeId'
|
||||
import type { LinkConnector } from '@/lib/litegraph/src/canvas/LinkConnector'
|
||||
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
|
||||
import type {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { CanvasPointer } from '@/lib/litegraph/src/CanvasPointer'
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { LLink } from '@/lib/litegraph/src/LLink'
|
||||
import type { RerouteId } from '@/lib/litegraph/src/Reroute'
|
||||
import type { LinkConnector } from '@/lib/litegraph/src/canvas/LinkConnector'
|
||||
import { SUBGRAPH_INPUT_ID } from '@/lib/litegraph/src/constants'
|
||||
import { SUBGRAPH_INPUT_NODE_ID } from '@/types/nodeId'
|
||||
import type {
|
||||
DefaultConnectionColors,
|
||||
INodeInputSlot,
|
||||
@@ -26,7 +26,7 @@ export class SubgraphInputNode
|
||||
extends SubgraphIONodeBase<SubgraphInput>
|
||||
implements Positionable
|
||||
{
|
||||
readonly id: NodeId = SUBGRAPH_INPUT_ID
|
||||
readonly id = SUBGRAPH_INPUT_NODE_ID
|
||||
|
||||
readonly emptySlot: EmptySubgraphInput = new EmptySubgraphInput(this)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
asNodeId,
|
||||
BaseWidget,
|
||||
LGraph,
|
||||
LGraphNode,
|
||||
@@ -55,12 +56,12 @@ describe('SubgraphNode Construction', () => {
|
||||
})
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph, {
|
||||
id: 42,
|
||||
id: asNodeId(42),
|
||||
pos: [300, 150],
|
||||
size: [180, 80]
|
||||
})
|
||||
|
||||
expect(subgraphNode.id).toBe(42)
|
||||
expect(subgraphNode.id).toBe(asNodeId(42))
|
||||
expect(Array.from(subgraphNode.pos)).toEqual([300, 150])
|
||||
expect(Array.from(subgraphNode.size)).toEqual([180, 80])
|
||||
})
|
||||
@@ -705,13 +706,13 @@ describe('SubgraphNode Execution', () => {
|
||||
})
|
||||
|
||||
const childSubgraphNode = createTestSubgraphNode(childSubgraph, {
|
||||
id: 42,
|
||||
id: asNodeId(42),
|
||||
parentGraph: parentSubgraph
|
||||
})
|
||||
parentSubgraph.add(childSubgraphNode)
|
||||
|
||||
const parentSubgraphNode = createTestSubgraphNode(parentSubgraph, {
|
||||
id: 10,
|
||||
id: asNodeId(10),
|
||||
parentGraph: rootGraph
|
||||
})
|
||||
rootGraph.add(parentSubgraphNode)
|
||||
@@ -951,7 +952,7 @@ describe('SubgraphNode duplicate input pruning (#9977)', () => {
|
||||
|
||||
const parentGraph = new LGraph()
|
||||
const instanceData = {
|
||||
id: 1 as const,
|
||||
id: asNodeId(1),
|
||||
type: subgraph.id,
|
||||
pos: [0, 0] as [number, number],
|
||||
size: [200, 100] as [number, number],
|
||||
|
||||
@@ -43,6 +43,7 @@ import { useWidgetValueStore } from '@/stores/widgetValueStore'
|
||||
import { createNodeLocatorId } from '@/types/nodeIdentification'
|
||||
import type { WidgetId } from '@/types/widgetId'
|
||||
import { widgetId } from '@/types/widgetId'
|
||||
import { asNodeId } from '@/types/nodeId'
|
||||
|
||||
import { ExecutableNodeDTO } from './ExecutableNodeDTO'
|
||||
import type { ExecutableLGraphNode, ExecutionId } from './ExecutableNodeDTO'
|
||||
@@ -733,7 +734,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
}
|
||||
|
||||
const newLink = LLink.create(innerLink)
|
||||
newLink.origin_id = `${this.id}:${innerLink.origin_id}`
|
||||
newLink.origin_id = asNodeId(`${this.id}:${innerLink.origin_id}`)
|
||||
newLink.origin_slot = innerLink.origin_slot
|
||||
|
||||
return newLink
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { CanvasPointer } from '@/lib/litegraph/src/CanvasPointer'
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { LLink } from '@/lib/litegraph/src/LLink'
|
||||
import type { RerouteId } from '@/lib/litegraph/src/Reroute'
|
||||
import type { LinkConnector } from '@/lib/litegraph/src/canvas/LinkConnector'
|
||||
import { SUBGRAPH_OUTPUT_ID } from '@/lib/litegraph/src/constants'
|
||||
import { SUBGRAPH_OUTPUT_NODE_ID } from '@/types/nodeId'
|
||||
import type {
|
||||
DefaultConnectionColors,
|
||||
INodeInputSlot,
|
||||
@@ -25,7 +25,7 @@ export class SubgraphOutputNode
|
||||
extends SubgraphIONodeBase<SubgraphOutput>
|
||||
implements Positionable
|
||||
{
|
||||
readonly id: NodeId = SUBGRAPH_OUTPUT_ID
|
||||
readonly id = SUBGRAPH_OUTPUT_NODE_ID
|
||||
|
||||
readonly emptySlot: EmptySubgraphOutput = new EmptySubgraphOutput(this)
|
||||
|
||||
|
||||
@@ -10,9 +10,12 @@ import { setActivePinia } from 'pinia'
|
||||
|
||||
import { duplicateSubgraphNodeIds } from '@/lib/litegraph/src/__fixtures__/duplicateSubgraphNodeIds'
|
||||
import {
|
||||
asNodeId,
|
||||
isNumericNodeId,
|
||||
LGraph,
|
||||
LGraphNode,
|
||||
LiteGraph,
|
||||
nodeIdToNumber,
|
||||
Subgraph
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type { ISlotType } from '@/lib/litegraph/src/litegraph'
|
||||
@@ -189,7 +192,9 @@ describe('SubgraphSerialization - Complex Serialization', () => {
|
||||
})
|
||||
|
||||
// Add child to parent
|
||||
const childInstance = createTestSubgraphNode(childSubgraph, { id: 100 })
|
||||
const childInstance = createTestSubgraphNode(childSubgraph, {
|
||||
id: asNodeId(100)
|
||||
})
|
||||
parentSubgraph.add(childInstance)
|
||||
|
||||
// Serialize both
|
||||
@@ -282,11 +287,11 @@ describe('SubgraphSerialization - Version Compatibility', () => {
|
||||
inputs: [{ id: 'input-id', name: 'modern_input', type: 'number' }],
|
||||
outputs: [{ id: 'output-id', name: 'modern_output', type: 'string' }],
|
||||
inputNode: {
|
||||
id: -10,
|
||||
id: asNodeId(-10),
|
||||
bounding: [0, 0, 120, 60]
|
||||
},
|
||||
outputNode: {
|
||||
id: -20,
|
||||
id: asNodeId(-20),
|
||||
bounding: [300, 0, 120, 60]
|
||||
},
|
||||
widgets: []
|
||||
@@ -312,11 +317,11 @@ describe('SubgraphSerialization - Version Compatibility', () => {
|
||||
config: {},
|
||||
definitions: { subgraphs: [] },
|
||||
inputNode: {
|
||||
id: -10,
|
||||
id: asNodeId(-10),
|
||||
bounding: [0, 0, 120, 60]
|
||||
},
|
||||
outputNode: {
|
||||
id: -20,
|
||||
id: asNodeId(-20),
|
||||
bounding: [300, 0, 120, 60]
|
||||
}
|
||||
// Missing optional: inputs, outputs, widgets
|
||||
@@ -345,11 +350,11 @@ describe('SubgraphSerialization - Version Compatibility', () => {
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
inputNode: {
|
||||
id: -10,
|
||||
id: asNodeId(-10),
|
||||
bounding: [0, 0, 120, 60]
|
||||
},
|
||||
outputNode: {
|
||||
id: -20,
|
||||
id: asNodeId(-20),
|
||||
bounding: [300, 0, 120, 60]
|
||||
},
|
||||
widgets: [],
|
||||
@@ -493,9 +498,9 @@ describe('SubgraphSerialization - Data Integrity', () => {
|
||||
|
||||
const rootIds = graph.nodes
|
||||
.map((node) => node.id)
|
||||
.filter((id): id is number => typeof id === 'number')
|
||||
.sort((a, b) => a - b)
|
||||
expect(rootIds).toEqual([102, 103])
|
||||
.filter((id) => isNumericNodeId(id))
|
||||
.sort((a, b) => nodeIdToNumber(a) - nodeIdToNumber(b))
|
||||
expect(rootIds).toEqual([asNodeId(102), asNodeId(103)])
|
||||
|
||||
const subgraphAIds = new Set(
|
||||
graph.subgraphs.get(DUPLICATE_ID_SUBGRAPH_A)!.nodes.map((node) => node.id)
|
||||
@@ -504,7 +509,9 @@ describe('SubgraphSerialization - Data Integrity', () => {
|
||||
graph.subgraphs.get(DUPLICATE_ID_SUBGRAPH_B)!.nodes.map((node) => node.id)
|
||||
)
|
||||
|
||||
expect(subgraphAIds).toEqual(new Set([3, 8, 37]))
|
||||
expect(subgraphAIds).toEqual(
|
||||
new Set([asNodeId(3), asNodeId(8), asNodeId(37)])
|
||||
)
|
||||
for (const id of subgraphAIds) {
|
||||
expect(subgraphBIds.has(id)).toBe(false)
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
|
||||
import {
|
||||
SUBGRAPH_INPUT_ID,
|
||||
asNodeId,
|
||||
LinkConnector,
|
||||
ToInputFromIoNodeLink,
|
||||
LGraphNode,
|
||||
isSubgraphInput,
|
||||
isSubgraphOutput
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { isSubgraphInputNodeId } from '@/types/nodeId'
|
||||
import type {
|
||||
LinkNetwork,
|
||||
NodeInputSlot,
|
||||
@@ -132,7 +133,7 @@ describe('Subgraph slot connections', () => {
|
||||
|
||||
// Create a node inside the subgraph
|
||||
const internalNode = new LGraphNode('InternalNode')
|
||||
internalNode.id = 100
|
||||
internalNode.id = asNodeId(100)
|
||||
internalNode.addInput('in', 'number')
|
||||
subgraph.add(internalNode)
|
||||
|
||||
@@ -142,7 +143,7 @@ describe('Subgraph slot connections', () => {
|
||||
internalNode
|
||||
)
|
||||
expect(link).toBeDefined()
|
||||
expect(link!.origin_id).toBe(SUBGRAPH_INPUT_ID)
|
||||
expect(isSubgraphInputNodeId(link!.origin_id)).toBe(true)
|
||||
expect(link!.target_id).toBe(internalNode.id)
|
||||
|
||||
// Verify the input slot has the link
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user