feat: preliminary work on assistant
This commit is contained in:
@@ -47,6 +47,8 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|> assign(:page_language, ShellData.ui_language())
|
||||
|> assign(:client_shortcuts, Commands.client_shortcuts())
|
||||
|> assign(:offline_mode, true)
|
||||
|> assign(:assistant_prompt, "")
|
||||
|> assign(:assistant_messages, [])
|
||||
|> assign(:is_mac_ui, mac_ui?())
|
||||
|> assign(:menu_groups, titlebar_menu_groups())
|
||||
|> assign(:titlebar_menu_group, nil)
|
||||
@@ -275,6 +277,25 @@ defmodule BDS.Desktop.ShellLive do
|
||||
{:noreply, reload_shell(socket, socket.assigns.workbench)}
|
||||
end
|
||||
|
||||
def handle_event("update_assistant_prompt", %{"assistant" => %{"prompt" => prompt}}, socket) do
|
||||
{:noreply, assign(socket, :assistant_prompt, prompt)}
|
||||
end
|
||||
|
||||
def handle_event("submit_assistant_prompt", %{"assistant" => %{"prompt" => prompt}}, socket) do
|
||||
prompt = prompt |> to_string() |> String.trim()
|
||||
|
||||
socket =
|
||||
if prompt == "" do
|
||||
assign(socket, :assistant_prompt, "")
|
||||
else
|
||||
socket
|
||||
|> assign(:assistant_prompt, "")
|
||||
|> assign(:assistant_messages, socket.assigns.assistant_messages ++ assistant_turn(prompt, socket))
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("open_tasks_panel", _params, socket) do
|
||||
workbench =
|
||||
socket.assigns.workbench
|
||||
@@ -1506,6 +1527,34 @@ defmodule BDS.Desktop.ShellLive do
|
||||
defp tab_icon_id(%{type: :style}), do: "settings"
|
||||
defp tab_icon_id(%{type: type}), do: Atom.to_string(type)
|
||||
|
||||
defp assistant_turn(prompt, socket) do
|
||||
[
|
||||
%{role: "user", content: prompt},
|
||||
%{role: "assistant", content: assistant_reply(socket)}
|
||||
]
|
||||
end
|
||||
|
||||
defp assistant_reply(socket) do
|
||||
if socket.assigns.offline_mode do
|
||||
ShellData.translate("Automatic AI actions stay gated by airplane mode.", %{}, socket.assigns.page_language)
|
||||
else
|
||||
ShellData.translate(
|
||||
"The assistant sidebar chat surface is ready, but model execution is not connected yet.",
|
||||
%{},
|
||||
socket.assigns.page_language
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp assistant_project_name(nil), do: translated("Projects")
|
||||
defp assistant_project_name(project), do: project.name
|
||||
|
||||
defp assistant_message_label("assistant"), do: translated("Assistant")
|
||||
defp assistant_message_label("user"), do: translated("You")
|
||||
defp assistant_message_label(_role), do: translated("Assistant")
|
||||
|
||||
defp assistant_message_testid(role), do: "assistant-message-#{role}"
|
||||
|
||||
defp media_thumbnail_glyph(mime_type) do
|
||||
case String.split(to_string(mime_type || ""), "/", parts: 2) do
|
||||
["image", _rest] -> "IMG"
|
||||
|
||||
@@ -416,15 +416,77 @@
|
||||
>
|
||||
<div class="resizable-panel-divider assistant-divider" data-resize="assistant" data-role="resize-handle"></div>
|
||||
<aside class="assistant-sidebar" data-region="assistant-sidebar">
|
||||
<div class="assistant-header">
|
||||
<strong><%= translated("Assistant") %></strong>
|
||||
</div>
|
||||
<div class="assistant-content">
|
||||
<%= for card <- @assistant_cards do %>
|
||||
<section class="assistant-card">
|
||||
<strong><%= translated(card.label) %></strong>
|
||||
<span><%= translated(card.text) %></span>
|
||||
</section>
|
||||
<header class="assistant-sidebar-header">
|
||||
<div class="assistant-sidebar-heading">
|
||||
<strong><%= translated("AI Assistant") %></strong>
|
||||
<span class="assistant-sidebar-description"><%= translated("AI conversations") %></span>
|
||||
</div>
|
||||
<span class={[
|
||||
"assistant-sidebar-status",
|
||||
if(@offline_mode, do: "is-offline", else: "is-online")
|
||||
]}>
|
||||
<%= if @offline_mode, do: translated("Offline"), else: translated("Chat") %>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<section class="assistant-sidebar-context" data-testid="assistant-context">
|
||||
<div class="assistant-sidebar-context-row">
|
||||
<span class="assistant-sidebar-context-label"><%= translated("Project") %></span>
|
||||
<span class="assistant-sidebar-context-value"><%= assistant_project_name(@current_project) %></span>
|
||||
</div>
|
||||
<div class="assistant-sidebar-context-row">
|
||||
<span class="assistant-sidebar-context-label"><%= translated("Editor") %></span>
|
||||
<span class="assistant-sidebar-context-value"><%= tab_title(@current_tab, @tab_meta) %></span>
|
||||
</div>
|
||||
<p class="assistant-sidebar-context-text"><%= tab_subtitle(@current_tab, @tab_meta) %></p>
|
||||
</section>
|
||||
|
||||
<form
|
||||
class="assistant-sidebar-prompt-form"
|
||||
data-testid="assistant-prompt-form"
|
||||
phx-change="update_assistant_prompt"
|
||||
phx-submit="submit_assistant_prompt"
|
||||
>
|
||||
<textarea
|
||||
class="assistant-sidebar-prompt"
|
||||
data-testid="assistant-prompt-input"
|
||||
name="assistant[prompt]"
|
||||
rows="6"
|
||||
placeholder={translated("Ask the assistant about the active project or editor.")}
|
||||
><%= @assistant_prompt %></textarea>
|
||||
|
||||
<button
|
||||
class="assistant-sidebar-start-button"
|
||||
data-testid="assistant-start-button"
|
||||
type="submit"
|
||||
disabled={String.trim(@assistant_prompt || "") == ""}
|
||||
>
|
||||
<%= translated("Start chat") %>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<%= if Enum.empty?(@assistant_messages) do %>
|
||||
<div class="assistant-sidebar-welcome">
|
||||
<%= for card <- @assistant_cards do %>
|
||||
<section class="assistant-card">
|
||||
<strong><%= translated(card.label) %></strong>
|
||||
<span><%= translated(card.text) %></span>
|
||||
</section>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="assistant-sidebar-transcript">
|
||||
<%= for message <- @assistant_messages do %>
|
||||
<article
|
||||
class={["assistant-sidebar-message", message.role]}
|
||||
data-testid={assistant_message_testid(message.role)}
|
||||
>
|
||||
<span class="assistant-sidebar-message-role"><%= assistant_message_label(message.role) %></span>
|
||||
<p class="assistant-sidebar-message-content"><%= message.content %></p>
|
||||
</article>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
Reference in New Issue
Block a user