mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
feat(flows): improve workflow builder UX and diagnostics (#4574)
This commit is contained in:
@@ -15,12 +15,16 @@ const dispatch = vi.hoisted(() => vi.fn());
|
||||
const selectorState = vi.hoisted(() => ({
|
||||
activeThreadIds: {} as Record<string, true>,
|
||||
proposals: {} as Record<string, WorkflowProposal>,
|
||||
messagesByThreadId: {} as Record<string, unknown[]>,
|
||||
}));
|
||||
vi.mock('../store/hooks', () => ({
|
||||
useAppDispatch: () => dispatch,
|
||||
useAppSelector: (sel: (s: unknown) => unknown) =>
|
||||
sel({
|
||||
thread: { activeThreadIds: selectorState.activeThreadIds },
|
||||
thread: {
|
||||
activeThreadIds: selectorState.activeThreadIds,
|
||||
messagesByThreadId: selectorState.messagesByThreadId,
|
||||
},
|
||||
chatRuntime: { pendingWorkflowProposalsByThread: selectorState.proposals },
|
||||
}),
|
||||
}));
|
||||
@@ -45,6 +49,7 @@ describe('useWorkflowBuilderChat', () => {
|
||||
chatSend.mockReset().mockResolvedValue(undefined);
|
||||
selectorState.activeThreadIds = {};
|
||||
selectorState.proposals = {};
|
||||
selectorState.messagesByThreadId = {};
|
||||
dispatch.mockReset().mockImplementation((action: { type: string }) => {
|
||||
if (action.type === 'createNewThread') {
|
||||
return { unwrap: () => Promise.resolve({ id: 'builder-1' }) };
|
||||
|
||||
@@ -54,6 +54,13 @@ export interface UseWorkflowBuilderChat {
|
||||
sending: boolean;
|
||||
/** The latest proposal the agent returned on this thread, or `null`. */
|
||||
proposal: WorkflowProposal | null;
|
||||
/**
|
||||
* The dedicated thread's transcript (user + agent turns) so a caller can
|
||||
* render the full conversation, not just the latest proposal. Empty until the
|
||||
* first send. Sourced from the same `messagesByThreadId` store the main chat
|
||||
* transcript reads.
|
||||
*/
|
||||
messages: ThreadMessage[];
|
||||
/** Last send error (thread create / RPC failure), or `null`. */
|
||||
error: string | null;
|
||||
/** Send a builder turn, creating the dedicated thread on first use. */
|
||||
@@ -62,6 +69,8 @@ export interface UseWorkflowBuilderChat {
|
||||
clearProposal: () => void;
|
||||
}
|
||||
|
||||
const EMPTY_MESSAGES: ThreadMessage[] = [];
|
||||
|
||||
/**
|
||||
* @param seedThreadId Optional existing thread to bind to instead of creating a
|
||||
* fresh one — lets a caller reuse a thread across mounts (unused today; the
|
||||
@@ -78,12 +87,18 @@ export function useWorkflowBuilderChat(seedThreadId?: string | null): UseWorkflo
|
||||
const proposalsByThread = useAppSelector(
|
||||
state => state.chatRuntime.pendingWorkflowProposalsByThread
|
||||
);
|
||||
const messagesByThreadId = useAppSelector(state => state.thread.messagesByThreadId);
|
||||
|
||||
const proposal = useMemo(
|
||||
() => (threadId ? (proposalsByThread[threadId] ?? null) : null),
|
||||
[threadId, proposalsByThread]
|
||||
);
|
||||
|
||||
const messages = useMemo(
|
||||
() => (threadId ? (messagesByThreadId[threadId] ?? EMPTY_MESSAGES) : EMPTY_MESSAGES),
|
||||
[threadId, messagesByThreadId]
|
||||
);
|
||||
|
||||
// "Sending" = we're mid-dispatch OR the runtime still marks the thread active.
|
||||
const runtimeActive = threadId ? Boolean(activeThreadIds[threadId]) : false;
|
||||
const sending = localSending || runtimeActive;
|
||||
@@ -157,5 +172,5 @@ export function useWorkflowBuilderChat(seedThreadId?: string | null): UseWorkflo
|
||||
if (threadId) dispatch(clearWorkflowProposalForThread({ threadId }));
|
||||
}, [dispatch, threadId]);
|
||||
|
||||
return { threadId, sending, proposal, error, send, clearProposal };
|
||||
return { threadId, sending, proposal, messages, error, send, clearProposal };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user