added url to flow service
This commit is contained in:
parent
1fef10ab17
commit
8a8f486eb5
|
|
@ -11,6 +11,8 @@ export type Flow = {
|
|||
description?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
published?: boolean;
|
||||
finalized?: boolean;
|
||||
};
|
||||
|
||||
export type FlowData = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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"
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[]> = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue