feat: ccloseout of changes for audit
This commit is contained in:
@@ -35,9 +35,11 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
|
||||
end
|
||||
|
||||
defp generate_blocking(url, headers, payload, request) do
|
||||
payload_json = encode_payload(payload)
|
||||
payload_json = Jason.encode!(payload)
|
||||
|
||||
log_request(:blocking, url, request, payload, payload_json)
|
||||
Logger.debug(
|
||||
"AI OpenAI-compatible request operation=#{inspect(Map.get(request, :operation))} model=#{inspect(request.model)} url=#{url} tools=#{payload |> Map.get("tools", []) |> length()} payload_size=#{byte_size(payload_json)}"
|
||||
)
|
||||
|
||||
case HttpClient.post(url, headers, payload_json) do
|
||||
{:ok, %{status: 200, body: body}} ->
|
||||
@@ -75,9 +77,11 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
|
||||
# snapshots to `on_stream` as they arrive. The assembled message goes
|
||||
# through the same normalization as the blocking path.
|
||||
defp generate_streaming(url, headers, payload, request, on_stream) do
|
||||
payload_json = payload |> streaming_payload() |> encode_payload()
|
||||
payload_json = payload |> streaming_payload() |> Jason.encode!()
|
||||
|
||||
log_request(:streaming, url, request, payload, payload_json)
|
||||
Logger.debug(
|
||||
"AI OpenAI-compatible streaming request operation=#{inspect(Map.get(request, :operation))} model=#{inspect(request.model)} url=#{url} payload_size=#{byte_size(payload_json)}"
|
||||
)
|
||||
|
||||
sse = SSE.new(on_stream, emit_interval_ms: stream_emit_interval_ms())
|
||||
|
||||
@@ -234,20 +238,6 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
|
||||
|> Map.put("stream_options", %{"include_usage" => true})
|
||||
end
|
||||
|
||||
defp encode_payload(payload), do: Jason.encode!(payload)
|
||||
|
||||
defp log_request(mode, url, request, payload, payload_json) do
|
||||
tools_segment =
|
||||
case mode do
|
||||
:blocking -> " tools=#{payload |> Map.get("tools", []) |> length()}"
|
||||
:streaming -> ""
|
||||
end
|
||||
|
||||
Logger.debug(
|
||||
"AI OpenAI-compatible#{if mode == :streaming, do: " streaming", else: ""} request operation=#{inspect(Map.get(request, :operation))} model=#{inspect(request.model)} url=#{url}#{tools_segment} payload_size=#{byte_size(payload_json)}"
|
||||
)
|
||||
end
|
||||
|
||||
defp log_http_status_error(prefix, status, body) do
|
||||
Logger.error("#{prefix} HTTP error status=#{status} body=#{String.slice(body, 0, 2000)}")
|
||||
end
|
||||
|
||||
@@ -545,8 +545,13 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
||||
|
||||
@spec markdown_html(binary()) :: Phoenix.HTML.Safe.t()
|
||||
def markdown_html(content) when is_binary(content) do
|
||||
# Match Earmark's defaults (GFM tables, strikethrough, autolinks); escape
|
||||
# raw HTML to text rather than rendering it, as the old escape: true did.
|
||||
html =
|
||||
case MDEx.to_html(content) do
|
||||
case MDEx.to_html(content,
|
||||
extension: [table: true, strikethrough: true, autolink: true],
|
||||
render: [escape: true]
|
||||
) do
|
||||
{:ok, rendered} -> rendered
|
||||
{:error, _reason} -> ""
|
||||
end
|
||||
|
||||
@@ -278,8 +278,13 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
||||
|
||||
@spec markdown_html(String.t()) :: Phoenix.HTML.safe()
|
||||
def markdown_html(content) do
|
||||
# Match Earmark's defaults (GFM tables, strikethrough, autolinks); escape
|
||||
# raw HTML to text rather than rendering it, as the old escape: true did.
|
||||
html =
|
||||
case MDEx.to_html(content || "") do
|
||||
case MDEx.to_html(content || "",
|
||||
extension: [table: true, strikethrough: true, autolink: true],
|
||||
render: [escape: true]
|
||||
) do
|
||||
{:ok, rendered} -> rendered
|
||||
{:error, _reason} -> ""
|
||||
end
|
||||
|
||||
@@ -3,24 +3,6 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|
||||
alias BDS.Desktop.ShellLive.SidebarState, as: ShellSidebarState
|
||||
|
||||
@event_handlers %{
|
||||
"toggle_sidebar_filters" => :toggle_sidebar_filters,
|
||||
"toggle_sidebar_archive" => :toggle_sidebar_archive,
|
||||
"toggle_sidebar_tags" => :toggle_sidebar_tags,
|
||||
"toggle_sidebar_categories" => :toggle_sidebar_categories,
|
||||
"update_sidebar_search" => :update_sidebar_search,
|
||||
"clear_sidebar_search" => :clear_sidebar_search,
|
||||
"clear_sidebar_tags" => :clear_sidebar_tags,
|
||||
"clear_sidebar_categories" => :clear_sidebar_categories,
|
||||
"toggle_sidebar_tag" => :toggle_sidebar_tag,
|
||||
"toggle_sidebar_category" => :toggle_sidebar_category,
|
||||
"select_sidebar_year" => :select_sidebar_year,
|
||||
"select_sidebar_month" => :select_sidebar_month,
|
||||
"clear_sidebar_month" => :clear_sidebar_month,
|
||||
"clear_sidebar_filters" => :clear_sidebar_filters,
|
||||
"load_more_sidebar" => :load_more_sidebar
|
||||
}
|
||||
|
||||
@spec handle(Phoenix.LiveView.Socket.t(), String.t(), map(), (Phoenix.LiveView.Socket.t(),
|
||||
term() ->
|
||||
Phoenix.LiveView.Socket.t())) ::
|
||||
@@ -29,14 +11,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
{:noreply, dispatch(event, socket, params, reload)}
|
||||
end
|
||||
|
||||
defp dispatch(event, socket, params, reload) do
|
||||
case Map.fetch(@event_handlers, event) do
|
||||
{:ok, handler} -> handle_event(handler, socket, params, reload)
|
||||
:error -> socket
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_event(:toggle_sidebar_filters, socket, _params, reload) do
|
||||
defp dispatch("toggle_sidebar_filters", socket, _params, reload) do
|
||||
socket =
|
||||
ShellSidebarState.put_filter_panel_state(socket, fn state ->
|
||||
if state.visible do
|
||||
@@ -55,7 +30,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
reload.(socket, socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:toggle_sidebar_archive, socket, _params, reload) do
|
||||
defp dispatch("toggle_sidebar_archive", socket, _params, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filter_panel_state(fn state ->
|
||||
%{state | archive_collapsed: not state.archive_collapsed}
|
||||
@@ -63,7 +38,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:toggle_sidebar_tags, socket, _params, reload) do
|
||||
defp dispatch("toggle_sidebar_tags", socket, _params, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filter_panel_state(fn state ->
|
||||
%{state | tags_collapsed: not state.tags_collapsed}
|
||||
@@ -71,7 +46,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:toggle_sidebar_categories, socket, _params, reload) do
|
||||
defp dispatch("toggle_sidebar_categories", socket, _params, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filter_panel_state(fn state ->
|
||||
%{state | categories_collapsed: not state.categories_collapsed}
|
||||
@@ -79,7 +54,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:update_sidebar_search, socket, %{"sidebar_filters" => params}, reload) do
|
||||
defp dispatch("update_sidebar_search", socket, %{"sidebar_filters" => params}, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filters(fn filters ->
|
||||
Map.put(
|
||||
@@ -91,25 +66,25 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:clear_sidebar_search, socket, _params, reload) do
|
||||
defp dispatch("clear_sidebar_search", socket, _params, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :search, nil) end)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:clear_sidebar_tags, socket, _params, reload) do
|
||||
defp dispatch("clear_sidebar_tags", socket, _params, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :tags, []) end)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:clear_sidebar_categories, socket, _params, reload) do
|
||||
defp dispatch("clear_sidebar_categories", socket, _params, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filters(fn filters -> Map.put(filters, :categories, []) end)
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:toggle_sidebar_tag, socket, %{"tag" => tag}, reload) do
|
||||
defp dispatch("toggle_sidebar_tag", socket, %{"tag" => tag}, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filters(fn filters ->
|
||||
ShellSidebarState.toggle_filter_value(filters, :tags, tag)
|
||||
@@ -117,7 +92,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:toggle_sidebar_category, socket, %{"category" => category}, reload) do
|
||||
defp dispatch("toggle_sidebar_category", socket, %{"category" => category}, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filters(fn filters ->
|
||||
ShellSidebarState.toggle_filter_value(filters, :categories, category)
|
||||
@@ -125,7 +100,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:select_sidebar_year, socket, %{"year" => year}, reload) do
|
||||
defp dispatch("select_sidebar_year", socket, %{"year" => year}, reload) do
|
||||
parsed_year = ShellSidebarState.parse_optional_integer(year)
|
||||
|
||||
socket
|
||||
@@ -144,7 +119,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:select_sidebar_month, socket, %{"year" => year, "month" => month}, reload) do
|
||||
defp dispatch("select_sidebar_month", socket, %{"year" => year, "month" => month}, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filter_panel_state(fn state ->
|
||||
%{
|
||||
@@ -161,7 +136,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:clear_sidebar_month, socket, _params, reload) do
|
||||
defp dispatch("clear_sidebar_month", socket, _params, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filter_panel_state(fn state ->
|
||||
%{state | archive_collapsed: false}
|
||||
@@ -172,7 +147,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:clear_sidebar_filters, socket, _params, reload) do
|
||||
defp dispatch("clear_sidebar_filters", socket, _params, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filters(fn filters ->
|
||||
filters
|
||||
@@ -189,7 +164,7 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do
|
||||
|> reload.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
defp handle_event(:load_more_sidebar, socket, _params, reload) do
|
||||
defp dispatch("load_more_sidebar", socket, _params, reload) do
|
||||
socket
|
||||
|> ShellSidebarState.put_filters(fn filters ->
|
||||
Map.update(
|
||||
|
||||
@@ -180,10 +180,13 @@ defmodule BDS.Rendering.Filters do
|
||||
end
|
||||
|
||||
defp render_markdown_html(markdown) do
|
||||
# Macros above inject raw HTML (gallery, video embeds), so the render path
|
||||
# must pass HTML through untouched. unsafe: true matches the prior Earmark
|
||||
# no-escape behavior.
|
||||
case MDEx.to_html(markdown, render: [unsafe: true]) do
|
||||
# Match Earmark's defaults: GFM tables, strikethrough and bare-URL autolinks
|
||||
# were all on. Macros above inject raw HTML (gallery, video embeds), so
|
||||
# unsafe: true keeps that HTML untouched as Earmark's no-escape path did.
|
||||
case MDEx.to_html(markdown,
|
||||
extension: [table: true, strikethrough: true, autolink: true],
|
||||
render: [unsafe: true]
|
||||
) do
|
||||
{:ok, html} -> html
|
||||
{:error, _reason} -> markdown
|
||||
end
|
||||
|
||||
@@ -5465,6 +5465,18 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
refute html =~ ~r/<img\b/
|
||||
end
|
||||
|
||||
test "chat editor renders GFM markdown and escapes raw HTML" do
|
||||
{:safe, html} =
|
||||
BDS.Desktop.ShellLive.ChatEditor.markdown_html(
|
||||
"~~old~~ value\n\n| A | B |\n|---|---|\n| 1 | 2 |\n\n<script>alert(1)</script>"
|
||||
)
|
||||
|
||||
assert html =~ "<del>old</del>"
|
||||
assert html =~ "<table>"
|
||||
refute html =~ "<script>"
|
||||
assert html =~ "<script>"
|
||||
end
|
||||
|
||||
test "chat editor renders chart inline surfaces from render_chart tool calls" do
|
||||
assert {:ok, conversation} = AI.start_chat(%{title: "Chart Chat", model: "gpt-4.1"})
|
||||
|
||||
|
||||
@@ -171,6 +171,37 @@ defmodule BDS.GalleryPipelineTest do
|
||||
assert result =~ "tag-cloud-empty"
|
||||
end
|
||||
|
||||
test "renders GFM tables, strikethrough and autolinks like Earmark did", %{
|
||||
project: project,
|
||||
post: post
|
||||
} do
|
||||
{:ok, metadata} = BDS.Metadata.get_project_metadata(project.id)
|
||||
language = metadata.main_language || "en"
|
||||
template_context = BDS.Rendering.TemplateSelection.template_render_context(project.id)
|
||||
|
||||
markdown = """
|
||||
| A | B |
|
||||
|---|---|
|
||||
| 1 | 2 |
|
||||
|
||||
~~gone~~ and visit https://example.com now
|
||||
"""
|
||||
|
||||
result =
|
||||
BDS.Rendering.Filters.render_markdown(
|
||||
markdown,
|
||||
%{},
|
||||
%{},
|
||||
language,
|
||||
template_context,
|
||||
post.id
|
||||
)
|
||||
|
||||
assert result =~ "<table>"
|
||||
assert result =~ "<del>gone</del>"
|
||||
assert result =~ ~s(<a href="https://example.com">)
|
||||
end
|
||||
|
||||
test "[[photo_archive]] renders without media", %{project: project, post: post} do
|
||||
{:ok, metadata} = BDS.Metadata.get_project_metadata(project.id)
|
||||
language = metadata.main_language || "en"
|
||||
|
||||
Reference in New Issue
Block a user