mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-05 00:46:00 +08:00
Build decision flow editor and runtime integration
This commit is contained in:
103
server/models/DecisionFlow.ts
Normal file
103
server/models/DecisionFlow.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import mongoose from 'mongoose'
|
||||
import type {
|
||||
DecisionFlowLayout,
|
||||
DecisionFlowMetadata,
|
||||
} from '~~/shared/types/decision'
|
||||
|
||||
export interface DecisionFlowDocument extends mongoose.Document {
|
||||
slug: string
|
||||
name: string
|
||||
description?: string
|
||||
schemaVersion?: string
|
||||
startState: string
|
||||
endStates: string[]
|
||||
variables: Record<string, any>
|
||||
flags: Record<string, any>
|
||||
policies: Record<string, any>
|
||||
hooks: Record<string, any>
|
||||
roles: string[]
|
||||
phases: string[]
|
||||
layout?: DecisionFlowLayout
|
||||
metadata?: DecisionFlowMetadata
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
const decisionFlowSchema = new mongoose.Schema<DecisionFlowDocument>(
|
||||
{
|
||||
slug: { type: String, required: true, unique: true, index: true },
|
||||
name: { type: String, required: true },
|
||||
description: { type: String },
|
||||
schemaVersion: { type: String },
|
||||
startState: { type: String, required: true },
|
||||
endStates: { type: [String], default: () => [] },
|
||||
variables: { type: mongoose.Schema.Types.Mixed, default: () => ({}) },
|
||||
flags: { type: mongoose.Schema.Types.Mixed, default: () => ({}) },
|
||||
policies: { type: mongoose.Schema.Types.Mixed, default: () => ({}) },
|
||||
hooks: { type: mongoose.Schema.Types.Mixed, default: () => ({}) },
|
||||
roles: { type: [String], default: () => [] },
|
||||
phases: { type: [String], default: () => [] },
|
||||
layout: {
|
||||
type: new mongoose.Schema<DecisionFlowLayout>(
|
||||
{
|
||||
zoom: { type: Number, default: 1 },
|
||||
pan: {
|
||||
type: new mongoose.Schema({ x: { type: Number, default: 0 }, y: { type: Number, default: 0 } }, { _id: false }),
|
||||
default: () => ({ x: 0, y: 0 }),
|
||||
},
|
||||
groups: {
|
||||
type: [
|
||||
new mongoose.Schema(
|
||||
{
|
||||
id: { type: String, required: true },
|
||||
label: { type: String, required: true },
|
||||
color: { type: String },
|
||||
bounds: {
|
||||
type: new mongoose.Schema(
|
||||
{
|
||||
x: { type: Number, required: true },
|
||||
y: { type: Number, required: true },
|
||||
width: { type: Number, required: true },
|
||||
height: { type: Number, required: true },
|
||||
},
|
||||
{ _id: false }
|
||||
),
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{ _id: false }
|
||||
),
|
||||
],
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
{ _id: false }
|
||||
),
|
||||
default: () => ({ zoom: 1, pan: { x: 0, y: 0 }, groups: [] }),
|
||||
},
|
||||
metadata: {
|
||||
type: new mongoose.Schema<DecisionFlowMetadata>(
|
||||
{
|
||||
notes: { type: String },
|
||||
tags: { type: [String], default: () => [] },
|
||||
ownerId: { type: String },
|
||||
lastEditedBy: { type: String },
|
||||
},
|
||||
{ _id: false }
|
||||
),
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
)
|
||||
|
||||
decisionFlowSchema.index({ updatedAt: -1 })
|
||||
|
||||
decisionFlowSchema.set('toJSON', {
|
||||
virtuals: true,
|
||||
getters: true,
|
||||
})
|
||||
|
||||
export const DecisionFlow =
|
||||
(mongoose.models.DecisionFlow as mongoose.Model<DecisionFlowDocument>) ||
|
||||
mongoose.model<DecisionFlowDocument>('DecisionFlow', decisionFlowSchema)
|
||||
178
server/models/DecisionNode.ts
Normal file
178
server/models/DecisionNode.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import mongoose from 'mongoose'
|
||||
import type {
|
||||
DecisionNodeAutoTrigger,
|
||||
DecisionNodeLayout,
|
||||
DecisionNodeLLMPlaceholder,
|
||||
DecisionNodeLLMTemplate,
|
||||
DecisionNodeMetadata,
|
||||
DecisionNodeModel,
|
||||
DecisionNodeTransition,
|
||||
} from '~~/shared/types/decision'
|
||||
|
||||
export interface DecisionNodeDocument
|
||||
extends mongoose.Document,
|
||||
Omit<DecisionNodeModel, 'stateId' | 'transitions'> {
|
||||
flow: mongoose.Types.ObjectId
|
||||
stateId: string
|
||||
transitions: DecisionNodeTransition[]
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
const llmPlaceholderSchema = new mongoose.Schema<DecisionNodeLLMPlaceholder>(
|
||||
{
|
||||
key: { type: String, required: true },
|
||||
label: { type: String, required: true },
|
||||
description: { type: String },
|
||||
required: { type: Boolean, default: false },
|
||||
example: { type: String },
|
||||
defaultValue: { type: String },
|
||||
type: { type: String, default: 'text' },
|
||||
},
|
||||
{ _id: false }
|
||||
)
|
||||
|
||||
const llmTemplateSchema = new mongoose.Schema<DecisionNodeLLMTemplate>(
|
||||
{
|
||||
summary: { type: String },
|
||||
prompt: { type: String },
|
||||
responseSchema: { type: String },
|
||||
autoProceed: { type: Boolean, default: false },
|
||||
temperature: { type: Number },
|
||||
topP: { type: Number },
|
||||
maxOutputTokens: { type: Number },
|
||||
placeholders: { type: [llmPlaceholderSchema], default: () => [] },
|
||||
guardrails: { type: [String], default: () => [] },
|
||||
notes: { type: String },
|
||||
},
|
||||
{ _id: false }
|
||||
)
|
||||
|
||||
const metadataSchema = new mongoose.Schema<DecisionNodeMetadata>(
|
||||
{
|
||||
tags: { type: [String], default: () => [] },
|
||||
notes: { type: String },
|
||||
pinned: { type: Boolean, default: false },
|
||||
complexity: { type: String, enum: ['low', 'medium', 'high'], default: 'medium' },
|
||||
},
|
||||
{ _id: false }
|
||||
)
|
||||
|
||||
const layoutSchema = new mongoose.Schema<DecisionNodeLayout>(
|
||||
{
|
||||
x: { type: Number, required: true },
|
||||
y: { type: Number, required: true },
|
||||
width: { type: Number },
|
||||
height: { type: Number },
|
||||
color: { type: String },
|
||||
icon: { type: String },
|
||||
locked: { type: Boolean, default: false },
|
||||
},
|
||||
{ _id: false }
|
||||
)
|
||||
|
||||
const autoTriggerSchema = new mongoose.Schema<DecisionNodeAutoTrigger>(
|
||||
{
|
||||
id: { type: String, required: true },
|
||||
type: { type: String, enum: ['telemetry', 'variable', 'expression'], required: true },
|
||||
parameter: { type: String },
|
||||
variable: { type: String },
|
||||
operator: { type: String },
|
||||
value: { type: mongoose.Schema.Types.Mixed },
|
||||
unit: { type: String },
|
||||
expression: { type: String },
|
||||
description: { type: String },
|
||||
once: { type: Boolean, default: true },
|
||||
delayMs: { type: Number },
|
||||
},
|
||||
{ _id: false }
|
||||
)
|
||||
|
||||
const transitionSchema = new mongoose.Schema<DecisionNodeTransition>(
|
||||
{
|
||||
key: { type: String, required: true },
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['next', 'ok', 'bad', 'timer', 'auto', 'interrupt', 'return'],
|
||||
default: 'next',
|
||||
},
|
||||
target: { type: String, required: true },
|
||||
label: { type: String },
|
||||
description: { type: String },
|
||||
condition: { type: String },
|
||||
guard: { type: String },
|
||||
order: { type: Number, default: 0 },
|
||||
timer: {
|
||||
type: new mongoose.Schema(
|
||||
{
|
||||
afterSeconds: { type: Number, required: true },
|
||||
allowManualProceed: { type: Boolean, default: true },
|
||||
},
|
||||
{ _id: false }
|
||||
),
|
||||
default: undefined,
|
||||
},
|
||||
autoTrigger: { type: autoTriggerSchema, default: undefined },
|
||||
metadata: {
|
||||
type: new mongoose.Schema(
|
||||
{
|
||||
color: { type: String },
|
||||
icon: { type: String },
|
||||
notes: { type: String },
|
||||
previewTemplate: { type: String },
|
||||
},
|
||||
{ _id: false }
|
||||
),
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
{ _id: false }
|
||||
)
|
||||
|
||||
const decisionNodeSchema = new mongoose.Schema<DecisionNodeDocument>(
|
||||
{
|
||||
flow: { type: mongoose.Schema.Types.ObjectId, ref: 'DecisionFlow', required: true, index: true },
|
||||
stateId: { type: String, required: true },
|
||||
title: { type: String },
|
||||
summary: { type: String },
|
||||
role: { type: String, enum: ['pilot', 'atc', 'system'], required: true },
|
||||
phase: { type: String, required: true },
|
||||
sayTemplate: { type: String },
|
||||
utteranceTemplate: { type: String },
|
||||
elseSayTemplate: { type: String },
|
||||
readbackRequired: { type: [String], default: () => [] },
|
||||
autoBehavior: { type: String },
|
||||
actions: { type: [mongoose.Schema.Types.Mixed], default: () => [] },
|
||||
handoff: {
|
||||
type: new mongoose.Schema(
|
||||
{
|
||||
to: { type: String, required: true },
|
||||
freq: { type: String },
|
||||
note: { type: String },
|
||||
},
|
||||
{ _id: false }
|
||||
),
|
||||
default: undefined,
|
||||
},
|
||||
guard: { type: String },
|
||||
trigger: { type: String },
|
||||
frequency: { type: String },
|
||||
frequencyName: { type: String },
|
||||
transitions: { type: [transitionSchema], default: () => [] },
|
||||
layout: { type: layoutSchema, default: undefined },
|
||||
metadata: { type: metadataSchema, default: undefined },
|
||||
llmTemplate: { type: llmTemplateSchema, default: undefined },
|
||||
},
|
||||
{ timestamps: true }
|
||||
)
|
||||
|
||||
decisionNodeSchema.index({ flow: 1, stateId: 1 }, { unique: true })
|
||||
|
||||
decisionNodeSchema.set('toJSON', {
|
||||
virtuals: true,
|
||||
getters: true,
|
||||
})
|
||||
|
||||
export const DecisionNode =
|
||||
(mongoose.models.DecisionNode as mongoose.Model<DecisionNodeDocument>) ||
|
||||
mongoose.model<DecisionNodeDocument>('DecisionNode', decisionNodeSchema)
|
||||
Reference in New Issue
Block a user