defmodule SuperintelligenceWeb.ChatLive do use SuperintelligenceWeb, :live_view alias Superintelligence.Conversations alias Superintelligence.Conversations.{Conversation, Message} alias Superintelligence.AIAgent @impl true def mount(_params, _session, socket) do if connected?(socket) do Conversations.subscribe() end conversations = Conversations.list_conversations() socket = socket |> assign(:conversations, conversations) |> assign(:current_conversation, nil) |> assign(:messages, []) |> assign(:message_input, "") |> assign(:processing, false) |> assign(:visualization_data, nil) {:ok, socket} end @impl true def handle_params(%{"id" => id}, _uri, socket) do case Conversations.get_conversation(id) do {:ok, conversation} -> if connected?(socket) do Conversations.subscribe_to_conversation(id) end messages = Conversations.list_messages(id) {:noreply, socket |> assign(:current_conversation, conversation) |> assign(:messages, messages)} {:error, :not_found} -> {:noreply, socket |> put_flash(:error, "Conversation not found") |> push_navigate(to: ~p"/chat")} end end def handle_params(_params, _uri, socket) do {:noreply, socket} end @impl true def handle_event("new_conversation", _params, socket) do {:ok, conversation} = Conversations.create_conversation() {:noreply, socket |> push_navigate(to: ~p"/chat/#{conversation.id}")} end @impl true def handle_event("select_conversation", %{"id" => id}, socket) do {:noreply, push_navigate(socket, to: ~p"/chat/#{id}")} end @impl true def handle_event("delete_conversation", %{"id" => id}, socket) do with {:ok, conversation} <- Conversations.get_conversation(id), {:ok, _} <- Conversations.delete_conversation(conversation) do socket = if socket.assigns.current_conversation && socket.assigns.current_conversation.id == id do socket |> assign(:current_conversation, nil) |> assign(:messages, []) |> push_navigate(to: ~p"/chat") else socket end {:noreply, socket} else _ -> {:noreply, put_flash(socket, :error, "Failed to delete conversation")} end end @impl true def handle_event("update_message_input", %{"value" => value}, socket) do {:noreply, assign(socket, :message_input, value)} end @impl true def handle_event("send_message", _params, socket) do if socket.assigns.current_conversation && String.trim(socket.assigns.message_input) != "" do # Create user message {:ok, _user_message} = Conversations.create_message( socket.assigns.current_conversation.id, %{ role: "user", content: socket.assigns.message_input } ) # Process with AI agent socket = socket |> assign(:message_input, "") |> assign(:processing, true) Task.start(fn -> process_ai_response( socket.assigns.current_conversation.id, socket.assigns.message_input ) end) {:noreply, socket} else {:noreply, socket} end end @impl true def handle_event("clear_visualization", _params, socket) do {:noreply, socket |> assign(:visualization_data, nil) |> push_event("draw", %{type: "clear", data: %{}})} end @impl true def handle_event("demo_visualization", %{"type" => type}, socket) do data = case type do "chart" -> %{ type: "bar", labels: ["Jan", "Feb", "Mar", "Apr", "May"], values: [65, 59, 80, 81, 56] } "custom" -> %{ commands: [ %{method: "beginPath", args: []}, %{method: "arc", args: [150, 150, 50, 0, 2 * :math.pi()]}, %{method: "fillStyle", args: ["#4F46E5"]}, %{method: "fill", args: []} ] } _ -> %{} end {:noreply, socket |> assign(:visualization_data, data) |> push_event("draw", %{type: type, data: data})} end @impl true def handle_info({:conversation_created, conversation}, socket) do conversations = [conversation | socket.assigns.conversations] {:noreply, assign(socket, :conversations, conversations)} end @impl true def handle_info({:conversation_updated, conversation}, socket) do conversations = socket.assigns.conversations |> Enum.map(fn c -> if c.id == conversation.id, do: conversation, else: c end) |> Enum.sort_by(& &1.updated_at, {:desc, DateTime}) socket = assign(socket, :conversations, conversations) socket = if socket.assigns.current_conversation && socket.assigns.current_conversation.id == conversation.id do assign(socket, :current_conversation, conversation) else socket end {:noreply, socket} end @impl true def handle_info({:conversation_deleted, conversation}, socket) do conversations = socket.assigns.conversations |> Enum.reject(fn c -> c.id == conversation.id end) {:noreply, assign(socket, :conversations, conversations)} end @impl true def handle_info({:message_created, message}, socket) do if socket.assigns.current_conversation && message.conversation_id == socket.assigns.current_conversation.id do messages = socket.assigns.messages ++ [message] socket = socket |> assign(:messages, messages) |> assign(:processing, false) # Handle visualization commands in AI responses socket = if message.role == "assistant" && message.metadata[:visualization] do push_event(socket, "draw", message.metadata[:visualization]) else socket end {:noreply, socket} else {:noreply, socket} end end defp process_ai_response(conversation_id, user_message) do # Simulate AI processing Process.sleep(1000) # Generate response based on message content {response, visualization} = generate_ai_response(user_message) metadata = if visualization, do: %{visualization: visualization}, else: %{} Conversations.create_message( conversation_id, %{ role: "assistant", content: response, metadata: metadata } ) end defp generate_ai_response(message) do cond do String.contains?(String.downcase(message), ["chart", "graph", "visualiz"]) -> { "I'll create a visualization for you. Here's a sample chart showing monthly data:", %{ type: "chart", data: %{ type: "bar", labels: ["Jan", "Feb", "Mar", "Apr", "May"], values: Enum.map(1..5, fn _ -> :rand.uniform(100) end) } } } String.contains?(String.downcase(message), "hello") -> {"Hello! I'm your AI assistant. I can help you with various tasks and create visualizations. How can I assist you today?", nil} true -> {"I understand your request. Let me help you with that. Feel free to ask me to create visualizations or analyze data!", nil} end end @impl true def render(assigns) do ~H"""
Select a conversation or create a new one to start chatting