mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 08:02:38 +00:00
The flow chat ran on its own components; this points it at the ones the AI session chat already uses, so the two surfaces share a transcript, a composer and a sidebar instead of keeping two of each. The seam is `ChatViewHost` (copilot/chat/chatViewHost.ts): the view components read the host rather than `AIChatManager` directly, and `getChatViewHost()` falls back to the session manager, so the copilot's call sites are unchanged. `FlowChatViewHost` is the second adapter, over `FlowChatManager`. What the flow chat gains from the move: - turns running in several conversations at once, with a status, a queue and a Stop per chat, and an unread count on the rail - attachments, uploaded to the workspace's object storage for the worker to read - a composer that speaks for the agent steps a message is fed to: the model, the thinking effort, and the flow inputs the agent reads straight out of `flow_input`; everything else is asked for in a Configure-inputs modal - tool cards with the call and the result, the model's reasoning, and a step name per answer once a conversation holds more than one agent - Retry, which replays the failed turn's own run arguments read back from its job - named and renamable conversations, and test chats kept out of the deployed flow's list Backend: conversation rows carry an MCP tool's call and result and the model's reasoning, which live nowhere else; every row gets a job so retention can empty it; and the providers parse a non-streaming answer's reasoning. Stacked on #11134, which keeps the windmill-chat SDK for external frontends and raw apps while the in-app flow chat runs on these components. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
189 lines
4.6 KiB
TypeScript
189 lines
4.6 KiB
TypeScript
import type { Script, ScriptLang } from './gen'
|
|
|
|
export type OwnerKind = 'group' | 'user' | 'folder'
|
|
|
|
export type ActionKind = 'Create' | 'Update' | 'Delete' | 'Execute'
|
|
|
|
export type SupportedLanguage = Script['language']
|
|
|
|
export interface PropertyDisplayInfo {
|
|
property: SchemaProperty
|
|
name: string
|
|
isRequired: boolean
|
|
path: string[]
|
|
index: number
|
|
propertiesNumber: number
|
|
}
|
|
|
|
export type EnumType = string[] | { value: string; label: string }[] | undefined
|
|
|
|
export interface SchemaProperty {
|
|
type: string | undefined
|
|
description?: string
|
|
pattern?: string
|
|
default?: any
|
|
enum?: EnumType
|
|
contentEncoding?: 'base64' | 'binary'
|
|
format?: string
|
|
items?: {
|
|
type?: 'string' | 'number' | 'bytes' | 'object' | 'resource'
|
|
contentEncoding?: 'base64'
|
|
enum?: string[]
|
|
resourceType?: string
|
|
properties?: { [name: string]: SchemaProperty }
|
|
required?: string[]
|
|
}
|
|
min?: number
|
|
max?: number
|
|
/** Height a string field's text area opens at, in rows. */
|
|
minRows?: number
|
|
currency?: string
|
|
currencyLocale?: string
|
|
multiselect?: boolean
|
|
customErrorMessage?: string
|
|
properties?: { [name: string]: SchemaProperty }
|
|
required?: string[]
|
|
showExpr?: string
|
|
hideWhenChatEnabled?: boolean
|
|
/** Why the oneOf variant is chat mode's to pick. Set = selector disabled, reason shown. */
|
|
lockOneOfWhenChatEnabled?: string
|
|
password?: boolean
|
|
order?: string[]
|
|
nullable?: boolean
|
|
dateFormat?: string
|
|
title?: string
|
|
placeholder?: string
|
|
oneOf?: SchemaProperty[]
|
|
originalType?: string
|
|
disabled?: boolean
|
|
'x-no-s3-storage-workspace-warning'?: string
|
|
'x-auto-generate'?: boolean
|
|
}
|
|
|
|
export interface ModalSchemaProperty {
|
|
selectedType?: string
|
|
description: string
|
|
name: string
|
|
required: boolean
|
|
min?: number
|
|
max?: number
|
|
currency?: string
|
|
currencyLocale?: string
|
|
multiselect?: boolean
|
|
format?: string
|
|
pattern?: string
|
|
enum_?: EnumType
|
|
default?: any
|
|
items?: { type?: 'string' | 'number'; enum?: string[] }
|
|
contentEncoding?: 'base64' | 'binary'
|
|
schema?: Schema
|
|
customErrorMessage?: string
|
|
showExpr?: string
|
|
password?: boolean
|
|
nullable?: boolean
|
|
dateFormat?: string
|
|
title?: string
|
|
placeholder?: string
|
|
oneOf?: SchemaProperty[]
|
|
}
|
|
|
|
export function modalToSchema(schema: ModalSchemaProperty): SchemaProperty {
|
|
return {
|
|
type: schema.selectedType,
|
|
description: schema.description,
|
|
pattern: schema.pattern,
|
|
default: schema.default,
|
|
enum: schema.enum_,
|
|
items: schema.items,
|
|
contentEncoding: schema.contentEncoding,
|
|
format: schema.format,
|
|
customErrorMessage: schema.customErrorMessage,
|
|
properties: schema.schema?.properties,
|
|
required: schema.schema?.required,
|
|
min: schema.min,
|
|
max: schema.max,
|
|
currency: schema.currency,
|
|
currencyLocale: schema.currencyLocale,
|
|
multiselect: schema.multiselect,
|
|
showExpr: schema.showExpr,
|
|
password: schema.password,
|
|
nullable: schema.nullable,
|
|
dateFormat: schema.dateFormat,
|
|
title: schema.title,
|
|
placeholder: schema.placeholder,
|
|
oneOf: schema.oneOf
|
|
}
|
|
}
|
|
export type Schema = {
|
|
$schema: string | undefined
|
|
type: string
|
|
'x-windmill-dyn-select-code'?: string
|
|
'x-windmill-dyn-select-lang'?: ScriptLang
|
|
properties: { [name: string]: SchemaProperty }
|
|
order?: string[]
|
|
required: string[]
|
|
}
|
|
|
|
export function mergeSchema(
|
|
schema: Schema | Record<string, any>,
|
|
enum_payload: Record<string, any> = {}
|
|
) {
|
|
if (!schema.properties || !enum_payload) {
|
|
return schema
|
|
}
|
|
let new_schema: Schema = JSON.parse(JSON.stringify(schema))
|
|
for (let [key, value] of Object.entries(new_schema.properties ?? {})) {
|
|
if (enum_payload[key]) {
|
|
value.enum = enum_payload[key]
|
|
value['disableCreate'] = true
|
|
}
|
|
}
|
|
|
|
return new_schema
|
|
}
|
|
|
|
export type Meta = { ownerKind: OwnerKind; owner: string; name: string }
|
|
|
|
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
|
|
? Acc[number]
|
|
: Enumerate<N, [...Acc, Acc['length']]>
|
|
|
|
/** An inclusive range of integer numbers */
|
|
export type IntRange<F extends number, T extends number> =
|
|
| F
|
|
| Exclude<Enumerate<T>, Enumerate<F>>
|
|
| T
|
|
|
|
export function pathToMeta(path: string, hideUser: boolean): Meta {
|
|
const splitted = path.split('/')
|
|
let ownerKind: OwnerKind
|
|
if (splitted[0] == 'g') {
|
|
ownerKind = 'group'
|
|
} else if (splitted[0] == 'f') {
|
|
ownerKind = 'folder'
|
|
} else if (splitted[0] == 'u') {
|
|
ownerKind = 'user'
|
|
} else {
|
|
console.error('Not recognized owner:' + splitted[0])
|
|
return {
|
|
ownerKind: hideUser ? 'folder' : 'user',
|
|
owner: '',
|
|
name: ''
|
|
}
|
|
}
|
|
return {
|
|
ownerKind,
|
|
owner: splitted[1],
|
|
name: splitted.slice(2).join('/')
|
|
}
|
|
}
|
|
|
|
export function prettyLanguage(lang: string) {
|
|
switch (lang) {
|
|
case 'nativets':
|
|
return 'Native TypeScript'
|
|
default:
|
|
return lang.charAt(0).toUpperCase() + lang.slice(1)
|
|
}
|
|
}
|