Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8f37165
feat: add DataTypeListSubFlowInputComponent for handling list of sub-…
nicosammito Aug 23, 2026
3bebf0c
feat: update flowIndex parsing in NodeBadgeComponent for improved typ…
nicosammito Aug 23, 2026
f8889ff
feat: enhance keyword filtering in SuggestionDialogComponent for impr…
nicosammito Aug 23, 2026
ac14266
feat: enhance mapNodeValue and mapNodeParameter functions to support …
nicosammito Aug 23, 2026
2e3e21a
feat: improve sub-flow handling in DataTypeListSubFlowInputComponent …
nicosammito Aug 23, 2026
d2e18e3
feat: enhance sub-flow handling in Flow components for improved param…
nicosammito Aug 23, 2026
dde2f28
feat: enhance sub-flow value handling in Flow.nodes.hook.ts to includ…
nicosammito Aug 24, 2026
59b8d17
feat: enhance parameter node removal logic to support sub-flow refere…
nicosammito Aug 24, 2026
61dbc0c
feat: adjust vertical positioning in FlowBuilderComponent for improve…
nicosammito Aug 24, 2026
218ff97
feat: enhance node deletion logic in FlowPanelControlComponent to sup…
nicosammito Aug 24, 2026
44e77e3
feat: extend FunctionNodeComponent to include parameterId and referen…
nicosammito Aug 24, 2026
88986fe
feat: improve text positioning in FunctionNodeSquareComponent for bet…
nicosammito Aug 24, 2026
a440b48
feat: update flowIndex parsing in DataTypeSubFlowInputComponent for i…
nicosammito Aug 24, 2026
c388e64
feat: update parameter handling in Flow.nodes.hook.ts to use paramete…
nicosammito Aug 24, 2026
fc0f49d
feat: update removeParameterMapping to use parameterIndex for improve…
nicosammito Aug 24, 2026
8f82239
feat: update FlowPanelControlComponent to use parameterIndex for impr…
nicosammito Aug 24, 2026
2ba82aa
feat: update FunctionNodeComponent to use parameterIndex for improved…
nicosammito Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface NodeBadgeComponentProps extends Omit<BadgeType, 'value' | 'chil
export const NodeBadgeComponent: React.FC<NodeBadgeComponentProps> = (props) => {

const params = useParams()
const flowIndex = params.flowId as any as number
const flowIndex = Number(params.flowId) || 1
const flowId: Flow['id'] = `gid://sagittarius/Flow/${flowIndex}`

const {value, definition, ...rest} = props
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ import {DataTypeGenericInputComponent} from "@edition/datatype/components/inputs
import {
DataTypeSubFlowInputComponent
} from "@edition/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent";
import {
DataTypeListSubFlowInputComponent
} from "@edition/datatype/components/inputs/list-sub-flow/DataTypeListSubFlowInputComponent";

export interface DataTypeInputComponentProps extends Omit<InputWrapperProps<NodeParameterValue | NodeFunction>, "onChange"> {
schema: (NodeSchema | Schema)
Expand Down Expand Up @@ -123,6 +126,11 @@ export const DataTypeInputComponent: React.FC<DataTypeInputComponentProps> = (pr
schema={schema}
suggestions={suggestions}
{...rest}/>
case "list-sub-flow":
return <DataTypeListSubFlowInputComponent
schema={schema}
suggestions={suggestions}
{...rest}/>
default:
return <DataTypeTextInputComponent
suggestions={suggestions}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from "react";
import {DataTypeInputComponentProps} from "../DataTypeInputComponent";
import {
Button,
InputDescription,
InputLabel,
TagInput,
TagInputTrigger,
TagInputValue,
TagValue,
useService
} from "@code0-tech/pictor";
import {useDebouncedCallback} from "use-debounce";
import {
Flow,
InlineReferenceValue,
LiteralValue,
NodeFunction,
ReferenceValue,
SubFlowValue
} from "@code0-tech/sagittarius-graphql-types";
import {NodeSchema, Schema} from "@code0-tech/triangulum";
import {IconPlus} from "@tabler/icons-react";
import {useParams} from "next/navigation";
import {FlowService} from "@edition/flow/services/Flow.service";
import {useFunctionSuggestions} from "@edition/function/hooks/Function.suggestion.hook";
import {SuggestionDialogComponent} from "@edition/function/components/suggestion/SuggestionDialogComponent";
import {DataTypeInputControlsComponent} from "@edition/datatype/components/inputs/DataTypeInputControlsComponent";
import {DataTypeInputValueComponent} from "@edition/datatype/components/inputs/DataTypeInputValueComponent";
import {NodeBadgeComponent} from "@edition/datatype/components/badges/NodeBadgeComponent";

export type DataTypeListSubFlowInputComponentProps = DataTypeInputComponentProps

export const DataTypeListSubFlowInputComponent: React.FC<DataTypeListSubFlowInputComponentProps> = (props) => {

const {schema, formValidation, title, initialValue, description, suggestions, onChange} = props

const params = useParams()
const flowService = useService(FlowService)

const flowIndex = Number(params.flowId) || 1
const flowId: Flow['id'] = `gid://sagittarius/Flow/${flowIndex}`

const initialSubFlows: SubFlowValue[] = ((initialValue as LiteralValue)?.__typename === "LiteralValue"
? ((initialValue as LiteralValue).references ?? [])
: [])
.map(reference => reference.value)
.filter((value): value is SubFlowValue => value?.__typename === "SubFlowValue")

const [subFlows, setSubFlows] = React.useState<SubFlowValue[]>(initialSubFlows)
const [dialogOpen, setDialogOpen] = React.useState(false)
const functionSuggestions = useFunctionSuggestions()

const directMappingSuggestions = React.useMemo(() => {
const inner = schema && "schema" in schema ? (schema as NodeSchema).schema : (schema as Schema | undefined)
const items = (inner as { items?: Schema[] })?.items ?? []
const seen = new Set<string>()
return items.flatMap(item => item.suggestions ?? [])
.filter((suggest): suggest is SubFlowValue => suggest.__typename === "SubFlowValue")
.filter(suggest => {
const key = suggest.functionDefinition?.id ?? suggest.signature ?? ""
if (seen.has(key)) return false
seen.add(key)
return true
})
}, [schema])

const referenceSuggestions = React.useMemo(
() => (suggestions ?? []).filter(suggest => suggest.__typename !== "LiteralValue"),
[suggestions]
)

const onChangeDebounced = useDebouncedCallback((value: LiteralValue | SubFlowValue | NodeFunction | ReferenceValue | null) => {
onChange?.(value)
}, 200)

const keyOf = (value: SubFlowValue) => value.startingNodeId ?? value.functionDefinition?.id ?? ""
const byKey = new Map(subFlows.map(value => [keyOf(value), value]))
const tags: TagValue[] = subFlows.map(value => ({value: keyOf(value)}))

const commit = (next: SubFlowValue[]) => {
setSubFlows(next)

if (next.length === 0) {
formValidation?.setValue?.(null)
onChangeDebounced(null)
return
}

const references: InlineReferenceValue[] = next.map((value, index) => ({
__typename: "InlineReferenceValue",
signature: `sub_flow_${index}`,
value
}))
const literal: LiteralValue = {
__typename: "LiteralValue",
value: next.map((_, index) => `\${sub_flow_${index}}`),
references
}
formValidation?.setValue?.(literal)
onChangeDebounced(literal)
}

return <>
<SuggestionDialogComponent suggestions={[...directMappingSuggestions, ...functionSuggestions]}
open={dialogOpen}
onOpenChange={setDialogOpen}
onSuggestionSelect={value => {
if (value?.__typename === "NodeFunction") {
const nodeId = flowService.addNodeById(flowId, value)
value = {__typename: "SubFlowValue", startingNodeId: nodeId}
}
if (value?.__typename !== "SubFlowValue") return
commit([...subFlows, value])
}}/>
<InputLabel>{title}</InputLabel>
<InputDescription>{description}</InputDescription>
<DataTypeInputValueComponent initialValue={initialValue}
onChange={value => {
formValidation?.setValue?.(value)
onChangeDebounced(value)
}}
suggestions={suggestions}
formValidation={formValidation}>
<TagInput allowCustomValues={false}
placeholder={typeof title === "string" ? title : undefined}
initialValue={tags}
maw={"100%"}
tokenRules={[
{
pattern: /.+/,
void: true,
wrap: matchedText => {
const value = byKey.get(matchedText)
return value ? <NodeBadgeComponent value={value}/> : null
}
}
]}
formValidation={{...formValidation, setValue: undefined}}
onChange={changed => {
commit(changed
.map(tag => byKey.get(String(tag.value)))
.filter((value): value is SubFlowValue => value !== undefined))
}}
right={
<DataTypeInputControlsComponent suggestions={referenceSuggestions} onSelect={value => {
if (value?.__typename === "SubFlowValue") {
commit([...subFlows, value])
return
}
if (!value) {
commit([])
return
}
setSubFlows([])
formValidation?.setValue?.(value)
onChangeDebounced(value)
}}>
<Button paddingSize={"xxs"} onClick={() => setDialogOpen(true)}>
<IconPlus size={13}/>
</Button>
</DataTypeInputControlsComponent>
}
rightType={"action"}>
<TagInputValue/>
<TagInputTrigger/>
</TagInput>
</DataTypeInputValueComponent>
</>
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const DataTypeSubFlowInputComponent: React.FC<DataTypeSubFlowInputCompone
const params = useParams()
const flowService = useService(FlowService)

const flowIndex = params.flowId as any as number
const flowIndex = Number(params.flowId) || 1
const flowId: Flow['id'] = `gid://sagittarius/Flow/${flowIndex}`

const defaultValue: number = React.useMemo(() => suggestions?.findIndex(suggest => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set<string>) => {
const paramIds = new Map<string, string[]>()

for (const n of nodes) {
const link = (n.data as any)?.parentNodeId
const link = (n.data as any)?.parentNodeId ?? (n.type === "group" ? (n.data as any)?.nodeId : undefined)
if (link) {
const arr = paramIds.get(link) ?? []
arr.push(n.id)
Expand Down Expand Up @@ -167,10 +167,6 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set<string>) => {
// relatives Layout (Center in globalen Koordinaten)
const relCenter = new Map<string, Pos>()

// Unterkante je rechter Spalten-"Band", damit Parameter nicht kollidieren
const columnBottom = new Map<number, number>()
const colKey = (x: number) => Math.round(x / 10)

const layoutIter = (root: Node, cx: number, cy: number): number => {
type Frame = {
node: Node
Expand All @@ -181,9 +177,8 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set<string>) => {
h?: number
right?: Node[]
rightIndex?: number
py?: number
rightX?: number
rightBottom?: number
childKey?: number
childPs?: Size
lastChildBottom?: number

Expand Down Expand Up @@ -225,11 +220,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set<string>) => {
f.right = right
f.gParams = gParams

let total = 0
for (const p of right) total += size(p).h
total += V * Math.max(0, right.length - 1)

f.py = f.cy - total / 2
f.rightX = f.cx + f.w! / 2 + H
f.rightBottom = f.cy + h / 2
f.rightIndex = 0
f.phase = 1
Expand All @@ -240,22 +231,13 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set<string>) => {
if (f.rightIndex! < f.right!.length) {
const p = f.right![f.rightIndex!]
const ps = size(p)
const px = f.cx + f.w! / 2 + H + ps.w / 2
let pcy = f.py! + ps.h / 2

const key = colKey(px)
const occ = columnBottom.get(key) ?? Number.NEGATIVE_INFINITY
const minTop = occ + V
const desiredTop = pcy - ps.h / 2

if (desiredTop < minTop) {
pcy = minTop + ps.h / 2
f.py = pcy - ps.h / 2
}
const pcx = f.rightX! + ps.w / 2
const pcy = f.cy

f.childKey = key
f.rightX = f.rightX! + ps.w + H
f.rightBottom = Math.max(f.rightBottom!, pcy + ps.h / 2)
f.childPs = ps
stack.push({node: p, cx: px, cy: pcy, phase: 0})
stack.push({node: p, cx: pcx, cy: pcy, phase: 0})
f.phase = 10
} else {
f.bottom = Math.max(f.cy + f.h! / 2, f.rightBottom!)
Expand All @@ -266,12 +248,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set<string>) => {

case 10: {
const subBottom = f.lastChildBottom!
columnBottom.set(
f.childKey!,
Math.max(columnBottom.get(f.childKey!) ?? Number.NEGATIVE_INFINITY, subBottom)
)
f.rightBottom = Math.max(f.rightBottom!, subBottom)
f.py = Math.max(f.py! + f.childPs!.h + V, subBottom + V)
f.rightIndex!++
f.phase = 1
break
Expand All @@ -290,7 +267,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set<string>) => {

f.gSizes = gSizes
f.gx = f.cx - rowW / 2
f.gy = f.bottom! + V
f.gy = f.bottom! + 2 * V
f.rowBottom = f.bottom
f.gIndex = 0
f.phase = 3
Expand Down Expand Up @@ -386,7 +363,8 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set<string>) => {
// Root-Nodes stapeln
let yCursor = 0
for (const r of nodes) {
if (!(r.data as any)?.parentNodeId && !r.parentId) {
const link = (r.data as any)?.parentNodeId ?? (r.type === "group" ? (r.data as any)?.nodeId : undefined)
if (!link && !r.parentId) {
const b = layoutIter(r, 0, yCursor + size(r).h / 2)
yCursor = b + V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {AIChatComponent} from "@edition/ai/components/AIChatComponent";
import {mapAiGenerationFlowToFlowInput} from "@edition/ai/util/AI.flow.mapper";
import {toast} from "@code0-tech/pictor/dist/components/toast/Toast";
import {useFlowCompareStore} from "@edition/flow/hooks/Flow.compare.hook";
import {FunctionNodeComponentProps} from "@edition/function/components/nodes/FunctionNodeComponent";
import {FlowView} from "@edition/flow/services/Flow.view";
import {FlowExecuteDialogComponent} from "@edition/flow/components/FlowExecuteDialogComponent";

Expand Down Expand Up @@ -67,11 +68,16 @@ export const FlowPanelControlComponent: React.FC<FlowPanelControlComponentProps>
//callbacks
const deleteActiveNode = React.useCallback(() => {
if (!selectedNode) return
const data = selectedNode.data as FunctionNodeComponentProps
// @ts-ignore
startTransition(async () => {
await flowService.deleteNodeById(flowId, selectedNode?.id as NodeFunction['id'])
if (data?.isParameter && data?.parentNodeId && data?.parameterIndex != null) {
await flowService.removeParameterMapping(flowId, data.parentNodeId, data.parameterIndex, data.referenceSignature)
} else {
await flowService.deleteNodeById(flowId, selectedNode?.id as NodeFunction['id'])
}
})
}, [selectedNode, flowService, flowStore])
}, [selectedNode, flowService, flowStore, flowId])

const onAIData = React.useCallback((payload: AiGenerateFlowSubscriptionPayload) => {
const aiFlow = payload?.flow
Expand Down
Loading