Skip to content

Commit f30ca39

Browse files
fix #3605 (#3618)
* fix #3605 * tests * cleaner diff * format * update existing tests --------- Co-authored-by: Mercy <mercy@openfn.org>
1 parent 51a9908 commit f30ca39

File tree

6 files changed

+97
-7
lines changed

6 files changed

+97
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ and this project adheres to
6565

6666
### Fixed
6767

68+
- Fix canvas "lockup" after AI chat errors, prevent sending empty message to AI
69+
[3605](https://github.com/OpenFn/lightning/issues/3605)
6870
- Fix memory bloat on dataclip viewer in dataclip detail page
6971
[#3641](https://github.com/OpenFn/lightning/issues/3641)
7072
- Ameliorate memory usage when scrubbing dataclips for security

lib/lightning_web/live/ai_assistant/component.ex

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ defmodule LightningWeb.AiAssistant.Component do
250250
socket
251251
) do
252252
cleared_params = Map.put(params, "content", nil)
253+
trimmed_content = if is_binary(content), do: String.trim(content), else: ""
253254

254255
cond do
255256
not socket.assigns.can_edit ->
@@ -263,6 +264,23 @@ defmodule LightningWeb.AiAssistant.Component do
263264
socket.assigns.ai_limit_result != :ok ->
264265
{:noreply, socket}
265266

267+
trimmed_content == "" ->
268+
changeset = socket.assigns.handler.validate_form(%{"content" => ""})
269+
270+
changeset =
271+
Ecto.Changeset.add_error(
272+
changeset,
273+
:content,
274+
"Please enter a message before sending"
275+
)
276+
277+
{:noreply,
278+
socket
279+
|> assign(
280+
changeset: changeset,
281+
alert: "Please enter a message before sending"
282+
)}
283+
266284
true ->
267285
{:noreply,
268286
socket
@@ -272,7 +290,7 @@ defmodule LightningWeb.AiAssistant.Component do
272290
:changeset,
273291
socket.assigns.handler.validate_form(cleared_params)
274292
)
275-
|> save_message(socket.assigns.action, content)}
293+
|> save_message(socket.assigns.action, trimmed_content)}
276294
end
277295
end
278296

@@ -447,7 +465,9 @@ defmodule LightningWeb.AiAssistant.Component do
447465
end
448466

449467
defp handle_save_error(socket, error) do
450-
assign(socket, alert: socket.assigns.handler.error_message(error))
468+
socket
469+
|> assign(alert: socket.assigns.handler.error_message(error))
470+
|> assign(pending_message: AsyncResult.ok(nil))
451471
end
452472

