mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-26 08:01:38 +00:00
fix(flow chat): fix chat in edit mode + cleaner code (#7118)
* handle conversation for preview endpoints * rm * way better chat logic * remove old logic * no streaming in flow input * pass conv id to preview func * max width on input * add info * cleaning * nits * nits * use streaming in preview
This commit is contained in:
@@ -7994,6 +7994,12 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
requestBody:
|
||||
description: preview
|
||||
@@ -8020,6 +8026,12 @@ paths:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
requestBody:
|
||||
description: preview
|
||||
|
||||
@@ -6329,7 +6329,15 @@ async fn run_preview_flow_job(
|
||||
check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?;
|
||||
let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into());
|
||||
|
||||
let (uuid, tx) = push(
|
||||
let chat_input_enabled = raw_flow.value.chat_input_enabled.unwrap_or(false);
|
||||
let flow_path = raw_flow.path.clone().unwrap_or_default();
|
||||
let user_message = raw_flow
|
||||
.args
|
||||
.as_ref()
|
||||
.and_then(|args| args.get("user_message"))
|
||||
.cloned();
|
||||
|
||||
let (uuid, mut tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -6363,6 +6371,25 @@ async fn run_preview_flow_job(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Set memory_id if provided (for agent memory)
|
||||
if let Some(memory_id) = run_query.memory_id {
|
||||
set_flow_memory_id(&mut tx, uuid, memory_id).await?;
|
||||
}
|
||||
|
||||
// Handle conversation messages for chat-enabled flows
|
||||
if chat_input_enabled {
|
||||
handle_chat_conversation_messages(
|
||||
&mut tx,
|
||||
&authed,
|
||||
&w_id,
|
||||
&flow_path,
|
||||
&run_query,
|
||||
user_message.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok((StatusCode::CREATED, uuid.to_string()))
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
import { getStepHistoryLoaderContext } from './stepHistoryLoader.svelte'
|
||||
import { aiChatManager } from './copilot/chat/AIChatManager.svelte'
|
||||
import { stateSnapshot } from '$lib/svelte5Utils.svelte'
|
||||
import FlowChatInterface from './flows/conversations/FlowChatInterface.svelte'
|
||||
import { randomUUID } from './flows/conversations/FlowChatManager.svelte'
|
||||
import FlowChat from './flows/conversations/FlowChat.svelte'
|
||||
|
||||
interface Props {
|
||||
previewMode: 'upTo' | 'whole'
|
||||
@@ -95,9 +94,9 @@
|
||||
let suspendStatus: StateStore<Record<string, { job: Job; nb: number }>> = $state({ val: {} })
|
||||
let isOwner: boolean = $state(false)
|
||||
|
||||
export async function test(): Promise<string | undefined> {
|
||||
export async function test(conversationId?: string): Promise<string | undefined> {
|
||||
renderCount++
|
||||
return await runPreview(previewArgs.val, undefined)
|
||||
return await runPreview(previewArgs.val, undefined, conversationId)
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -120,6 +119,16 @@
|
||||
let flowProgressBar: FlowProgressBar | undefined = $state(undefined)
|
||||
let loadingHistory = $state(false)
|
||||
|
||||
let shouldUseStreaming = $derived.by(() => {
|
||||
const modules = flowStore.val.value?.modules
|
||||
const lastModule = modules && modules.length > 0 ? modules[modules.length - 1] : undefined
|
||||
return (
|
||||
lastModule?.value?.type === 'aiagent' &&
|
||||
lastModule?.value?.input_transforms?.streaming?.type === 'static' &&
|
||||
lastModule?.value?.input_transforms?.streaming?.value === true
|
||||
)
|
||||
})
|
||||
|
||||
function extractFlow(previewMode: 'upTo' | 'whole'): OpenFlow {
|
||||
const previewFlow = aiChatManager.flowAiChatHelpers?.getPreviewFlow()
|
||||
if (previewMode === 'whole') {
|
||||
@@ -139,7 +148,8 @@
|
||||
let lastPreviewFlow: undefined | string = $state(undefined)
|
||||
export async function runPreview(
|
||||
args: Record<string, any>,
|
||||
restartedFrom: RestartedFrom | undefined
|
||||
restartedFrom: RestartedFrom | undefined,
|
||||
conversationId?: string | undefined
|
||||
) {
|
||||
let newJobId: string | undefined = undefined
|
||||
if (stepHistoryLoader?.flowJobInitial !== false) {
|
||||
@@ -149,7 +159,7 @@
|
||||
lastPreviewFlow = JSON.stringify(flowStore.val)
|
||||
flowProgressBar?.reset()
|
||||
const newFlow = extractFlow(previewMode)
|
||||
newJobId = await runFlowPreview(args, newFlow, $pathStore, restartedFrom)
|
||||
newJobId = await runFlowPreview(args, newFlow, $pathStore, restartedFrom, conversationId)
|
||||
jobId = newJobId
|
||||
isRunning = true
|
||||
if (inputSelected) {
|
||||
@@ -464,15 +474,14 @@
|
||||
{#if render}
|
||||
{#if flowStore.val.value?.chat_input_enabled}
|
||||
<div class="flex flex-row justify-center w-full">
|
||||
<FlowChatInterface
|
||||
onRunFlow={async (userMessage, _conversationId) => {
|
||||
await runPreview({ user_message: userMessage }, undefined)
|
||||
<FlowChat
|
||||
useStreaming={shouldUseStreaming}
|
||||
onRunFlow={async (userMessage, conversationId) => {
|
||||
await runPreview({ user_message: userMessage }, undefined, conversationId)
|
||||
return jobId ?? ''
|
||||
}}
|
||||
createConversation={async () => {
|
||||
const newConversationId = randomUUID()
|
||||
return newConversationId
|
||||
}}
|
||||
hideSidebar={true}
|
||||
path={$pathStore}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
localModuleStates?: Record<string, GraphModuleState>
|
||||
testModuleStates?: ModulesTestStates
|
||||
isOwner?: boolean
|
||||
onTestFlow?: () => Promise<string | undefined>
|
||||
onTestFlow?: (conversationId?: string) => Promise<string | undefined>
|
||||
isRunning?: boolean
|
||||
onCancelTestFlow?: () => void
|
||||
onOpenPreview?: () => void
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
onDeployTrigger?: (trigger: Trigger) => void
|
||||
forceTestTab?: Record<string, boolean>
|
||||
highlightArg?: Record<string, string | undefined>
|
||||
onTestFlow?: () => Promise<string | undefined>
|
||||
onTestFlow?: (conversationId?: string) => Promise<string | undefined>
|
||||
job?: Job
|
||||
isOwner?: boolean
|
||||
suspendStatus?: StateStore<Record<string, { job: Job; nb: number }>>
|
||||
|
||||
@@ -44,17 +44,16 @@
|
||||
import { refreshStateStore } from '$lib/svelte5Utils.svelte'
|
||||
import type { ScriptLang } from '$lib/gen'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import FlowChatInterface from '$lib/components/flows/conversations/FlowChatInterface.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { AI_AGENT_SCHEMA } from '../flowInfers'
|
||||
import { nextId } from '../flowModuleNextId'
|
||||
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
||||
import { randomUUID } from '../conversations/FlowChatManager.svelte'
|
||||
import FlowChat from '../conversations/FlowChat.svelte'
|
||||
|
||||
interface Props {
|
||||
noEditor: boolean
|
||||
disabled: boolean
|
||||
onTestFlow?: () => Promise<string | undefined>
|
||||
onTestFlow?: (conversationId?: string) => Promise<string | undefined>
|
||||
previewOpen: boolean
|
||||
}
|
||||
|
||||
@@ -70,6 +69,15 @@
|
||||
} = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
let chatInputEnabled = $derived(Boolean(flowStore.val.value?.chat_input_enabled))
|
||||
let shouldUseStreaming = $derived.by(() => {
|
||||
const modules = flowStore.val.value?.modules
|
||||
const lastModule = modules && modules.length > 0 ? modules[modules.length - 1] : undefined
|
||||
return (
|
||||
lastModule?.value?.type === 'aiagent' &&
|
||||
lastModule?.value?.input_transforms?.streaming?.type === 'static' &&
|
||||
lastModule?.value?.input_transforms?.streaming?.value === true
|
||||
)
|
||||
})
|
||||
let showChatModeWarning = $state(false)
|
||||
|
||||
let addPropertyV2: AddPropertyV2 | undefined = $state(undefined)
|
||||
@@ -218,7 +226,7 @@
|
||||
}
|
||||
|
||||
async function runPreview() {
|
||||
await onTestFlow?.()
|
||||
await onTestFlow?.(undefined)
|
||||
}
|
||||
|
||||
function updatePreviewSchemaAndArgs(payload: any) {
|
||||
@@ -371,9 +379,12 @@
|
||||
firstStepInputs?.resetSelected(true)
|
||||
}
|
||||
|
||||
async function runFlowWithMessage(message: string): Promise<string | undefined> {
|
||||
async function runFlowWithMessage(
|
||||
message: string,
|
||||
conversationId: string
|
||||
): Promise<string | undefined> {
|
||||
previewArgs.val = { user_message: message }
|
||||
const jobId = await onTestFlow?.()
|
||||
const jobId = await onTestFlow?.(conversationId)
|
||||
return jobId
|
||||
}
|
||||
|
||||
@@ -488,13 +499,12 @@
|
||||
{#if !disabled}
|
||||
<div class="flex flex-col h-full">
|
||||
{#if flowStore.val.value?.chat_input_enabled}
|
||||
<div class="flex-1 min-h-0">
|
||||
<FlowChatInterface
|
||||
<div class="flex flex-col h-full">
|
||||
<FlowChat
|
||||
onRunFlow={runFlowWithMessage}
|
||||
createConversation={async () => {
|
||||
const newConversationId = randomUUID()
|
||||
return newConversationId
|
||||
}}
|
||||
path={$pathStore}
|
||||
hideSidebar={true}
|
||||
useStreaming={shouldUseStreaming}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { createFlowChatManager } from './FlowChatManager.svelte'
|
||||
import FlowConversationsSidebar from './FlowConversationsSidebar.svelte'
|
||||
import FlowChatInterface from './FlowChatInterface.svelte'
|
||||
import { untrack } from 'svelte'
|
||||
|
||||
interface Props {
|
||||
onRunFlow: (userMessage: string, conversationId: string) => Promise<string | undefined>
|
||||
useStreaming?: boolean
|
||||
deploymentInProgress?: boolean
|
||||
path: string
|
||||
hideSidebar?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
onRunFlow,
|
||||
deploymentInProgress = false,
|
||||
useStreaming = false,
|
||||
path,
|
||||
hideSidebar = false
|
||||
}: Props = $props()
|
||||
|
||||
const manager = createFlowChatManager()
|
||||
|
||||
// Initialize manager when component mounts
|
||||
$effect(() => {
|
||||
if ($workspaceStore) {
|
||||
manager.initialize(onRunFlow, path, useStreaming)
|
||||
}
|
||||
|
||||
return () => {
|
||||
manager.cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
// Initialize InfiniteList when component mounts or flowPath changes
|
||||
$effect(() => {
|
||||
if ($workspaceStore && path && manager.conversationListComponent) {
|
||||
untrack(() => {
|
||||
manager.setupInfiniteList()
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden flex-1">
|
||||
{#if !hideSidebar}
|
||||
<FlowConversationsSidebar {manager} />
|
||||
{/if}
|
||||
<FlowChatInterface {manager} {deploymentInProgress} />
|
||||
</div>
|
||||
@@ -1,75 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Button, Alert } from '$lib/components/common'
|
||||
import { MessageCircle, Loader2, ArrowUp, Square } from 'lucide-svelte'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import autosize from '$lib/autosize'
|
||||
import FlowChatMessage from './FlowChatMessage.svelte'
|
||||
import { createFlowChatManager } from './FlowChatManager.svelte'
|
||||
import { FlowChatManager } from './FlowChatManager.svelte'
|
||||
|
||||
interface Props {
|
||||
onRunFlow: (userMessage: string, conversationId: string) => Promise<string | undefined>
|
||||
useStreaming?: boolean
|
||||
refreshConversations?: () => Promise<void>
|
||||
conversationId?: string
|
||||
manager: FlowChatManager
|
||||
deploymentInProgress?: boolean
|
||||
createConversation: (options: { clearMessages?: boolean }) => Promise<string>
|
||||
path?: string
|
||||
}
|
||||
|
||||
let {
|
||||
onRunFlow,
|
||||
conversationId,
|
||||
refreshConversations,
|
||||
deploymentInProgress = false,
|
||||
createConversation,
|
||||
useStreaming = false,
|
||||
path
|
||||
}: Props = $props()
|
||||
|
||||
const manager = createFlowChatManager()
|
||||
|
||||
// Initialize manager when component mounts
|
||||
$effect(() => {
|
||||
if ($workspaceStore) {
|
||||
manager.initialize(
|
||||
{
|
||||
onRunFlow,
|
||||
createConversation,
|
||||
refreshConversations,
|
||||
conversationId,
|
||||
useStreaming,
|
||||
path
|
||||
},
|
||||
$workspaceStore
|
||||
)
|
||||
}
|
||||
|
||||
return () => {
|
||||
manager.cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
// Update conversation ID when it changes
|
||||
$effect(() => {
|
||||
manager.updateConversationId(conversationId)
|
||||
})
|
||||
|
||||
// Public API for parent components
|
||||
export function fillInputMessage(message: string) {
|
||||
manager.fillInputMessage(message)
|
||||
}
|
||||
|
||||
export function focusInput() {
|
||||
manager.focusInput()
|
||||
}
|
||||
|
||||
export function clearMessages() {
|
||||
manager.clearMessages()
|
||||
}
|
||||
|
||||
export async function loadConversationMessages(conversationId?: string) {
|
||||
await manager.loadConversationMessages(conversationId)
|
||||
}
|
||||
let { manager, deploymentInProgress = false }: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full flex-1 min-w-0">
|
||||
@@ -93,7 +34,7 @@
|
||||
<p class="text-sm">Send a message to run the flow and see the results</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-full xl:max-w-7xl mx-auto space-y-4">
|
||||
<div class="w-full space-y-4 xl:max-w-7xl mx-auto">
|
||||
{#each manager.messages as message (message.id)}
|
||||
<FlowChatMessage {message} />
|
||||
{/each}
|
||||
@@ -108,9 +49,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Chat Input -->
|
||||
<div class="p-2 bg-surface">
|
||||
<div class="flex flex-row justify-center py-2 xl:max-w-7xl mx-auto w-full">
|
||||
<div
|
||||
class="flex items-center gap-2 rounded-lg border border-gray-200 dark:border-gray-600 bg-surface"
|
||||
class="flex items-center gap-2 rounded-lg border border-gray-200 dark:border-gray-600 bg-surface-input w-full"
|
||||
class:opacity-50={deploymentInProgress}
|
||||
>
|
||||
<textarea
|
||||
@@ -119,10 +60,10 @@
|
||||
use:autosize
|
||||
onkeydown={manager.handleKeyDown}
|
||||
placeholder="Type your message here..."
|
||||
class="flex-1 min-h-[24px] max-h-32 resize-none !border-0 !bg-transparent text-sm placeholder-gray-400 !outline-none !ring-0 p-0 !shadow-none focus:!border-0 focus:!outline-none focus:!ring-0 focus:!shadow-none"
|
||||
class="flex-1 min-h-[24px] max-h-32 resize-none !border-0 text-sm placeholder-gray-400 !outline-none !ring-0 p-0 !shadow-none focus:!border-0 focus:!outline-none focus:!ring-0 focus:!shadow-none"
|
||||
rows={3}
|
||||
></textarea>
|
||||
<div class="flex-shrink-0 pr-2">
|
||||
<div class="flex-shrink-0 pr-2 bg-surface-input">
|
||||
{#if manager.isWaitingForResponse || manager.isLoading}
|
||||
<Button
|
||||
color="red"
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import type { FlowConversationMessage } from '$lib/gen/types.gen'
|
||||
import type { FlowConversation, FlowConversationMessage } from '$lib/gen/types.gen'
|
||||
import { FlowConversationService, JobService } from '$lib/gen'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { waitJob } from '$lib/components/waitJob'
|
||||
import { tick } from 'svelte'
|
||||
import InfiniteList from '$lib/components/InfiniteList.svelte'
|
||||
import { workspaceStore, userStore } from '$lib/stores'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
export interface ChatMessage extends FlowConversationMessage {
|
||||
loading?: boolean
|
||||
streaming?: boolean
|
||||
}
|
||||
|
||||
export interface FlowChatManagerOptions {
|
||||
onRunFlow: (userMessage: string, conversationId: string) => Promise<string | undefined>
|
||||
createConversation: (options: { clearMessages?: boolean }) => Promise<string>
|
||||
refreshConversations?: () => Promise<void>
|
||||
conversationId?: string
|
||||
useStreaming?: boolean
|
||||
path?: string
|
||||
export interface ConversationWithDraft extends FlowConversation {
|
||||
isDraft?: boolean
|
||||
}
|
||||
|
||||
export function randomUUID() {
|
||||
@@ -27,7 +25,7 @@ export function randomUUID() {
|
||||
})
|
||||
}
|
||||
|
||||
class FlowChatManager {
|
||||
export class FlowChatManager {
|
||||
// State
|
||||
messages = $state<ChatMessage[]>([])
|
||||
inputMessage = $state('')
|
||||
@@ -42,33 +40,34 @@ class FlowChatManager {
|
||||
currentEventSource = $state<EventSource | undefined>(undefined)
|
||||
pollingInterval = $state<ReturnType<typeof setInterval> | undefined>(undefined)
|
||||
currentJobId = $state<string | undefined>(undefined)
|
||||
conversations = $state<ConversationWithDraft[]>([])
|
||||
deletingConversationId = $state<string | undefined>(undefined)
|
||||
isSidebarExpanded = $state(false)
|
||||
selectedConversationId = $state<string | undefined>(undefined)
|
||||
conversationListComponent = $state<InfiniteList | undefined>(undefined)
|
||||
|
||||
// Private state
|
||||
#conversationsCache = $state<Record<string, ChatMessage[]>>({})
|
||||
#scrollTimeout: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
#perPage = 50
|
||||
#workspace = $state<string | undefined>(undefined)
|
||||
|
||||
// Options
|
||||
#onRunFlow?: FlowChatManagerOptions['onRunFlow']
|
||||
#createConversation?: FlowChatManagerOptions['createConversation']
|
||||
#refreshConversations?: FlowChatManagerOptions['refreshConversations']
|
||||
#conversationId = $state<string | undefined>(undefined)
|
||||
#onRunFlow?: (userMessage: string, conversationId: string) => Promise<string | undefined>
|
||||
#useStreaming = $state(false)
|
||||
#path = $state<string | undefined>(undefined)
|
||||
|
||||
initialize(options: FlowChatManagerOptions, workspace: string) {
|
||||
this.#onRunFlow = options.onRunFlow
|
||||
this.#createConversation = options.createConversation
|
||||
this.#refreshConversations = options.refreshConversations
|
||||
this.#conversationId = options.conversationId
|
||||
this.#useStreaming = options.useStreaming ?? false
|
||||
this.#path = options.path
|
||||
this.#workspace = workspace
|
||||
initialize(
|
||||
onRunFlow: (userMessage: string, conversationId: string) => Promise<string | undefined>,
|
||||
path: string,
|
||||
useStreaming: boolean = false
|
||||
) {
|
||||
this.#onRunFlow = onRunFlow
|
||||
this.#path = path
|
||||
this.#useStreaming = useStreaming
|
||||
}
|
||||
|
||||
updateConversationId(conversationId: string | undefined) {
|
||||
this.#conversationId = conversationId
|
||||
this.selectedConversationId = conversationId
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
@@ -97,15 +96,95 @@ class FlowChatManager {
|
||||
this.page = 1
|
||||
}
|
||||
|
||||
async createConversation({ clearMessages = true }: { clearMessages?: boolean }) {
|
||||
// Check if there's already a draft conversation
|
||||
const existingDraft = this.conversations.find((c) => c.isDraft)
|
||||
if (existingDraft) {
|
||||
// Select the existing draft instead of creating a new one
|
||||
this.selectedConversationId = existingDraft.id
|
||||
this.clearMessages()
|
||||
return existingDraft.id
|
||||
}
|
||||
const newConversationId = randomUUID()
|
||||
this.selectedConversationId = newConversationId
|
||||
|
||||
// Create a new conversation object and add it to the top of the list
|
||||
const newConversation: ConversationWithDraft = {
|
||||
id: newConversationId,
|
||||
workspace_id: get(workspaceStore)!,
|
||||
flow_path: this.#path!,
|
||||
title: 'New chat',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
created_by: get(userStore)!.username!,
|
||||
isDraft: true
|
||||
}
|
||||
|
||||
// Prepend to conversations list
|
||||
this.conversations = [newConversation, ...this.conversations]
|
||||
// Clear messages in the chat interface
|
||||
if (clearMessages) {
|
||||
this.clearMessages()
|
||||
}
|
||||
this.focusInput()
|
||||
|
||||
return newConversationId
|
||||
}
|
||||
|
||||
setupInfiniteList() {
|
||||
this.conversationListComponent?.setLoader((page, perPage) =>
|
||||
this.loadConversations(page, perPage)
|
||||
)
|
||||
this.conversationListComponent?.setDeleteItemFn((id) => this.deleteConversation(id))
|
||||
}
|
||||
|
||||
async selectConversation(conversationId: string, isDraft?: boolean) {
|
||||
this.selectedConversationId = conversationId
|
||||
// Load conversation messages into chat interface
|
||||
if (isDraft) {
|
||||
// For draft conversations, just clear messages (don't try to load from backend)
|
||||
this.clearMessages()
|
||||
} else {
|
||||
// For persisted conversations, load messages from backend
|
||||
await this.loadConversationMessages(conversationId)
|
||||
}
|
||||
}
|
||||
|
||||
async refreshConversations() {
|
||||
await this.conversationListComponent?.loadData('forceRefresh')
|
||||
}
|
||||
|
||||
// Only used by InfiniteList
|
||||
private async deleteConversation(conversationId: string) {
|
||||
try {
|
||||
this.deletingConversationId = conversationId
|
||||
await FlowConversationService.deleteFlowConversation({
|
||||
workspace: get(workspaceStore)!,
|
||||
conversationId
|
||||
})
|
||||
if (this.selectedConversationId === conversationId) {
|
||||
this.selectedConversationId = undefined
|
||||
this.clearMessages()
|
||||
}
|
||||
sendUserToast('Conversation deleted successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to delete conversation:', error)
|
||||
sendUserToast('Failed to delete conversation', true)
|
||||
throw error
|
||||
} finally {
|
||||
this.deletingConversationId = undefined
|
||||
}
|
||||
}
|
||||
|
||||
async cancelCurrentJob() {
|
||||
if (!this.#workspace) {
|
||||
if (!get(workspaceStore)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.currentJobId) {
|
||||
await JobService.cancelQueuedJob({
|
||||
workspace: this.#workspace,
|
||||
workspace: get(workspaceStore)!,
|
||||
id: this.currentJobId,
|
||||
requestBody: {}
|
||||
})
|
||||
@@ -124,10 +203,30 @@ class FlowChatManager {
|
||||
await this.loadMessages(true, conversationId)
|
||||
}
|
||||
|
||||
// Only used by InfiniteList
|
||||
private async loadConversations(page: number, perPage: number) {
|
||||
if (!get(workspaceStore) || !this.#path) return []
|
||||
|
||||
try {
|
||||
const response = await FlowConversationService.listFlowConversations({
|
||||
workspace: get(workspaceStore)!,
|
||||
flowPath: this.#path,
|
||||
page: page,
|
||||
perPage: perPage
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to load conversations:', error)
|
||||
sendUserToast('Failed to load conversations', true)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Message loading
|
||||
private async loadMessages(reset: boolean, conversationId?: string) {
|
||||
let conversationIdToUse = conversationId ?? this.#conversationId
|
||||
if (!this.#workspace || !conversationIdToUse) return
|
||||
let conversationIdToUse = conversationId ?? this.selectedConversationId
|
||||
if (!get(workspaceStore) || !conversationIdToUse) return
|
||||
|
||||
if (reset) {
|
||||
if (this.#conversationsCache[conversationIdToUse]) {
|
||||
@@ -145,7 +244,7 @@ class FlowChatManager {
|
||||
const previousScrollHeight = this.messagesContainer?.scrollHeight || 0
|
||||
|
||||
const response = await FlowConversationService.listConversationMessages({
|
||||
workspace: this.#workspace,
|
||||
workspace: get(workspaceStore)!,
|
||||
conversationId: conversationIdToUse,
|
||||
page: pageToFetch,
|
||||
perPage: this.#perPage
|
||||
@@ -213,8 +312,8 @@ class FlowChatManager {
|
||||
} finally {
|
||||
// Do a final poll to get all messages from database
|
||||
try {
|
||||
if (this.#conversationId) {
|
||||
await this.pollConversationMessages(this.#conversationId)
|
||||
if (this.selectedConversationId) {
|
||||
await this.pollConversationMessages(this.selectedConversationId)
|
||||
}
|
||||
} catch {}
|
||||
this.cleanup()
|
||||
@@ -252,12 +351,12 @@ class FlowChatManager {
|
||||
}
|
||||
|
||||
private async pollConversationMessages(conversationId: string, isNewConversation?: boolean) {
|
||||
if (!this.#workspace) return
|
||||
if (!get(workspaceStore)) return
|
||||
|
||||
try {
|
||||
const lastId = this.messages[this.messages.length - 1].id
|
||||
const response = await FlowConversationService.listConversationMessages({
|
||||
workspace: this.#workspace,
|
||||
workspace: get(workspaceStore)!,
|
||||
conversationId: conversationId,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
@@ -265,7 +364,7 @@ class FlowChatManager {
|
||||
})
|
||||
|
||||
if (isNewConversation) {
|
||||
await this.#refreshConversations?.()
|
||||
await this.refreshConversations()
|
||||
}
|
||||
|
||||
const filteredResponse = response.filter((msg) => msg.message_type !== 'user')
|
||||
@@ -314,9 +413,9 @@ class FlowChatManager {
|
||||
this.stopPolling()
|
||||
|
||||
// Generate a new conversation ID if we don't have one
|
||||
let currentConversationId = this.#conversationId
|
||||
if (!this.#conversationId && this.#createConversation) {
|
||||
const newConversationId = await this.#createConversation({ clearMessages: false })
|
||||
let currentConversationId = this.selectedConversationId
|
||||
if (!this.selectedConversationId) {
|
||||
const newConversationId = await this.createConversation({ clearMessages: false })
|
||||
currentConversationId = newConversationId
|
||||
}
|
||||
|
||||
@@ -380,16 +479,14 @@ class FlowChatManager {
|
||||
let isCompleted = false
|
||||
|
||||
try {
|
||||
const jobId = await JobService.runFlowByPath({
|
||||
workspace: this.#workspace!,
|
||||
path: this.#path!,
|
||||
requestBody: { user_message: messageContent },
|
||||
memoryId: currentConversationId
|
||||
})
|
||||
// Encode the payload as base64
|
||||
const jobId = await this.#onRunFlow?.(messageContent, currentConversationId)
|
||||
if (!jobId) {
|
||||
console.error('No jobId returned from onRunFlow')
|
||||
return
|
||||
}
|
||||
|
||||
// Build the EventSource URL
|
||||
const streamUrl = `/api/w/${this.#workspace}/jobs_u/getupdate_sse/${jobId}`
|
||||
const streamUrl = `/api/w/${get(workspaceStore)}/jobs_u/getupdate_sse/${jobId}`
|
||||
const url = new URL(streamUrl, window.location.origin)
|
||||
url.searchParams.set('poll_delay_ms', '50')
|
||||
url.searchParams.set('fast', 'true')
|
||||
@@ -479,8 +576,8 @@ class FlowChatManager {
|
||||
if (data.completed) {
|
||||
isCompleted = true
|
||||
// Do a final poll to get all messages from database
|
||||
if (this.#conversationId) {
|
||||
await this.pollConversationMessages(this.#conversationId)
|
||||
if (this.selectedConversationId) {
|
||||
await this.pollConversationMessages(this.selectedConversationId)
|
||||
}
|
||||
this.cleanup()
|
||||
}
|
||||
@@ -518,7 +615,7 @@ class FlowChatManager {
|
||||
this.currentJobId = jobId
|
||||
|
||||
if (isNewConversation) {
|
||||
await this.#refreshConversations?.()
|
||||
await this.refreshConversations()
|
||||
}
|
||||
|
||||
// Start polling for intermediate messages in non-streaming mode too
|
||||
|
||||
@@ -8,126 +8,25 @@
|
||||
PanelLeftOpen,
|
||||
Loader2
|
||||
} from 'lucide-svelte'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { FlowConversationService, type FlowConversation } from '$lib/gen'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { type FlowConversation } from '$lib/gen'
|
||||
import CountBadge from '$lib/components/common/badge/CountBadge.svelte'
|
||||
import InfiniteList from '$lib/components/InfiniteList.svelte'
|
||||
import { untrack } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { FlowChatManager } from './FlowChatManager.svelte'
|
||||
|
||||
interface Props {
|
||||
flowPath: string
|
||||
selectedConversationId?: string
|
||||
onNewConversation: (options: { clearMessages?: boolean }) => void
|
||||
onSelectConversation: (conversationId: string, isDraft?: boolean) => void
|
||||
onDeleteConversation: (conversationId: string) => void
|
||||
manager: FlowChatManager
|
||||
}
|
||||
|
||||
interface ConversationWithDraft extends FlowConversation {
|
||||
isDraft?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
flowPath,
|
||||
selectedConversationId,
|
||||
onNewConversation,
|
||||
onSelectConversation,
|
||||
onDeleteConversation
|
||||
}: Props = $props()
|
||||
|
||||
let isExpanded = $state(false)
|
||||
let infiniteList: InfiniteList | undefined = $state()
|
||||
let conversations = $state<ConversationWithDraft[]>([])
|
||||
let deletingConversationId = $state<string | undefined>(undefined)
|
||||
|
||||
export async function refreshConversations() {
|
||||
return await infiniteList?.loadData('forceRefresh')
|
||||
}
|
||||
|
||||
export async function addNewConversation(conversationId: string, username: string) {
|
||||
// Check if there's already a draft conversation
|
||||
const existingDraft = conversations.find((c) => c.isDraft)
|
||||
if (existingDraft) {
|
||||
// Select the existing draft instead of creating a new one
|
||||
onSelectConversation(existingDraft.id, true)
|
||||
return existingDraft.id
|
||||
}
|
||||
|
||||
// Create a new conversation object and add it to the top of the list
|
||||
const newConversation: ConversationWithDraft = {
|
||||
id: conversationId,
|
||||
workspace_id: $workspaceStore!,
|
||||
flow_path: flowPath,
|
||||
title: 'New chat',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
created_by: username,
|
||||
isDraft: true
|
||||
}
|
||||
|
||||
// Prepend to conversations list
|
||||
conversations = [newConversation, ...conversations]
|
||||
return conversationId
|
||||
}
|
||||
|
||||
async function loadConversations(page: number, perPage: number) {
|
||||
if (!$workspaceStore || !flowPath) return []
|
||||
|
||||
try {
|
||||
const response = await FlowConversationService.listFlowConversations({
|
||||
workspace: $workspaceStore,
|
||||
flowPath: flowPath,
|
||||
page: page,
|
||||
perPage: perPage
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Failed to load conversations:', error)
|
||||
sendUserToast('Failed to load conversations', true)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteConversation(conversationId: string) {
|
||||
try {
|
||||
deletingConversationId = conversationId
|
||||
await FlowConversationService.deleteFlowConversation({
|
||||
workspace: $workspaceStore!,
|
||||
conversationId
|
||||
})
|
||||
|
||||
onDeleteConversation(conversationId)
|
||||
sendUserToast('Conversation deleted successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to delete conversation:', error)
|
||||
sendUserToast('Failed to delete conversation', true)
|
||||
throw error
|
||||
} finally {
|
||||
deletingConversationId = undefined
|
||||
}
|
||||
}
|
||||
let { manager }: Props = $props()
|
||||
|
||||
function getConversationTitle(conversation: FlowConversation): string {
|
||||
return conversation.title || `Conversation ${conversation.created_at.slice(0, 10)}`
|
||||
}
|
||||
|
||||
// Initialize InfiniteList when component mounts or flowPath changes
|
||||
$effect(() => {
|
||||
if ($workspaceStore && flowPath) {
|
||||
if (infiniteList) {
|
||||
untrack(() => {
|
||||
infiniteList?.setLoader(loadConversations)
|
||||
infiniteList?.setDeleteItemFn(deleteConversation)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex flex-col h-full bg-surface border-r border-gray-200 dark:border-gray-700 transition-all duration-300 {isExpanded
|
||||
class="flex flex-col h-full bg-surface border-r border-gray-200 dark:border-gray-700 transition-all duration-300 {manager.isSidebarExpanded
|
||||
? 'w-60'
|
||||
: 'w-16'}"
|
||||
>
|
||||
@@ -137,10 +36,10 @@
|
||||
<Button
|
||||
size="sm"
|
||||
color="light"
|
||||
startIcon={{ icon: isExpanded ? PanelLeftClose : PanelLeftOpen }}
|
||||
onclick={() => (isExpanded = !isExpanded)}
|
||||
iconOnly={!isExpanded}
|
||||
btnClasses="!justify-start"
|
||||
startIcon={{ icon: manager.isSidebarExpanded ? PanelLeftClose : PanelLeftOpen }}
|
||||
onclick={() => (manager.isSidebarExpanded = !manager.isSidebarExpanded)}
|
||||
iconOnly={!manager.isSidebarExpanded}
|
||||
btnClasses={manager.isSidebarExpanded ? '!justify-start' : ''}
|
||||
label="Conversations"
|
||||
>
|
||||
Conversations
|
||||
@@ -149,10 +48,10 @@
|
||||
size="sm"
|
||||
color="light"
|
||||
startIcon={{ icon: Plus }}
|
||||
onclick={() => onNewConversation({ clearMessages: true })}
|
||||
onclick={() => manager.createConversation({ clearMessages: true })}
|
||||
title="Start new conversation"
|
||||
iconOnly={!isExpanded}
|
||||
btnClasses="!justify-start"
|
||||
iconOnly={!manager.isSidebarExpanded}
|
||||
btnClasses={manager.isSidebarExpanded ? '!justify-start' : ''}
|
||||
label="New chat"
|
||||
>
|
||||
New chat
|
||||
@@ -161,26 +60,28 @@
|
||||
</div>
|
||||
|
||||
<!-- Conversations List -->
|
||||
{#if !isExpanded}
|
||||
{#if !manager.isSidebarExpanded}
|
||||
<!-- Collapsed state - show single chat icon with badge -->
|
||||
<div class="p-2 flex flex-col items-center mt-2">
|
||||
<button
|
||||
class="relative w-[23px] h-[23px] rounded-md center-center hover:bg-surface-hover transition-all duration-100 text-secondary hover:text-primary group"
|
||||
onclick={() => (isExpanded = true)}
|
||||
title="{conversations.length} conversation{conversations.length !== 1 ? 's' : ''}"
|
||||
onclick={() => (manager.isSidebarExpanded = true)}
|
||||
title="{manager.conversations.length} conversation{manager.conversations.length !== 1
|
||||
? 's'
|
||||
: ''}"
|
||||
>
|
||||
<MessageCircle size={16} />
|
||||
<CountBadge count={conversations.length} small={true} alwaysVisible={true} />
|
||||
<CountBadge count={manager.conversations.length} small={true} alwaysVisible={true} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Always mount InfiniteList, but hide it when collapsed -->
|
||||
<div class="flex-1 overflow-hidden" class:hidden={!isExpanded}>
|
||||
<div class="flex-1 overflow-hidden" class:hidden={!manager.isSidebarExpanded}>
|
||||
<InfiniteList
|
||||
bind:this={infiniteList}
|
||||
bind:items={conversations}
|
||||
selectedItemId={selectedConversationId}
|
||||
bind:this={manager.conversationListComponent}
|
||||
bind:items={manager.conversations}
|
||||
selectedItemId={manager.selectedConversationId}
|
||||
noBorder={true}
|
||||
rounded={false}
|
||||
>
|
||||
@@ -188,7 +89,7 @@
|
||||
<div
|
||||
class={twMerge(
|
||||
'w-full p-1',
|
||||
selectedConversationId === conversation.id
|
||||
manager.selectedConversationId === conversation.id
|
||||
? 'bg-blue-200/30 text-blue-500 dark:bg-blue-600/30 text-blue-400'
|
||||
: ''
|
||||
)}
|
||||
@@ -196,30 +97,29 @@
|
||||
<Button
|
||||
color="transparent"
|
||||
size="xs"
|
||||
onclick={() => onSelectConversation(conversation.id, conversation.isDraft)}
|
||||
onclick={() => manager.selectConversation(conversation.id, conversation.isDraft)}
|
||||
>
|
||||
<span class="flex-1 text-left text-sm font-medium text-primary truncate">
|
||||
{getConversationTitle(conversation)}
|
||||
</span>
|
||||
<button
|
||||
class="ml-2 p-1 rounded hover:bg-red-100 dark:hover:bg-red-900/30 text-red-500 transition-all {hover ||
|
||||
deletingConversationId === conversation.id
|
||||
manager.deletingConversationId === conversation.id
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'}"
|
||||
disabled={deletingConversationId === conversation.id}
|
||||
disabled={manager.deletingConversationId === conversation.id}
|
||||
onclick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (conversation.isDraft) {
|
||||
// just remove first conversation as it is the draft
|
||||
conversations = [...conversations.slice(1)]
|
||||
onDeleteConversation(conversation.id)
|
||||
manager.conversations = [...manager.conversations.slice(1)]
|
||||
} else {
|
||||
infiniteList?.deleteItem(conversation.id)
|
||||
manager.conversationListComponent?.deleteItem(conversation.id)
|
||||
}
|
||||
}}
|
||||
title="Delete conversation"
|
||||
>
|
||||
{#if deletingConversationId === conversation.id}
|
||||
{#if manager.deletingConversationId === conversation.id}
|
||||
<Loader2 size={14} class="animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={14} />
|
||||
@@ -237,11 +137,11 @@
|
||||
</InfiniteList>
|
||||
</div>
|
||||
|
||||
{#if isExpanded}
|
||||
{#if manager.isSidebarExpanded}
|
||||
<!-- Footer -->
|
||||
<div class="flex-shrink-0 p-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<p class="text-xs text-primary">
|
||||
{conversations.length} conversation{conversations.length !== 1 ? 's' : ''}
|
||||
{manager.conversations.length} conversation{manager.conversations.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -50,14 +50,14 @@
|
||||
flowPreviewContent?.test()
|
||||
}
|
||||
|
||||
export async function runPreview(): Promise<string | undefined> {
|
||||
export async function runPreview(conversationId?: string): Promise<string | undefined> {
|
||||
if (!previewOpen) {
|
||||
deferContent = true
|
||||
await tick()
|
||||
}
|
||||
previewMode = 'whole'
|
||||
flowPreviewContent?.refresh()
|
||||
return await flowPreviewContent?.test()
|
||||
return await flowPreviewContent?.test(conversationId)
|
||||
}
|
||||
|
||||
export function cancelTest() {
|
||||
|
||||
@@ -154,7 +154,8 @@ export async function runFlowPreview(
|
||||
args: Record<string, any>,
|
||||
flow: OpenFlow & { tag?: string },
|
||||
path: string,
|
||||
restartedFrom: RestartedFrom | undefined
|
||||
restartedFrom: RestartedFrom | undefined,
|
||||
conversationId?: string | undefined
|
||||
) {
|
||||
const newFlow = flow
|
||||
return await JobService.runFlowPreview({
|
||||
@@ -165,7 +166,8 @@ export async function runFlowPreview(
|
||||
path: path,
|
||||
tag: newFlow.tag,
|
||||
restarted_from: restartedFrom
|
||||
}
|
||||
},
|
||||
memoryId: conversationId
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
import { Badge as HeaderBadge, Alert } from '$lib/components/common'
|
||||
import MoveDrawer from '$lib/components/MoveDrawer.svelte'
|
||||
import RunForm from '$lib/components/RunForm.svelte'
|
||||
import FlowChatInterface from '$lib/components/flows/conversations/FlowChatInterface.svelte'
|
||||
import FlowConversationsSidebar from '$lib/components/flows/conversations/FlowConversationsSidebar.svelte'
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
@@ -65,7 +63,7 @@
|
||||
initFlowGraphAssetsCtx
|
||||
} from '$lib/components/flows/FlowAssetsHandler.svelte'
|
||||
import { page } from '$app/state'
|
||||
import { randomUUID } from '$lib/components/flows/conversations/FlowChatManager.svelte'
|
||||
import FlowChat from '$lib/components/flows/conversations/FlowChat.svelte'
|
||||
|
||||
let flow: Flow | undefined = $state()
|
||||
let can_write = false
|
||||
@@ -393,68 +391,11 @@
|
||||
}
|
||||
}
|
||||
let stepDetail: FlowModule | string | undefined = $state(undefined)
|
||||
let flowChatInterface: FlowChatInterface | undefined = $state(undefined)
|
||||
let flowConversationsSidebar: FlowConversationsSidebar | undefined = $state(undefined)
|
||||
let rightPaneSelected = $state('saved_inputs')
|
||||
let savedInputsV2: SavedInputsV2 | undefined = $state(undefined)
|
||||
let flowHistory: FlowHistory | undefined = $state(undefined)
|
||||
let selectedConversationId: string | undefined = $state(undefined)
|
||||
let path = $derived(page.params.path ?? '')
|
||||
|
||||
async function handleNewConversation({ clearMessages = true }: { clearMessages?: boolean }) {
|
||||
const newConversationId = randomUUID()
|
||||
|
||||
// Add the new conversation to the sidebar (returns id of draft or new conversation)
|
||||
if (flowConversationsSidebar) {
|
||||
const actualConversationId = await flowConversationsSidebar.addNewConversation(
|
||||
newConversationId,
|
||||
$userStore?.username || 'anonymous'
|
||||
)
|
||||
selectedConversationId = actualConversationId
|
||||
} else {
|
||||
selectedConversationId = newConversationId
|
||||
}
|
||||
|
||||
// Clear messages in the chat interface
|
||||
if (flowChatInterface && clearMessages) {
|
||||
flowChatInterface.clearMessages()
|
||||
}
|
||||
|
||||
flowChatInterface?.focusInput()
|
||||
|
||||
return newConversationId
|
||||
}
|
||||
|
||||
async function handleSelectConversation(conversationId: string, isDraft?: boolean) {
|
||||
selectedConversationId = conversationId
|
||||
// Load conversation messages into chat interface
|
||||
if (flowChatInterface) {
|
||||
if (isDraft) {
|
||||
// For draft conversations, just clear messages (don't try to load from backend)
|
||||
flowChatInterface.clearMessages()
|
||||
} else {
|
||||
// For persisted conversations, load messages from backend
|
||||
await flowChatInterface.loadConversationMessages(conversationId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshConversations() {
|
||||
if (flowConversationsSidebar) {
|
||||
await flowConversationsSidebar.refreshConversations()
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeleteConversation(conversationId: string) {
|
||||
if (selectedConversationId === conversationId) {
|
||||
selectedConversationId = undefined
|
||||
// Clear chat interface since we deleted the selected conversation
|
||||
if (flowChatInterface) {
|
||||
flowChatInterface.clearMessages()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
const cliTrigger = triggersState.triggers.find((t) => t.type === 'cli')
|
||||
if (cliTrigger) {
|
||||
@@ -612,28 +553,12 @@
|
||||
|
||||
{#if chatInputEnabled}
|
||||
<!-- Chat Layout with Sidebar -->
|
||||
<div
|
||||
class="flex border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden flex-1"
|
||||
>
|
||||
<FlowConversationsSidebar
|
||||
bind:this={flowConversationsSidebar}
|
||||
flowPath={flow?.path ?? ''}
|
||||
{selectedConversationId}
|
||||
onNewConversation={handleNewConversation}
|
||||
onSelectConversation={handleSelectConversation}
|
||||
onDeleteConversation={handleDeleteConversation}
|
||||
/>
|
||||
<FlowChatInterface
|
||||
bind:this={flowChatInterface}
|
||||
onRunFlow={runFlowForChat}
|
||||
useStreaming={shouldUseStreaming}
|
||||
{refreshConversations}
|
||||
conversationId={selectedConversationId}
|
||||
{deploymentInProgress}
|
||||
createConversation={handleNewConversation}
|
||||
{path}
|
||||
/>
|
||||
</div>
|
||||
<FlowChat
|
||||
onRunFlow={runFlowForChat}
|
||||
{deploymentInProgress}
|
||||
path={flow?.path ?? ''}
|
||||
useStreaming={shouldUseStreaming}
|
||||
/>
|
||||
{:else}
|
||||
<!-- Normal Mode: Form Layout -->
|
||||
<div class="flex flex-col align-left">
|
||||
@@ -733,10 +658,6 @@
|
||||
args={args ?? {}}
|
||||
bind:inputSelected
|
||||
on:selected_args={(e) => {
|
||||
if (chatInputEnabled) {
|
||||
flowChatInterface?.fillInputMessage(e.detail.user_message)
|
||||
return
|
||||
}
|
||||
const nargs = JSON.parse(JSON.stringify(e.detail))
|
||||
args = nargs
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user