fix: fixes for AI chat
This commit is contained in:
@@ -90,8 +90,8 @@ defmodule BDS.Desktop.MainWindow do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def terminate(_reason, %{frame: frame, last_bounds: last_bounds}) do
|
||||
if bounds = current_bounds(frame) || last_bounds do
|
||||
def terminate(_reason, %{last_bounds: last_bounds}) do
|
||||
if bounds = last_bounds do
|
||||
_ = persist_bounds(bounds)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,54 +1,56 @@
|
||||
<div id={"chat-editor-#{@chat_editor.id}"} class="chat-panel" data-testid="chat-editor" phx-hook="ChatSurface">
|
||||
<div class="chat-panel-header">
|
||||
<div class="chat-panel-title">
|
||||
<%= if @chat_editor.needs_api_key? do %>
|
||||
<%= translated("chat.setupTitle") %>
|
||||
<% else %>
|
||||
<%= @chat_editor.title %>
|
||||
<span class="chat-panel-title-main">
|
||||
<%= if @chat_editor.needs_api_key? do %>
|
||||
<%= translated("chat.setupTitle") %>
|
||||
<% else %>
|
||||
<%= @chat_editor.title %>
|
||||
<% end %>
|
||||
</span>
|
||||
|
||||
<%= unless @chat_editor.needs_api_key? do %>
|
||||
<span class="chat-model-selector-wrap">
|
||||
<button
|
||||
class="chat-model-selector-button chat-model-selector-inline"
|
||||
type="button"
|
||||
phx-click="toggle_chat_model_selector"
|
||||
data-testid="chat-model-selector-button"
|
||||
>
|
||||
<span><%= @chat_editor.model || translated("chat.newChat") %></span>
|
||||
<span class="chat-model-selector-caret">▾</span>
|
||||
</button>
|
||||
|
||||
<%= if @chat_editor.model_selector_open? and @chat_editor.available_models != [] do %>
|
||||
<div class="chat-model-selector-menu">
|
||||
<%= for group <- @chat_editor.available_model_groups do %>
|
||||
<section class="chat-model-provider-group" data-testid="chat-model-provider-group" data-provider={group.provider}>
|
||||
<%= if length(@chat_editor.available_model_groups) > 1 do %>
|
||||
<div class="chat-model-provider-header"><%= group.label %></div>
|
||||
<% end %>
|
||||
|
||||
<%= for model <- group.models do %>
|
||||
<button
|
||||
class={[
|
||||
"chat-model-selector-option",
|
||||
if(model.id == @chat_editor.model, do: "active")
|
||||
]}
|
||||
type="button"
|
||||
phx-click="select_chat_model"
|
||||
phx-value-model={model.id}
|
||||
data-testid="chat-model-selector-option"
|
||||
data-provider={group.provider}
|
||||
>
|
||||
<span class="chat-model-selector-option-name"><%= model.name || model.id %></span>
|
||||
</button>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= unless @chat_editor.needs_api_key? do %>
|
||||
<div class="chat-panel-header-actions">
|
||||
<button
|
||||
class="chat-model-selector-button"
|
||||
type="button"
|
||||
phx-click="toggle_chat_model_selector"
|
||||
data-testid="chat-model-selector-button"
|
||||
>
|
||||
<span><%= @chat_editor.model || translated("chat.newChat") %></span>
|
||||
<span class="chat-model-selector-caret">▾</span>
|
||||
</button>
|
||||
|
||||
<%= if @chat_editor.model_selector_open? and @chat_editor.available_models != [] do %>
|
||||
<div class="chat-model-selector-menu">
|
||||
<%= for group <- @chat_editor.available_model_groups do %>
|
||||
<section class="chat-model-provider-group" data-testid="chat-model-provider-group" data-provider={group.provider}>
|
||||
<%= if length(@chat_editor.available_model_groups) > 1 do %>
|
||||
<div class="chat-model-provider-header"><%= group.label %></div>
|
||||
<% end %>
|
||||
|
||||
<%= for model <- group.models do %>
|
||||
<button
|
||||
class={[
|
||||
"chat-model-selector-option",
|
||||
if(model.id == @chat_editor.model, do: "active")
|
||||
]}
|
||||
type="button"
|
||||
phx-click="select_chat_model"
|
||||
phx-value-model={model.id}
|
||||
data-testid="chat-model-selector-option"
|
||||
data-provider={group.provider}
|
||||
>
|
||||
<span class="chat-model-selector-option-name"><%= model.name || model.id %></span>
|
||||
</button>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="chat-messages chat-surface-scroll">
|
||||
@@ -83,7 +85,7 @@
|
||||
<div class="chat-message-header">
|
||||
<span class="chat-message-role"><%= message_role_label(:user) %></span>
|
||||
</div>
|
||||
<div class="chat-message-text"><%= @chat_editor.pending_user_message %></div>
|
||||
<div class="chat-message-text chat-user-message-text" data-testid="chat-user-message-text"><%= @chat_editor.pending_user_message %></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -95,13 +97,11 @@
|
||||
<div class="chat-message-header"><span class="chat-message-role"><%= message_role_label(message.role) %></span></div>
|
||||
<.chat_tool_markers markers={message.tool_markers} />
|
||||
|
||||
<div class="chat-message-text">
|
||||
<%= if message.role == :assistant do %>
|
||||
<%= markdown_html(message.content || "") %>
|
||||
<% else %>
|
||||
<%= message.content || "" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= if message.role == :assistant do %>
|
||||
<div class="chat-message-text"><%= markdown_html(message.content || "") %></div>
|
||||
<% else %>
|
||||
<div class="chat-message-text chat-user-message-text" data-testid="chat-user-message-text"><%= message.content || "" %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
<div class="settings-view-shell" data-testid="settings-editor" data-selected-settings-section={@settings_editor.selected_section}>
|
||||
<div
|
||||
id="settings-editor-shell"
|
||||
class="settings-view-shell"
|
||||
data-testid="settings-editor"
|
||||
phx-hook="SettingsSectionScroll"
|
||||
data-selected-settings-section={@settings_editor.selected_section}
|
||||
data-settings-scroll-target={"settings-section-#{@settings_editor.selected_section}"}
|
||||
>
|
||||
<div class="settings-view">
|
||||
<div class="settings-header">
|
||||
<h2 data-testid="editor-title"><%= translated("Settings") %></h2>
|
||||
|
||||
@@ -2,6 +2,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
@moduledoc false
|
||||
|
||||
alias BDS.Desktop.{FilePicker, ShellData}
|
||||
alias BDS.AI
|
||||
alias BDS.ImportDefinitions
|
||||
alias BDS.Scripts
|
||||
alias BDS.Templates
|
||||
@@ -132,6 +133,27 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
end
|
||||
end
|
||||
|
||||
def create(socket, _project_id, "chat", callbacks) do
|
||||
case AI.start_chat(%{}) do
|
||||
{:ok, conversation} ->
|
||||
callbacks.open_sidebar.(
|
||||
socket,
|
||||
%{
|
||||
"route" => "chat",
|
||||
"id" => conversation.id,
|
||||
"title" => conversation.title,
|
||||
"subtitle" => "AI conversations"
|
||||
},
|
||||
:pin
|
||||
)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> callbacks.append_output.(translated("chat.newChat"), inspect(reason), nil, "error")
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
def create(socket, project_id, "import", callbacks) do
|
||||
case ImportDefinitions.create_definition(%{
|
||||
project_id: project_id,
|
||||
@@ -168,6 +190,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
def action(:media), do: %{kind: "media", label: "sidebar.importMedia"}
|
||||
def action(:scripts), do: %{kind: "script", label: "sidebar.scripts.newScript"}
|
||||
def action(:templates), do: %{kind: "template", label: "sidebar.templates.newTemplate"}
|
||||
def action(:chat), do: %{kind: "chat", label: "chat.newChat"}
|
||||
def action(:import), do: %{kind: "import", label: "sidebar.import.newDefinition"}
|
||||
def action(_view), do: nil
|
||||
|
||||
|
||||
124
priv/ui/app.css
124
priv/ui/app.css
@@ -3561,14 +3561,22 @@ button svg * {
|
||||
|
||||
.chat-panel-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vscode-foreground, inherit);
|
||||
}
|
||||
|
||||
.chat-panel-title-main {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-panel-header {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
@@ -5133,14 +5141,22 @@ button svg * {
|
||||
|
||||
.chat-panel-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vscode-foreground, inherit);
|
||||
}
|
||||
|
||||
.chat-panel-title-main {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-panel-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -5160,9 +5176,29 @@ button svg * {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 0 1 auto;
|
||||
max-width: min(40vw, 240px);
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground, inherit);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-model-selector-inline {
|
||||
min-width: 0;
|
||||
background-color: var(--vscode-input-background, transparent);
|
||||
}
|
||||
|
||||
.chat-model-selector-inline span:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chat-model-selector-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
min-width: 0;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.chat-model-selector-button:hover,
|
||||
@@ -5177,7 +5213,7 @@ button svg * {
|
||||
.chat-model-selector-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 16px;
|
||||
left: 0;
|
||||
min-width: 180px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
@@ -5318,6 +5354,13 @@ button svg * {
|
||||
}
|
||||
|
||||
.chat-message.user .chat-message-content {
|
||||
width: fit-content;
|
||||
min-width: 0;
|
||||
max-width: min(80%, 720px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -5368,6 +5411,9 @@ button svg * {
|
||||
}
|
||||
|
||||
.chat-message.user .chat-message-text {
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
border-radius: 12px 12px 2px 12px;
|
||||
background-color: var(--vscode-button-background, var(--accent-color));
|
||||
color: var(--vscode-button-foreground, #ffffff);
|
||||
@@ -5377,6 +5423,74 @@ button svg * {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.chat-panel .chat-model-selector-button.chat-model-selector-inline {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: 240px;
|
||||
height: auto;
|
||||
flex: 0 1 auto;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--vscode-input-border, var(--line, #3c3c3c));
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--vscode-descriptionForeground, inherit);
|
||||
}
|
||||
|
||||
.chat-panel .chat-model-selector-caret {
|
||||
position: static;
|
||||
inset: auto;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
height: auto;
|
||||
display: inline;
|
||||
flex: 0 0 auto;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
color: inherit;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.chat-panel .chat-model-selector-menu {
|
||||
left: 0;
|
||||
right: auto;
|
||||
width: max-content;
|
||||
min-width: 180px;
|
||||
max-width: min(360px, calc(100vw - 48px));
|
||||
height: auto;
|
||||
padding: 6px;
|
||||
border: 1px solid var(--vscode-dropdown-border, var(--line, #3c3c3c));
|
||||
border-radius: 4px;
|
||||
background: var(--vscode-dropdown-background, var(--panel-1, #1e1e1e));
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.chat-panel .chat-message.user .chat-message-content {
|
||||
width: max-content;
|
||||
min-width: 0;
|
||||
max-width: min(72%, 720px);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.chat-panel .chat-message.user .chat-message-text.chat-user-message-text {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding: 6px 12px;
|
||||
line-height: 1.35;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.chat-message.streaming .chat-message-text {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
|
||||
@@ -690,6 +690,35 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}
|
||||
},
|
||||
|
||||
SettingsSectionScroll: {
|
||||
mounted() {
|
||||
this.lastTargetId = null;
|
||||
this.scrollToSelectedSection();
|
||||
},
|
||||
|
||||
updated() {
|
||||
this.scrollToSelectedSection();
|
||||
},
|
||||
|
||||
scrollToSelectedSection() {
|
||||
const targetId = this.el.dataset.settingsScrollTarget;
|
||||
|
||||
if (!targetId || targetId === this.lastTargetId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastTargetId = targetId;
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
const target = document.getElementById(targetId);
|
||||
|
||||
if (target && this.el.contains(target)) {
|
||||
target.scrollIntoView({ block: "start", behavior: "smooth" });
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
ChatSurface: {
|
||||
mounted() {
|
||||
this.stickToBottom = true;
|
||||
|
||||
@@ -63,4 +63,19 @@ defmodule BDS.Desktop.MainWindowTest do
|
||||
|
||||
assert opts[:size] == {1200, 700}
|
||||
end
|
||||
|
||||
test "terminate persists the last known bounds without querying wx during shutdown", %{
|
||||
path: path
|
||||
} do
|
||||
bounds = %{x: 33, y: 44, width: 900, height: 700}
|
||||
|
||||
assert :ok = MainWindow.terminate(:shutdown, %{frame: :invalid_wx_frame, last_bounds: bounds})
|
||||
|
||||
assert Jason.decode!(File.read!(path)) == %{
|
||||
"x" => 33,
|
||||
"y" => 44,
|
||||
"width" => 900,
|
||||
"height" => 700
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -136,7 +136,7 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "sidebar headers expose old-app create actions for posts, media, scripts, templates, and imports" do
|
||||
test "sidebar headers expose old-app create actions for posts, media, scripts, templates, chat, and imports" do
|
||||
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
assert html =~ ~s(data-testid="sidebar-create-action")
|
||||
@@ -162,6 +162,13 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
|
||||
assert html =~ ~s(data-sidebar-action="template")
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='activity-button'][data-view='chat']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ ~s(data-sidebar-action="chat")
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='activity-button'][data-view='import']")
|
||||
@@ -170,13 +177,15 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
assert html =~ ~s(data-sidebar-action="import")
|
||||
end
|
||||
|
||||
test "sidebar create actions follow the old-app post, script, template, and import flows", %{
|
||||
project: project
|
||||
} do
|
||||
test "sidebar create actions follow the old-app post, script, template, chat, and import flows",
|
||||
%{
|
||||
project: project
|
||||
} do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
post_count_before = Repo.aggregate(Post, :count, :id)
|
||||
script_count_before = Repo.aggregate(BDS.Scripts.Script, :count, :id)
|
||||
template_count_before = Repo.aggregate(BDS.Templates.Template, :count, :id)
|
||||
chat_count_before = Repo.aggregate(BDS.AI.ChatConversation, :count, :id)
|
||||
import_count_before = Repo.aggregate(ImportDefinitions.ImportDefinition, :count, :id)
|
||||
|
||||
html =
|
||||
@@ -225,6 +234,20 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
assert html =~ ~s(data-tab-type="templates")
|
||||
assert html =~ ~s(data-tab-id="#{created_template.id}")
|
||||
|
||||
_html = render_click(view, "select_view", %{"view" => "chat"})
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='sidebar-create-action'][data-sidebar-action='chat']")
|
||||
|> render_click()
|
||||
|
||||
assert Repo.aggregate(BDS.AI.ChatConversation, :count, :id) == chat_count_before + 1
|
||||
|
||||
created_chat = Repo.one!(BDS.AI.ChatConversation)
|
||||
assert created_chat.title == "New Chat"
|
||||
assert html =~ ~s(data-tab-type="chat")
|
||||
assert html =~ ~s(data-tab-id="#{created_chat.id}")
|
||||
|
||||
_html = render_click(view, "select_view", %{"view" => "import"})
|
||||
|
||||
html =
|
||||
@@ -242,6 +265,21 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
assert html =~ ~s(data-tab-id="#{created_definition.id}")
|
||||
end
|
||||
|
||||
test "settings sidebar selections expose a scroll target for the preferences editor" do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
_html = render_click(view, "select_view", %{"view" => "settings"})
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='sidebar-open-item'][data-item-id='settings-ai']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ ~s(phx-hook="SettingsSectionScroll")
|
||||
assert html =~ ~s(data-selected-settings-section="ai")
|
||||
assert html =~ ~s(data-settings-scroll-target="settings-section-ai")
|
||||
end
|
||||
|
||||
test "shell live refreshes the posts sidebar when the CLI watcher broadcasts an entity change",
|
||||
%{project: project} do
|
||||
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
@@ -2074,6 +2112,38 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
refute chat_html =~ "Desktop workbench content routed through the Elixir shell."
|
||||
end
|
||||
|
||||
test "chat editor uses the model name itself as the selector" do
|
||||
assert {:ok, conversation} = AI.start_chat(%{title: "Selector Chat", model: "qwen3.5-122b"})
|
||||
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
html =
|
||||
render_click(view, "pin_sidebar_item", %{
|
||||
"route" => "chat",
|
||||
"id" => conversation.id,
|
||||
"title" => conversation.title,
|
||||
"subtitle" => conversation.model || "chat"
|
||||
})
|
||||
|
||||
assert html =~ ~s(data-testid="chat-model-selector-button")
|
||||
assert html =~ ~s(class="chat-panel-title-main")
|
||||
assert html =~ ~s(class="chat-model-selector-wrap")
|
||||
assert html =~ ~s(class="chat-model-selector-button chat-model-selector-inline")
|
||||
refute html =~ ~s(class="chat-panel-header-actions")
|
||||
|
||||
css = File.read!(Path.expand("../../../priv/ui/app.css", __DIR__))
|
||||
assert css =~ ".chat-model-selector-wrap"
|
||||
assert css =~ "left: 0;"
|
||||
assert css =~ "right: auto;"
|
||||
|
||||
refute css =~
|
||||
".chat-model-selector-menu {\n position: absolute;\n top: calc(100% + 4px);\n right: 16px;"
|
||||
|
||||
assert css =~ ".chat-panel .chat-model-selector-button.chat-model-selector-inline"
|
||||
assert css =~ ".chat-panel .chat-model-selector-caret"
|
||||
assert css =~ "position: static;"
|
||||
end
|
||||
|
||||
test "chat editor renders legacy model controls, tool markers, and structured tool surfaces" do
|
||||
assert {:ok, conversation} = AI.start_chat(%{title: "Editor Chat", model: "gpt-4.1"})
|
||||
|
||||
@@ -2141,6 +2211,42 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
assert html =~ "Posts"
|
||||
end
|
||||
|
||||
test "chat editor marks user message text as compact" do
|
||||
assert {:ok, conversation} = AI.start_chat(%{title: "Compact Chat", model: "gpt-4.1"})
|
||||
|
||||
Repo.insert!(
|
||||
BDS.AI.ChatMessage.changeset(%BDS.AI.ChatMessage{}, %{
|
||||
conversation_id: conversation.id,
|
||||
role: :user,
|
||||
content: "wie viele Posts sind im Blog?",
|
||||
created_at: Persistence.now_ms()
|
||||
})
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
html =
|
||||
render_click(view, "pin_sidebar_item", %{
|
||||
"route" => "chat",
|
||||
"id" => conversation.id,
|
||||
"title" => conversation.title,
|
||||
"subtitle" => conversation.model || "chat"
|
||||
})
|
||||
|
||||
assert html =~ ~s(data-testid="chat-user-message-text")
|
||||
assert html =~ ~s(class="chat-message-text chat-user-message-text")
|
||||
|
||||
assert html =~
|
||||
~s(<div class="chat-message-text chat-user-message-text" data-testid="chat-user-message-text">wie viele Posts sind im Blog?</div>)
|
||||
|
||||
css = File.read!(Path.expand("../../../priv/ui/app.css", __DIR__))
|
||||
assert css =~ ".chat-panel .chat-message.user .chat-message-content"
|
||||
assert css =~ "background: transparent;"
|
||||
assert css =~ "border: 0;"
|
||||
assert css =~ "padding: 6px 12px;"
|
||||
assert css =~ "line-height: 1.35;"
|
||||
end
|
||||
|
||||
test "chat editor groups selector models by provider and uses catalog labels" do
|
||||
updated_at = Persistence.now_ms()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user