mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 16:02:59 +00:00
* feat: run turns in several flow chat conversations at once Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep finished turns finished and cached chats current in the flow chat pool Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: attribute a turn's rows by job id as well as sequence Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: count only real stream updates and retry the job-id read Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a chat that holds an unsent draft, and take one back when its first turn is withdrawn Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ignore a stale running-turn snapshot, keep a withdrawn chat's draft, poll after clean stream ends Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: follow the turn running now when the listing named one already over Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep replacement turns and SSE fallback moving * fix: keep replacement turn handoffs active * fix: preserve unread badge line height * fix: settle local fallback handoffs * fix: settle refused turn handoffs * fix: scope turn handoffs to conversation * fix: drop stale turn handoffs * refactor: move the queued message and 409 handling into per-conversation turns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address cubic's review of the parallel flow chat turns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: clear a stale failure on refresh, and tighten the docs and test waits Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: recover running rows past the first page, and drop the failure a re-read disproves Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: check the running-turn query at compile time, and narrow what a refresh clears Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: settle a failed turn only from an answer that turn wrote Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: settle a failed turn from its own answer, and only while it is still the failure shown Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: drop a failure whose answer arrived even when a newer turn owns the error Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: free an answered failure whatever the turn that started meanwhile is doing Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: drop a rows read that a turn outran, rather than merging it under newer messages Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: drop a rows read whose conversation was left and opened again Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hand over a file still being read when its composer goes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: count a drop's routing as work in flight, so its file is handed over too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hold the send until every file a conversation is owed has landed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: keep a panel mounted per conversation instead of handing its draft over Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the withdrawn chat whose composer was written in, not the empty one Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the chat in front of the reader when both withdrawn composers were written in Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a retry's own run arguments when a turn elsewhere refuses it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: name panels apart across pools, and read a flow's inputs when its chat is built Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
79 lines
3.2 KiB
TypeScript
79 lines
3.2 KiB
TypeScript
import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react'
|
|
import { createChat } from './chat'
|
|
import type { Chat, ChatOptions, ChatState } from './types'
|
|
|
|
export type UseWindmillChat = ChatState &
|
|
Pick<
|
|
Chat,
|
|
| 'sendMessage'
|
|
| 'resumeTurn'
|
|
| 'stop'
|
|
| 'newConversation'
|
|
| 'selectConversation'
|
|
| 'loadConversations'
|
|
| 'deleteConversation'
|
|
| 'renameConversation'
|
|
| 'loadOlderMessages'
|
|
| 'refreshMessages'
|
|
> & { chat: Chat }
|
|
|
|
/**
|
|
* A chat on a chat-mode flow. The chat is created once per `flowPath`, `baseUrl`,
|
|
* `workspace`, `history`, `storageKey`, credential and presence of `run`, and
|
|
* destroyed on unmount. A credential change is a new user, whose chat must not
|
|
* carry the previous one's state: a different token string, or a switch between no
|
|
* token, a token string and a token function, all recreate it. A token function is
|
|
* read through a ref on every call, so a new closure per render changes what the
|
|
* next call runs and nothing else; pass a `storageKey` per user when local history
|
|
* must not be shared. `run`, the callbacks and `inputs` are read the same way: the
|
|
* latest render's values go with the next message.
|
|
*/
|
|
export function useWindmillChat(options: ChatOptions): UseWindmillChat {
|
|
const latest = useRef(options)
|
|
latest.current = options
|
|
const credential =
|
|
typeof options.token === 'function' ? 'fn' : typeof options.token === 'string' ? `str:${options.token}` : 'none'
|
|
// A custom runner replaces the deployed flow call, so its presence is part of what the chat is.
|
|
const customRun = options.run !== undefined
|
|
const chat = useMemo(
|
|
() =>
|
|
createChat({
|
|
...options,
|
|
// Sent per message from the latest render instead, so a removed key stays removed.
|
|
inputs: undefined,
|
|
token:
|
|
typeof options.token === 'function'
|
|
? () => {
|
|
const token = latest.current.token
|
|
return typeof token === 'function' ? token() : (token ?? '')
|
|
}
|
|
: options.token,
|
|
run: customRun ? (args, turn) => (latest.current.run ?? options.run!)(args, turn) : undefined,
|
|
onFinish: (turn) => latest.current.onFinish?.(turn),
|
|
onError: (error, turn) => latest.current.onError?.(error, turn)
|
|
}),
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[options.flowPath, options.baseUrl, options.workspace, options.history, options.storageKey, credential, customRun]
|
|
)
|
|
useEffect(() => () => chat.destroy(), [chat])
|
|
const state = useSyncExternalStore(chat.subscribe, chat.getState, chat.getState)
|
|
return useMemo(
|
|
() => ({
|
|
...state,
|
|
chat,
|
|
sendMessage: (text, options) =>
|
|
chat.sendMessage(text, { ...options, inputs: { ...latest.current.inputs, ...options?.inputs } }),
|
|
resumeTurn: chat.resumeTurn,
|
|
stop: chat.stop,
|
|
newConversation: chat.newConversation,
|
|
selectConversation: chat.selectConversation,
|
|
loadConversations: chat.loadConversations,
|
|
deleteConversation: chat.deleteConversation,
|
|
renameConversation: chat.renameConversation,
|
|
loadOlderMessages: chat.loadOlderMessages,
|
|
refreshMessages: chat.refreshMessages
|
|
}),
|
|
[state, chat]
|
|
)
|
|
}
|