Skip to content

Commit a3126d7

Browse files
doc-hanelias-ba
andauthored
feat: allow assistant to work on unsaved jobs (#4416)
* feat: allow unsaved jobs in ai assistant * fix: conditions * chore: refactor * fix: remove @doc false from defp functions and rename is_job_chat? to job_chat? @doc false is unnecessary on private functions. Renamed predicate to follow Elixir convention and fix Credo lint failure. * fix: may_get_job * fix: close code editor when ai session is also closed * test: message processing tests * test: channel tests --------- Co-authored-by: Elias W. BA <eliaswalyba@gmail.com>
1 parent 9919348 commit a3126d7

10 files changed

Lines changed: 535 additions & 30 deletions

File tree

assets/js/collaborative-editor/components/AIAssistantPanelWrapper.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useRef, useEffect, useCallback } from 'react';
1+
import { useCallback, useEffect, useRef, useState } from 'react';
22

33
import { useURLState } from '../../react/lib/use-url-state';
44
import {
@@ -24,23 +24,23 @@ import { useAIWorkflowApplications } from '../hooks/useAIWorkflowApplications';
2424
import { useAutoPreview } from '../hooks/useAutoPreview';
2525
import { useResizablePanel } from '../hooks/useResizablePanel';
2626
import {
27-
useProject,
2827
useHasReadAIDisclaimer,
28+
useIsNewWorkflow,
29+
useLimits,
2930
useMarkAIDisclaimerRead,
31+
useProject,
3032
useSessionContextLoaded,
31-
useLimits,
32-
useIsNewWorkflow,
3333
useUser,
3434
} from '../hooks/useSessionContext';
3535
import {
36+
useAIAssistantInitialMessage,
3637
useIsAIAssistantPanelOpen,
3738
useUICommands,
38-
useAIAssistantInitialMessage,
3939
} from '../hooks/useUI';
4040
import {
41-
useWorkflowState,
4241
useWorkflowActions,
4342
useWorkflowReadOnly,
43+
useWorkflowState,
4444
} from '../hooks/useWorkflow';
4545
import { useKeyboardShortcut } from '../keyboard';
4646
import type { JobCodeContext } from '../types/ai-assistant';
@@ -51,7 +51,6 @@ import {
5151
} from '../utils/workflowSerialization';
5252

5353
import { AIAssistantPanel } from './AIAssistantPanel';
54-
import flowEvents from './diagram/react-flow-events';
5554
import { MessageList } from './MessageList';
5655

5756
/**
@@ -162,6 +161,7 @@ export function AIAssistantPanelWrapper({
162161
// URL synchronization hook - manages ?chat=true and session ID params
163162
const { sessionIdFromURL } = useAIPanelURLSync({
164163
isOpen: isAIAssistantPanelOpen,
164+
isNewWorkflow,
165165
sessionId,
166166
aiMode,
167167
aiStore,
@@ -414,7 +414,7 @@ export function AIAssistantPanelWrapper({
414414
}
415415
} else {
416416
// important: determines what ai to be used
417-
options = { ...options, job_id: aiMode?.context.job_id };
417+
options = { ...aiMode?.context, ...options };
418418
}
419419

420420
// Update store state and send through registry

assets/js/collaborative-editor/hooks/useAIPanelURLSync.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ import type { AIModeResult } from './useAIMode';
1717
*/
1818
export function useAIPanelURLSync({
1919
isOpen,
20+
isNewWorkflow,
2021
sessionId,
2122
aiMode,
2223
aiStore,
2324
updateSearchParams,
2425
params,
2526
}: {
2627
isOpen: boolean;
28+
isNewWorkflow: boolean;
2729
sessionId: string | null;
2830
aiMode: AIModeResult | null;
2931
aiStore: AIAssistantStoreInstance;
@@ -58,6 +60,13 @@ export function useAIPanelURLSync({
5860
// When closing, clear chat param and session params
5961
updateSearchParams({
6062
chat: null,
63+
...(isNewWorkflow
64+
? {
65+
job: null,
66+
trigger: null,
67+
edge: null,
68+
}
69+
: {}),
6170
'w-chat': null,
6271
'j-chat': null,
6372
});

assets/test/collaborative-editor/hooks/useAIPanelURLSync.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ describe('useAIPanelURLSync', () => {
5252
renderHook(() =>
5353
useAIPanelURLSync({
5454
isOpen: true,
55+
isNewWorkflow: false,
5556
sessionId: null,
5657
aiMode: createMockAIMode('workflow_template'),
5758
aiStore: createMockAIStore(),
@@ -69,6 +70,7 @@ describe('useAIPanelURLSync', () => {
6970
renderHook(() =>
7071
useAIPanelURLSync({
7172
isOpen: false,
73+
isNewWorkflow: false,
7274
sessionId: null,
7375
aiMode: createMockAIMode('workflow_template'),
7476
aiStore: createMockAIStore(),
@@ -89,6 +91,7 @@ describe('useAIPanelURLSync', () => {
8991
({ isOpen }) =>
9092
useAIPanelURLSync({
9193
isOpen,
94+
isNewWorkflow: false,
9295
sessionId: null,
9396
aiMode: createMockAIMode('workflow_template'),
9497
aiStore: createMockAIStore(),
@@ -122,6 +125,7 @@ describe('useAIPanelURLSync', () => {
122125
renderHook(() =>
123126
useAIPanelURLSync({
124127
isOpen: true,
128+
isNewWorkflow: false,
125129
sessionId: 'session-123',
126130
aiMode: createMockAIMode('workflow_template'),
127131
aiStore: createMockAIStore('workflow_template'),
@@ -138,6 +142,7 @@ describe('useAIPanelURLSync', () => {
138142
renderHook(() =>
139143
useAIPanelURLSync({
140144
isOpen: true,
145+
isNewWorkflow: false,
141146
sessionId: 'session-123',
142147
aiMode: createMockAIMode('workflow_template'),
143148
aiStore: createMockAIStore('workflow_template'),
@@ -158,6 +163,7 @@ describe('useAIPanelURLSync', () => {
158163
renderHook(() =>
159164
useAIPanelURLSync({
160165
isOpen: true,
166+
isNewWorkflow: false,
161167
sessionId: 'session-456',
162168
aiMode: createMockAIMode('job_code', { job_id: 'job-1' }),
163169
aiStore: createMockAIStore('job_code'),
@@ -174,6 +180,7 @@ describe('useAIPanelURLSync', () => {
174180
renderHook(() =>
175181
useAIPanelURLSync({
176182
isOpen: true,
183+
isNewWorkflow: false,
177184
sessionId: 'session-456',
178185
aiMode: createMockAIMode('job_code', { job_id: 'job-1' }),
179186
aiStore: createMockAIStore('job_code'),
@@ -194,6 +201,7 @@ describe('useAIPanelURLSync', () => {
194201
renderHook(() =>
195202
useAIPanelURLSync({
196203
isOpen: false,
204+
isNewWorkflow: false,
197205
sessionId: 'session-123',
198206
aiMode: createMockAIMode('workflow_template'),
199207
aiStore: createMockAIStore('workflow_template'),
@@ -218,6 +226,7 @@ describe('useAIPanelURLSync', () => {
218226
({ sessionId }) =>
219227
useAIPanelURLSync({
220228
isOpen: true,
229+
isNewWorkflow: false,
221230
sessionId,
222231
aiMode: createMockAIMode('workflow_template'),
223232
aiStore: createMockAIStore('workflow_template'),
@@ -244,6 +253,7 @@ describe('useAIPanelURLSync', () => {
244253
renderHook(() =>
245254
useAIPanelURLSync({
246255
isOpen: true,
256+
isNewWorkflow: false,
247257
sessionId: 'session-123',
248258
aiMode: null,
249259
aiStore: createMockAIStore('workflow_template'),
@@ -265,6 +275,7 @@ describe('useAIPanelURLSync', () => {
265275
({ sessionId }) =>
266276
useAIPanelURLSync({
267277
isOpen: true,
278+
isNewWorkflow: false,
268279
sessionId,
269280
aiMode: createMockAIMode('job_code', { job_id: 'job-1' }),
270281
aiStore: createMockAIStore('workflow_template'), // Mismatch
@@ -291,6 +302,7 @@ describe('useAIPanelURLSync', () => {
291302
const { result } = renderHook(() =>
292303
useAIPanelURLSync({
293304
isOpen: true,
305+
isNewWorkflow: false,
294306
sessionId: null,
295307
aiMode: createMockAIMode('workflow_template'),
296308
aiStore: createMockAIStore(),
@@ -306,6 +318,7 @@ describe('useAIPanelURLSync', () => {
306318
const { result } = renderHook(() =>
307319
useAIPanelURLSync({
308320
isOpen: true,
321+
isNewWorkflow: false,
309322
sessionId: null,
310323
aiMode: createMockAIMode('job_code', { job_id: 'job-1' }),
311324
aiStore: createMockAIStore('job_code'),
@@ -321,6 +334,7 @@ describe('useAIPanelURLSync', () => {
321334
const { result } = renderHook(() =>
322335
useAIPanelURLSync({
323336
isOpen: true,
337+
isNewWorkflow: false,
324338
sessionId: null,
325339
aiMode: null,
326340
aiStore: createMockAIStore(),
@@ -336,6 +350,7 @@ describe('useAIPanelURLSync', () => {
336350
const { result } = renderHook(() =>
337351
useAIPanelURLSync({
338352
isOpen: true,
353+
isNewWorkflow: false,
339354
sessionId: null,
340355
aiMode: createMockAIMode('workflow_template'),
341356
aiStore: createMockAIStore(),
@@ -352,6 +367,7 @@ describe('useAIPanelURLSync', () => {
352367
({ params }) =>
353368
useAIPanelURLSync({
354369
isOpen: true,
370+
isNewWorkflow: false,
355371
sessionId: null,
356372
aiMode: createMockAIMode('workflow_template'),
357373
aiStore: createMockAIStore(),
@@ -378,6 +394,7 @@ describe('useAIPanelURLSync', () => {
378394
({ aiMode, aiStore }) =>
379395
useAIPanelURLSync({
380396
isOpen: true,
397+
isNewWorkflow: false,
381398
sessionId: 'session-123',
382399
aiMode,
383400
aiStore,
@@ -415,6 +432,7 @@ describe('useAIPanelURLSync', () => {
415432
({ aiMode, aiStore }) =>
416433
useAIPanelURLSync({
417434
isOpen: true,
435+
isNewWorkflow: false,
418436
sessionId: 'session-456',
419437
aiMode,
420438
aiStore,
@@ -451,6 +469,7 @@ describe('useAIPanelURLSync', () => {
451469
const { result } = renderHook(() =>
452470
useAIPanelURLSync({
453471
isOpen: true,
472+
isNewWorkflow: false,
454473
sessionId: null,
455474
aiMode: createMockAIMode('workflow_template'),
456475
aiStore: createMockAIStore(),
@@ -466,6 +485,7 @@ describe('useAIPanelURLSync', () => {
466485
const { result } = renderHook(() =>
467486
useAIPanelURLSync({
468487
isOpen: true,
488+
isNewWorkflow: false,
469489
sessionId: null,
470490
aiMode: createMockAIMode('workflow_template'),
471491
aiStore: createMockAIStore(),
@@ -487,6 +507,7 @@ describe('useAIPanelURLSync', () => {
487507
renderHook(() =>
488508
useAIPanelURLSync({
489509
isOpen: true,
510+
isNewWorkflow: false,
490511
sessionId: 'session-123',
491512
aiMode: createMockAIMode('workflow_template'),
492513
aiStore: createMockAIStore('workflow_template'),
@@ -508,6 +529,7 @@ describe('useAIPanelURLSync', () => {
508529
({ sessionId }) =>
509530
useAIPanelURLSync({
510531
isOpen: true,
532+
isNewWorkflow: false,
511533
sessionId,
512534
aiMode: createMockAIMode('workflow_template'),
513535
aiStore: createMockAIStore('workflow_template'),

lib/lightning/ai_assistant/ai_assistant.ex

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,23 @@ defmodule Lightning.AiAssistant do
785785
|> Map.put("chat_session_id", session.id)
786786
|> Map.put("code", code)
787787
|> maybe_put_job_id_from_session(session)
788+
|> maybe_put_unsaved_job_meta(session)
789+
end
790+
791+
defp maybe_put_unsaved_job_meta(attrs, session) do
792+
is_assistant = to_string(Map.get(attrs, "role")) == "assistant"
793+
unsaved_job = get_in(session.meta || %{}, ["unsaved_job"])
794+
795+
if is_assistant && unsaved_job && unsaved_job["id"] do
796+
existing_meta = Map.get(attrs, "meta", %{})
797+
798+
updated_meta =
799+
Map.put(existing_meta, "from_unsaved_job", unsaved_job["id"])
800+
801+
Map.put(attrs, "meta", updated_meta)
802+
else
803+
attrs
804+
end
788805
end
789806

790807
defp update_session_meta(session, nil),

lib/lightning/ai_assistant/chat_message.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ defmodule Lightning.AiAssistant.ChatMessage do
1414
* `status` - Processing status: `:pending`, `:success`, `:error`, or `:cancelled`
1515
* `is_deleted` - Soft deletion flag (defaults to false)
1616
* `is_public` - Whether the message is publicly visible (defaults to true)
17+
* `meta` - Additional metadata (e.g., `"unsaved_job"` for job data not yet saved)
1718
* `chat_session_id` - Reference to the parent chat session
1819
* `user_id` - Reference to the user who sent the message (required for user messages)
1920
"""
@@ -36,6 +37,7 @@ defmodule Lightning.AiAssistant.ChatMessage do
3637
job_id: Ecto.UUID.t() | nil,
3738
is_deleted: boolean(),
3839
is_public: boolean(),
40+
meta: map() | nil,
3941
processing_started_at: DateTime.t() | nil,
4042
processing_completed_at: DateTime.t() | nil,
4143
chat_session_id: Ecto.UUID.t(),
@@ -54,6 +56,7 @@ defmodule Lightning.AiAssistant.ChatMessage do
5456

5557
field :is_deleted, :boolean, default: false
5658
field :is_public, :boolean, default: true
59+
field :meta, :map, default: %{}
5760
field :processing_started_at, :utc_datetime_usec
5861
field :processing_completed_at, :utc_datetime_usec
5962

@@ -89,6 +92,7 @@ defmodule Lightning.AiAssistant.ChatMessage do
8992
:status,
9093
:is_deleted,
9194
:is_public,
95+
:meta,
9296
:chat_session_id,
9397
:processing_started_at,
9498
:processing_completed_at

0 commit comments

Comments
 (0)