This commit is contained in:
56
lib/elixir_ai_web/utils/form_components.ex
Normal file
56
lib/elixir_ai_web/utils/form_components.ex
Normal file
@@ -0,0 +1,56 @@
|
||||
defmodule ElixirAiWeb.FormComponents do
|
||||
use Phoenix.Component
|
||||
|
||||
@doc """
|
||||
Renders a styled input field with label.
|
||||
|
||||
## Examples
|
||||
|
||||
<.input type="text" name="email" value={@email} label="Email" />
|
||||
<.input type="password" name="password" label="Password" />
|
||||
"""
|
||||
attr :type, :string, default: "text"
|
||||
attr :name, :string, required: true
|
||||
attr :value, :string, default: ""
|
||||
attr :label, :string, required: true
|
||||
attr :autocomplete, :string, default: "off"
|
||||
attr :rest, :global
|
||||
|
||||
def input(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<label for={@name} class="block text-sm text-seafoam-300 mb-1">{@label}</label>
|
||||
<input
|
||||
type={@type}
|
||||
name={@name}
|
||||
id={@name}
|
||||
value={@value}
|
||||
autocomplete={@autocomplete}
|
||||
class="w-full rounded px-3 py-2 text-sm bg-seafoam-950/20 border border-seafoam-900/40 text-seafoam-100 focus:outline-none focus:ring-1 focus:ring-seafoam-700"
|
||||
{@rest}
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders a centered overlay modal. Pass content via the `:inner_block` slot.
|
||||
|
||||
## Examples
|
||||
|
||||
<.modal>
|
||||
<p>Are you sure?</p>
|
||||
</.modal>
|
||||
"""
|
||||
slot :inner_block, required: true
|
||||
|
||||
def modal(assigns) do
|
||||
~H"""
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
||||
<div class="w-full max-w-sm rounded-lg border border-seafoam-900/40 bg-seafoam-950 p-6 shadow-xl">
|
||||
{render_slot(@inner_block)}
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
14
lib/elixir_ai_web/utils/layouts.ex
Normal file
14
lib/elixir_ai_web/utils/layouts.ex
Normal file
@@ -0,0 +1,14 @@
|
||||
defmodule ElixirAiWeb.Layouts do
|
||||
@moduledoc """
|
||||
This module holds different layouts used by your application.
|
||||
|
||||
See the `layouts` directory for all templates available.
|
||||
The "root" layout is a skeleton rendered as part of the
|
||||
application router. The "app" layout is set as the default
|
||||
layout on both `use ElixirAiWeb, :controller` and
|
||||
`use ElixirAiWeb, :live_view`.
|
||||
"""
|
||||
use ElixirAiWeb, :html
|
||||
|
||||
embed_templates "layouts/*"
|
||||
end
|
||||
3
lib/elixir_ai_web/utils/layouts/app.html.heex
Normal file
3
lib/elixir_ai_web/utils/layouts/app.html.heex
Normal file
@@ -0,0 +1,3 @@
|
||||
<main class="px-4 py-8 sm:px-6 lg:px-8 h-screen w-screen">
|
||||
{@inner_content}
|
||||
</main>
|
||||
24
lib/elixir_ai_web/utils/layouts/root.html.heex
Normal file
24
lib/elixir_ai_web/utils/layouts/root.html.heex
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="[scrollbar-gutter:stable]">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="csrf-token" content={get_csrf_token()} />
|
||||
<.live_title default="ElixirAi" suffix=" · Phoenix Framework">
|
||||
{assigns[:page_title]}
|
||||
</.live_title>
|
||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||
</script>
|
||||
<script type="module">
|
||||
import * as smd from "https://cdn.jsdelivr.net/npm/streaming-markdown/smd.min.js"
|
||||
import DOMPurify from "https://cdn.jsdelivr.net/npm/dompurify/+esm"
|
||||
window.smd = smd
|
||||
window.DOMPurify = DOMPurify
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-seafoam-900 text-seafoam-50">
|
||||
{live_render(@conn, ElixirAiWeb.VoiceLive, id: "voice-control")}
|
||||
{@inner_content}
|
||||
</body>
|
||||
</html>
|
||||
16
lib/elixir_ai_web/utils/live_view_pg.ex
Normal file
16
lib/elixir_ai_web/utils/live_view_pg.ex
Normal file
@@ -0,0 +1,16 @@
|
||||
defmodule ElixirAi.LiveViewPG do
|
||||
@moduledoc """
|
||||
Named :pg scope for tracking LiveView processes across the cluster.
|
||||
Each LiveView joins {:liveview, ViewModule} on connect; :pg syncs membership
|
||||
automatically and removes dead processes without any additional cleanup.
|
||||
"""
|
||||
|
||||
def child_spec(_opts) do
|
||||
%{
|
||||
id: __MODULE__,
|
||||
start: {:pg, :start_link, [__MODULE__]},
|
||||
type: :worker,
|
||||
restart: :permanent
|
||||
}
|
||||
end
|
||||
end
|
||||
15
lib/elixir_ai_web/utils/page_tools_pg.ex
Normal file
15
lib/elixir_ai_web/utils/page_tools_pg.ex
Normal file
@@ -0,0 +1,15 @@
|
||||
defmodule ElixirAi.PageToolsPG do
|
||||
@moduledoc """
|
||||
Named :pg scope for tracking LiveViews that implement AiControllable.
|
||||
Group key is `{:page, voice_session_id}` — one group per browser session.
|
||||
"""
|
||||
|
||||
def child_spec(_opts) do
|
||||
%{
|
||||
id: __MODULE__,
|
||||
start: {:pg, :start_link, [__MODULE__]},
|
||||
type: :worker,
|
||||
restart: :permanent
|
||||
}
|
||||
end
|
||||
end
|
||||
11
lib/elixir_ai_web/utils/spinner.ex
Normal file
11
lib/elixir_ai_web/utils/spinner.ex
Normal file
@@ -0,0 +1,11 @@
|
||||
defmodule ElixirAiWeb.Spinner do
|
||||
use Phoenix.Component
|
||||
|
||||
attr :class, :string, default: nil
|
||||
|
||||
def spinner(assigns) do
|
||||
~H"""
|
||||
<span class={["loader", @class]}></span>
|
||||
"""
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user