added url to flow service

This commit is contained in:
Lucio Lelii 2026-03-06 12:04:56 +01:00
parent 1fef10ab17
commit 8a8f486eb5
7 changed files with 148 additions and 50 deletions

View File

@ -11,6 +11,8 @@ export type Flow = {
description?: string;
createdAt: Date;
updatedAt: Date;
published?: boolean;
finalized?: boolean;
};
export type FlowData = {

View File

@ -0,0 +1,49 @@
import { Flow, FlowData, FlowVisibility } from '@models/flow';
function parseDate(value: unknown, fallback: Date): Date {
if (typeof value !== 'string' || !value) return fallback;
const parsed = new Date(value);
return Number.isNaN(parsed.getTime()) ? fallback : parsed;
}
export function flowFromApi(raw: unknown): Flow {
const value = (raw ?? {}) as Record<string, unknown>;
const now = new Date();
const createdAt = parseDate(value['createdAt'], now);
const updatedAt = parseDate(value['updatedAt'] ?? value['lastUpdateAt'], createdAt);
const data = (value['data'] ?? value['flow'] ?? {}) as Partial<FlowData>;
const published = typeof value['published'] === 'boolean'
? value['published']
: ((value['visibility'] as FlowVisibility | undefined) === 'PUBLIC');
const visibility: FlowVisibility = published ? 'PUBLIC' : 'PRIVATE';
return {
id: String(value['id'] ?? crypto.randomUUID()),
name: String(value['name'] ?? 'Untitled Flow'),
description: typeof value['description'] === 'string' ? value['description'] : undefined,
author: String(value['author'] ?? value['owner'] ?? 'unknown'),
createdAt,
updatedAt,
visibility,
published,
finalized: typeof value['finalized'] === 'boolean' ? value['finalized'] : undefined,
data: {
blocks: Array.isArray(data.blocks) ? data.blocks : [],
connections: Array.isArray(data.connections) ? data.connections : []
}
};
}
export function toFlowCreateRequest(name: string) {
return { name };
}
export function toFlowUpdateRequest(flow: Flow) {
return {
name: flow.name,
description: flow.description,
published: flow.published ?? flow.visibility === 'PUBLIC',
finalized: flow.finalized ?? false,
flow: flow.data
};
}

View File

@ -1,8 +1,9 @@
import { Flow, FlowVisibility } from "@models/flow";
import { Flow } from "@models/flow";
import { FlowsCallServiceBase } from "./flows-call.base";
import { Observable, of } from "rxjs";
import { Authorization } from "@services/authorization/authorization";
import { inject } from "@angular/core";
import { flowFromApi } from "./flow-mapper";
export class FlowsCallServiceFake extends FlowsCallServiceBase {
override getFlowById(flowId: string): Observable<Flow> {
@ -18,7 +19,7 @@ export class FlowsCallServiceFake extends FlowsCallServiceBase {
private data: Record<string, Flow> = {
'1': { id: '1', name: 'A Flow', data: { blocks: [], connections: [] }, visibility: 'PUBLIC', author: 'Alice', createdAt: new Date("December 17, 2023 03:24:00"), updatedAt: new Date("January 7, 2026 12:24:00") },
'2': { id: '2', name: 'Test Flow', data: { blocks: [], connections: [] }, visibility: 'PRIVATE', author: 'Bob', createdAt: new Date("April 25, 2025 12:24:00"), updatedAt: new Date("April 27, 2025 18:42:00") },
'testFlow': flowFromJson(testDataFlow)
'testFlow': flowFromApi(testDataFlow)
}
override retrieveAllFlows() {
@ -41,35 +42,16 @@ export class FlowsCallServiceFake extends FlowsCallServiceBase {
return of(void 0);
}
}
export function flowFromJson(raw: any): Flow {
const flowId = raw.id ?? raw.name ?? crypto.randomUUID();
return {
id: flowId,
author: raw.author,
visibility: raw.visibility as FlowVisibility,
createdAt: new Date(raw.createdAt),
updatedAt: new Date(raw.updatedAt),
name: raw.name,
description: raw.description,
data: {
blocks: raw.blocks,
connections: raw.connections
}
};
}
const testDataFlow ={
"id": "testFlow",
"name" : "Test Flow",
"visibility": "PRIVATE",
"author": "lucio",
"owner": "lucio",
"published": false,
"finalized": false,
"description" : "This is a test flow",
"createdAt": "2023-12-17T03:24:00.000Z",
"updatedAt": "2026-01-07T12:24:00.000Z",
"lastUpdateAt": "2026-01-07T12:24:00.000Z",
"flow" : {
"blocks" : [ {
"id" : "cabd6f4e-5a05-41f8-9bf7-4de20391ac4e",
"sink" : false,
@ -128,4 +110,5 @@ const testDataFlow ={
"targetId" : "0063a3ec-3863-4045-bd3b-61eaf87b4604",
"targetName" : "input"
} ]
}
};

View File

@ -1,22 +1,40 @@
import { Flow } from "@models/flow";
import { FlowsCallServiceBase } from "./flows-call.base";
import { Observable } from "rxjs";
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { environment } from '@environment';
import { Flow } from '@models/flow';
import { map, Observable } from 'rxjs';
import { flowFromApi, toFlowCreateRequest, toFlowUpdateRequest } from './flow-mapper';
import { FlowsCallServiceBase } from './flows-call.base';
export class FlowsCallService extends FlowsCallServiceBase {
override getFlowById(_flowId: string): Observable<Flow> {
throw new Error("Method not implemented.");
}
override createNewFlow(_name?: string): Observable<Flow> {
throw new Error("Method not implemented.");
}
override deleteFlow(_flowId: string): Observable<void> {
throw new Error("Method not implemented.");
}
override retrieveAllFlows(): Observable<Flow[]> {
throw new Error("Method not implemented.");
}
override updateFlow(_flow: Flow): Observable<void> {
throw new Error("Method not implemented.");
}
private readonly http = inject(HttpClient);
override getFlowById(flowId: string): Observable<Flow> {
const encodedId = encodeURIComponent(flowId);
return this.http
.get<unknown>(`${environment.apiUrl}/flows/${encodedId}`)
.pipe(map((raw) => flowFromApi(raw)));
}
override createNewFlow(name?: string): Observable<Flow> {
return this.http
.post<unknown>(`${environment.apiUrl}/flows`, toFlowCreateRequest(name ?? 'New Flow'))
.pipe(map((raw) => flowFromApi(raw)));
}
override deleteFlow(flowId: string): Observable<void> {
const encodedId = encodeURIComponent(flowId);
return this.http.delete<void>(`${environment.apiUrl}/flows/${encodedId}`);
}
override retrieveAllFlows(): Observable<Flow[]> {
return this.http
.get<unknown[]>(`${environment.apiUrl}/flows`)
.pipe(map((raw) => raw.map((flow) => flowFromApi(flow))));
}
override updateFlow(flow: Flow): Observable<void> {
const encodedId = encodeURIComponent(flow.id);
return this.http.put<void>(`${environment.apiUrl}/flows/${encodedId}`, toFlowUpdateRequest(flow));
}
}

View File

@ -3,8 +3,7 @@ import { FieldRetreiverCallServiceBase } from "./field-retreiver-call.base";
export class FieldRetreiverCallServiceFake extends FieldRetreiverCallServiceBase {
private readonly providersByBlockType: Record<string, string[]> = {
LLMBlock: ["OpenAI", "Anthropic", "OllamaTestProvider"],
HumanInteractionBlock: ["OpenAI", "OllamaTestProvider"]
LLM: ["OpenAI", "Anthropic", "OllamaTestProvider"],
};
private readonly modelsByProvider: Record<string, string[]> = {

View File

@ -30,6 +30,7 @@ type EditableFieldDefinition = {
path: string;
label: string;
type: FieldType;
retrieverBlockType: string | null;
retrieverKey: string | null;
retrieverDependsOn: RetrieverDependency[];
ui: {
@ -370,6 +371,7 @@ export class GenericNodeComponent {
path,
label: pathToLabel(path),
type: this.toFieldType(childResolved?.type),
retrieverBlockType: this.toRetrieverBlockType(childResolved),
retrieverKey: this.toRetrieverKey(childResolved),
retrieverDependsOn: this.toRetrieverDependsOn(childResolved, pathPrefix),
ui: this.toFieldUiMeta(childResolved)
@ -438,10 +440,34 @@ export class GenericNodeComponent {
private toRetrieverKey(schema: Record<string, any> | null | undefined): string | null {
if (!schema || typeof schema !== 'object') return null;
const fromUrl = this.parseRetrieverUrl(schema['x-retriever-url'])?.key;
if (fromUrl) return fromUrl;
const retrieverName = schema['x-retriever-name'];
return typeof retrieverName === 'string' && retrieverName.length > 0 ? retrieverName : null;
}
private toRetrieverBlockType(schema: Record<string, any> | null | undefined): string | null {
if (!schema || typeof schema !== 'object') return null;
return this.parseRetrieverUrl(schema['x-retriever-url'])?.blockType ?? null;
}
private parseRetrieverUrl(rawUrl: unknown): { blockType: string; key: string } | null {
if (typeof rawUrl !== 'string' || rawUrl.trim().length === 0) return null;
const path = rawUrl.split('?')[0];
const parts = path.split('/').filter(Boolean);
const retrieverIndex = parts.findIndex((part) => part === 'retriever');
if (retrieverIndex < 0 || parts.length < retrieverIndex + 3) return null;
const blockType = parts[retrieverIndex + 1];
const key = parts[retrieverIndex + 2];
if (!blockType || !key) return null;
return { blockType, key };
}
private toRetrieverDependsOn(schema: Record<string, any> | null | undefined, pathPrefix: string): RetrieverDependency[] {
if (!schema || typeof schema !== 'object') return [];
@ -458,7 +484,7 @@ export class GenericNodeComponent {
}
private async loadLocalEditorOptions(definition: EditableFieldDefinition) {
const blockType = this.blockType;
const blockType = definition.retrieverBlockType ?? this.blockType;
if (!blockType || !definition.retrieverKey) {
this.localEditorLoading = false;
return;
@ -660,6 +686,7 @@ export class GenericNodeComponent {
}
private async fetchConditionalRequirement(blockType: string, field: ConditionalRequiredField) {
const retrieverBlockType = field.retrieverBlockType ?? blockType;
const context: Record<string, string> = {};
for (const dep of field.dependsOn) {
const value = this.getByPath(this.blockConfiguration ?? {}, dep.path);
@ -668,7 +695,7 @@ export class GenericNodeComponent {
try {
return await firstValueFrom(
this.fieldRetriever.isFieldRequired(blockType, field.retrieverKey, context)
this.fieldRetriever.isFieldRequired(retrieverBlockType, field.retrieverKey, context)
);
} catch {
return false;

View File

@ -8,6 +8,7 @@ export type RequiredField = {
export type ConditionalRequiredField = {
path: string;
label: string;
retrieverBlockType: string | null;
retrieverKey: string;
dependsOn: Array<{ key: string; path: string }>;
};
@ -60,7 +61,9 @@ function walkSchema(
const retrieverRequiredUrl = propertyResolved?.['x-retriever-required-url'];
if (typeof retrieverRequiredUrl === 'string') {
const retrieverKey = String(propertyResolved?.['x-retriever-name'] ?? key);
const parsedRetriever = parseRetrieverUrl(retrieverRequiredUrl);
const retrieverKey = parsedRetriever?.key ?? String(propertyResolved?.['x-retriever-name'] ?? key);
const retrieverBlockType = parsedRetriever?.blockType ?? null;
const rawDepends = Array.isArray(propertyResolved?.['x-retriever-required-depends-on'])
? (propertyResolved['x-retriever-required-depends-on'] as unknown[])
: [];
@ -78,6 +81,7 @@ function walkSchema(
conditional.push({
path: propertyPath,
label,
retrieverBlockType,
retrieverKey,
dependsOn
});
@ -96,3 +100,19 @@ function toLabel(key: string): string {
.replace(/[_-]/g, ' ')
.replace(/^./, (c) => c.toUpperCase());
}
function parseRetrieverUrl(rawUrl: unknown): { blockType: string; key: string } | null {
if (typeof rawUrl !== 'string' || rawUrl.trim().length === 0) return null;
const path = rawUrl.split('?')[0];
const normalized = path.endsWith('/required') ? path.slice(0, -'/required'.length) : path;
const parts = normalized.split('/').filter(Boolean);
const retrieverIndex = parts.findIndex((part) => part === 'retriever');
if (retrieverIndex < 0 || parts.length < retrieverIndex + 3) return null;
const blockType = parts[retrieverIndex + 1];
const key = parts[retrieverIndex + 2];
if (!blockType || !key) return null;
return { blockType, key };
}