453473
defp redirect_url(base_url, query_params) do
@@ -679,11 +699,11 @@ defmodule LightningWeb.AiAssistant.Component do
679699
<.simple_button_with_tooltip
680700
id={"ai-assistant-form-submit-btn-#{@id}"}
681701
type="submit"
682-
disabled={@disabled}
702+
disabled={@disabled || form_content_empty?(@form[:content].value)}
683703
form={@form_id}
684704
class={[
685705
"p-1.5 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 transition-all duration-200 flex items-center justify-center h-7 w-7",
686-
if(@disabled,
706+
if(@disabled || form_content_empty?(@form[:content].value),
687707
do:
688708
"text-gray-400 bg-gray-300 cursor-not-allowed focus:ring-gray-300",
689709
else:
@@ -933,6 +953,15 @@ defmodule LightningWeb.AiAssistant.Component do
933953
Timex.diff(DateTime.utc_now(), datetime, :hours) < 1
934954
end
935955

956+
defp form_content_empty?(value) do
957+
case value do
958+
nil -> true
959+
"" -> true
960+
content when is_binary(content) -> String.trim(content) == ""
961+
_ -> false
962+
end
963+
end
964+
936965
defp render_onboarding(assigns) do
937966
assigns = assign(assigns, ai_quote: Quotes.random_enabled())
938967

lib/lightning_web/live/ai_assistant/mode_behavior.ex

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,20 @@ defmodule LightningWeb.Live.AiAssistant.ModeBehavior do
233233
field :content, :string
234234
end
235235

236-
def changeset(params) do
236+
@doc false
237+
def changeset(params \\ %{}) do
237238
%__MODULE__{}
238239
|> cast(params, [:content])
240+
|> validate_required([:content],
241+
message: "Please enter a message before sending"
242+
)
243+
|> validate_length(:content,
244+
min: 1,
245+
message: "Please enter a message before sending"
246+
)
239247
end
240248

249+
@doc false
241250
def extract_options(_changeset), do: []
242251
end
243252

lib/lightning_web/live/ai_assistant/modes/job_code.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ defmodule LightningWeb.Live.AiAssistant.Modes.JobCode do
4040
def changeset(params) do
4141
%__MODULE__{}
4242
|> cast(params, [:content])
43+
|> validate_required([:content],
44+
message: "Please enter a message before sending"
45+
)
46+
|> validate_length(:content,
47+
min: 1,
48+
message: "Please enter a message before sending"
49+
)
4350
|> cast_embed(:options, with: &options_changeset/2)
4451
end
4552

test/lightning_web/live/ai_assistant_live_test.exs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ defmodule LightningWeb.AiAssistantLiveTest do
308308
assert has_element?(input_element)
309309
refute render(input_element) =~ "disabled=\"disabled\""
310310
assert has_element?(submit_btn)
311-
refute render(submit_btn) =~ "disabled=\"disabled\""
311+
# Submit button should be disabled when no content is entered
312+
assert render(submit_btn) =~ "disabled=\"disabled\""
312313

313314
html =
314315
view
@@ -2354,7 +2355,8 @@ defmodule LightningWeb.AiAssistantLiveTest do
23542355
)
23552356

23562357
refute render(input_element) =~ "disabled=\"disabled\""
2357-
refute render(submit_btn) =~ "disabled=\"disabled\""
2358+
# Submit button should be disabled when no content is entered
2359+
assert render(submit_btn) =~ "disabled=\"disabled\""
23582360

23592361
refute render(input_element) =~ "Save your workflow first"
23602362
end

test/lightning_web/live/workflow_live/ai_assistant_component_test.exs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,45 @@ defmodule LightningWeb.WorkflowLive.AiAssistant.ComponentTest do
289289
]
290290
end
291291
end
292+
293+
describe "form validation" do
294+
alias LightningWeb.Live.AiAssistant.Modes.WorkflowTemplate
295+
296+
test "JobCode Form validates empty content" do
297+
changeset = JobCode.Form.changeset(%{"content" => ""})
298+
299+
assert changeset.valid? == false
300+
assert Keyword.has_key?(changeset.errors, :content)
301+
{msg, _opts} = changeset.errors[:content]
302+
assert msg == "Please enter a message before sending"
303+
end
304+
305+
test "JobCode validate_form includes content validation" do
306+
changeset = JobCode.validate_form(%{"content" => nil})
307+
308+
assert changeset.valid? == false
309+
assert Keyword.has_key?(changeset.errors, :content)
310+
end
311+
312+
test "WorkflowTemplate DefaultForm validates empty content" do
313+
changeset = WorkflowTemplate.DefaultForm.changeset(%{"content" => ""})
314+
315+
assert changeset.valid? == false
316+
assert Keyword.has_key?(changeset.errors, :content)
317+
{msg, _opts} = changeset.errors[:content]
318+
assert msg == "Please enter a message before sending"
319+
end
320+
321+
test "form validation accepts valid content" do
322+
# JobCode
323+
changeset = JobCode.validate_form(%{"content" => "Help me with my code"})
324+
assert changeset.valid? == true
325+
326+
# WorkflowTemplate
327+
changeset =
328+
WorkflowTemplate.validate_form(%{"content" => "Create a workflow"})
329+
330+
assert changeset.valid? == true
331+
end
332+
end
292333
end

0 commit comments

Comments
 (0)