fix: fixes for AI chat
This commit is contained in:
@@ -90,8 +90,8 @@ defmodule BDS.Desktop.MainWindow do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def terminate(_reason, %{frame: frame, last_bounds: last_bounds}) do
|
def terminate(_reason, %{last_bounds: last_bounds}) do
|
||||||
if bounds = current_bounds(frame) || last_bounds do
|
if bounds = last_bounds do
|
||||||
_ = persist_bounds(bounds)
|
_ = persist_bounds(bounds)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
<div id={"chat-editor-#{@chat_editor.id}"} class="chat-panel" data-testid="chat-editor" phx-hook="ChatSurface">
|
<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-header">
|
||||||
<div class="chat-panel-title">
|
<div class="chat-panel-title">
|
||||||
|
<span class="chat-panel-title-main">
|
||||||
<%= if @chat_editor.needs_api_key? do %>
|
<%= if @chat_editor.needs_api_key? do %>
|
||||||
<%= translated("chat.setupTitle") %>
|
<%= translated("chat.setupTitle") %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= @chat_editor.title %>
|
<%= @chat_editor.title %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</span>
|
||||||
|
|
||||||
<%= unless @chat_editor.needs_api_key? do %>
|
<%= unless @chat_editor.needs_api_key? do %>
|
||||||
<div class="chat-panel-header-actions">
|
<span class="chat-model-selector-wrap">
|
||||||
<button
|
<button
|
||||||
class="chat-model-selector-button"
|
class="chat-model-selector-button chat-model-selector-inline"
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="toggle_chat_model_selector"
|
phx-click="toggle_chat_model_selector"
|
||||||
data-testid="chat-model-selector-button"
|
data-testid="chat-model-selector-button"
|
||||||
@@ -47,9 +48,10 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="chat-messages chat-surface-scroll">
|
<div class="chat-messages chat-surface-scroll">
|
||||||
<%= if @chat_editor.needs_api_key? do %>
|
<%= if @chat_editor.needs_api_key? do %>
|
||||||
@@ -83,7 +85,7 @@
|
|||||||
<div class="chat-message-header">
|
<div class="chat-message-header">
|
||||||
<span class="chat-message-role"><%= message_role_label(:user) %></span>
|
<span class="chat-message-role"><%= message_role_label(:user) %></span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -95,15 +97,13 @@
|
|||||||
<div class="chat-message-header"><span class="chat-message-role"><%= message_role_label(message.role) %></span></div>
|
<div class="chat-message-header"><span class="chat-message-role"><%= message_role_label(message.role) %></span></div>
|
||||||
<.chat_tool_markers markers={message.tool_markers} />
|
<.chat_tool_markers markers={message.tool_markers} />
|
||||||
|
|
||||||
<div class="chat-message-text">
|
|
||||||
<%= if message.role == :assistant do %>
|
<%= if message.role == :assistant do %>
|
||||||
<%= markdown_html(message.content || "") %>
|
<div class="chat-message-text"><%= markdown_html(message.content || "") %></div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= message.content || "" %>
|
<div class="chat-message-text chat-user-message-text" data-testid="chat-user-message-text"><%= message.content || "" %></div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= for surface <- message.inline_surfaces do %>
|
<%= for surface <- message.inline_surfaces do %>
|
||||||
<.chat_surface surface={surface} />
|
<.chat_surface surface={surface} />
|
||||||
|
|||||||
@@ -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-view">
|
||||||
<div class="settings-header">
|
<div class="settings-header">
|
||||||
<h2 data-testid="editor-title"><%= translated("Settings") %></h2>
|
<h2 data-testid="editor-title"><%= translated("Settings") %></h2>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
alias BDS.Desktop.{FilePicker, ShellData}
|
alias BDS.Desktop.{FilePicker, ShellData}
|
||||||
|
alias BDS.AI
|
||||||
alias BDS.ImportDefinitions
|
alias BDS.ImportDefinitions
|
||||||
alias BDS.Scripts
|
alias BDS.Scripts
|
||||||
alias BDS.Templates
|
alias BDS.Templates
|
||||||
@@ -132,6 +133,27 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
end
|
end
|
||||||
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
|
def create(socket, project_id, "import", callbacks) do
|
||||||
case ImportDefinitions.create_definition(%{
|
case ImportDefinitions.create_definition(%{
|
||||||
project_id: project_id,
|
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(:media), do: %{kind: "media", label: "sidebar.importMedia"}
|
||||||
def action(:scripts), do: %{kind: "script", label: "sidebar.scripts.newScript"}
|
def action(:scripts), do: %{kind: "script", label: "sidebar.scripts.newScript"}
|
||||||
def action(:templates), do: %{kind: "template", label: "sidebar.templates.newTemplate"}
|
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(:import), do: %{kind: "import", label: "sidebar.import.newDefinition"}
|
||||||
def action(_view), do: nil
|
def action(_view), do: nil
|
||||||
|
|
||||||
|
|||||||
124
priv/ui/app.css
124
priv/ui/app.css
@@ -3561,14 +3561,22 @@ button svg * {
|
|||||||
|
|
||||||
.chat-panel-title {
|
.chat-panel-title {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--vscode-foreground, inherit);
|
color: var(--vscode-foreground, inherit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-panel-title-main {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-panel-header {
|
.chat-panel-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
@@ -5133,14 +5141,22 @@ button svg * {
|
|||||||
|
|
||||||
.chat-panel-title {
|
.chat-panel-title {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--vscode-foreground, inherit);
|
color: var(--vscode-foreground, inherit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-panel-title-main {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-panel-header-actions {
|
.chat-panel-header-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -5160,9 +5176,29 @@ button svg * {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
max-width: min(40vw, 240px);
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--vscode-descriptionForeground, inherit);
|
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,
|
.chat-model-selector-button:hover,
|
||||||
@@ -5177,7 +5213,7 @@ button svg * {
|
|||||||
.chat-model-selector-menu {
|
.chat-model-selector-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(100% + 4px);
|
top: calc(100% + 4px);
|
||||||
right: 16px;
|
left: 0;
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -5318,6 +5354,13 @@ button svg * {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-message.user .chat-message-content {
|
.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;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5368,6 +5411,9 @@ button svg * {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-message.user .chat-message-text {
|
.chat-message.user .chat-message-text {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-block;
|
||||||
border-radius: 12px 12px 2px 12px;
|
border-radius: 12px 12px 2px 12px;
|
||||||
background-color: var(--vscode-button-background, var(--accent-color));
|
background-color: var(--vscode-button-background, var(--accent-color));
|
||||||
color: var(--vscode-button-foreground, #ffffff);
|
color: var(--vscode-button-foreground, #ffffff);
|
||||||
@@ -5377,6 +5423,74 @@ button svg * {
|
|||||||
white-space: normal;
|
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 {
|
.chat-message.streaming .chat-message-text {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
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: {
|
ChatSurface: {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.stickToBottom = true;
|
this.stickToBottom = true;
|
||||||
|
|||||||
@@ -63,4 +63,19 @@ defmodule BDS.Desktop.MainWindowTest do
|
|||||||
|
|
||||||
assert opts[:size] == {1200, 700}
|
assert opts[:size] == {1200, 700}
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ defmodule BDS.Desktop.ShellLiveTest do
|
|||||||
%{project: project, temp_dir: temp_dir}
|
%{project: project, temp_dir: temp_dir}
|
||||||
end
|
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)
|
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
assert html =~ ~s(data-testid="sidebar-create-action")
|
assert html =~ ~s(data-testid="sidebar-create-action")
|
||||||
@@ -162,6 +162,13 @@ defmodule BDS.Desktop.ShellLiveTest do
|
|||||||
|
|
||||||
assert html =~ ~s(data-sidebar-action="template")
|
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 =
|
html =
|
||||||
view
|
view
|
||||||
|> element("[data-testid='activity-button'][data-view='import']")
|
|> element("[data-testid='activity-button'][data-view='import']")
|
||||||
@@ -170,13 +177,15 @@ defmodule BDS.Desktop.ShellLiveTest do
|
|||||||
assert html =~ ~s(data-sidebar-action="import")
|
assert html =~ ~s(data-sidebar-action="import")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "sidebar create actions follow the old-app post, script, template, and import flows", %{
|
test "sidebar create actions follow the old-app post, script, template, chat, and import flows",
|
||||||
|
%{
|
||||||
project: project
|
project: project
|
||||||
} do
|
} do
|
||||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
post_count_before = Repo.aggregate(Post, :count, :id)
|
post_count_before = Repo.aggregate(Post, :count, :id)
|
||||||
script_count_before = Repo.aggregate(BDS.Scripts.Script, :count, :id)
|
script_count_before = Repo.aggregate(BDS.Scripts.Script, :count, :id)
|
||||||
template_count_before = Repo.aggregate(BDS.Templates.Template, :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)
|
import_count_before = Repo.aggregate(ImportDefinitions.ImportDefinition, :count, :id)
|
||||||
|
|
||||||
html =
|
html =
|
||||||
@@ -225,6 +234,20 @@ defmodule BDS.Desktop.ShellLiveTest do
|
|||||||
assert html =~ ~s(data-tab-type="templates")
|
assert html =~ ~s(data-tab-type="templates")
|
||||||
assert html =~ ~s(data-tab-id="#{created_template.id}")
|
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 = render_click(view, "select_view", %{"view" => "import"})
|
||||||
|
|
||||||
html =
|
html =
|
||||||
@@ -242,6 +265,21 @@ defmodule BDS.Desktop.ShellLiveTest do
|
|||||||
assert html =~ ~s(data-tab-id="#{created_definition.id}")
|
assert html =~ ~s(data-tab-id="#{created_definition.id}")
|
||||||
end
|
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",
|
test "shell live refreshes the posts sidebar when the CLI watcher broadcasts an entity change",
|
||||||
%{project: project} do
|
%{project: project} do
|
||||||
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
{: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."
|
refute chat_html =~ "Desktop workbench content routed through the Elixir shell."
|
||||||
end
|
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
|
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"})
|
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"
|
assert html =~ "Posts"
|
||||||
end
|
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
|
test "chat editor groups selector models by provider and uses catalog labels" do
|
||||||
updated_at = Persistence.now_ms()
|
updated_at = Persistence.now_ms()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user