diff --git a/browser_use/browser_use_fullstack_runtime/backend/agentscope_browseruse_agent.py b/browser_use/browser_use_fullstack_runtime/backend/agentscope_browseruse_agent.py index 3e913e4..20eddab 100644 --- a/browser_use/browser_use_fullstack_runtime/backend/agentscope_browseruse_agent.py +++ b/browser_use/browser_use_fullstack_runtime/backend/agentscope_browseruse_agent.py @@ -1,56 +1,41 @@ # -*- coding: utf-8 -*- +import functools import os +import threading + from typing import List, Dict, AsyncGenerator +from openai import OpenAI + from agentscope.agent import ReActAgent +from agentscope.formatter import DashScopeChatFormatter +from agentscope.message import TextBlock from agentscope.model import DashScopeChatModel -from agentscope_runtime.engine import Runner -from agentscope_runtime.engine.agents.agentscope_agent import AgentScopeAgent -from agentscope_runtime.engine.schemas.agent_schemas import ( - AgentRequest, - RunStatus, -) -from agentscope_runtime.engine.services import SandboxService -from agentscope_runtime.engine.services.context_manager import ContextManager -from agentscope_runtime.engine.services.environment_manager import ( - EnvironmentManager, -) -from agentscope_runtime.engine.services.memory_service import ( - InMemoryMemoryService, -) -from agentscope_runtime.engine.services.session_history_service import ( - InMemorySessionHistoryService, -) -from agentscope_runtime.sandbox.tools.browser import ( - browser_click, - browser_close, - browser_console_messages, - browser_drag, - browser_file_upload, - browser_handle_dialog, - browser_hover, - browser_navigate, - browser_navigate_back, - browser_navigate_forward, - browser_network_requests, - browser_pdf_save, - browser_press_key, - browser_resize, - browser_select_option, - browser_snapshot, - browser_tab_close, - browser_tab_list, - browser_tab_new, - browser_tab_select, - browser_take_screenshot, - browser_type, - browser_wait_for, - run_ipython_cell, - run_shell_command, -) +from agentscope.pipeline import stream_printing_messages +from agentscope.tool import Toolkit, ToolResponse from prompts import SYSTEM_PROMPT +from agentscope_runtime.adapters.agentscope.memory import ( + AgentScopeSessionHistoryMemory, +) +from agentscope_runtime.engine import AgentApp +from agentscope_runtime.engine.schemas.agent_schemas import ( + AgentRequest, +) +from agentscope_runtime.engine.services.agent_state.state_service import ( + InMemoryStateService, +) +from agentscope_runtime.engine.services.sandbox.sandbox_service import ( + SandboxService, +) + +# flake8: noqa: E501 +from agentscope_runtime.engine.services.session_history.session_history_service import ( # pylint: disable=line-too-long + InMemorySessionHistoryService, # pylint: disable=line-too-long +) + + if os.path.exists(".env"): from dotenv import load_dotenv @@ -59,116 +44,162 @@ if os.path.exists(".env"): USER_ID = "user_1" SESSION_ID = "session_001" # Using a fixed ID for simplicity +PORT = 8090 -class AgentscopeBrowseruseAgent: - def __init__(self) -> None: - self.tools = [ - run_shell_command, - run_ipython_cell, - browser_close, - browser_resize, - browser_console_messages, - browser_handle_dialog, - browser_file_upload, - browser_press_key, - browser_navigate, - browser_navigate_back, - browser_navigate_forward, - browser_network_requests, - browser_pdf_save, - browser_take_screenshot, - browser_snapshot, - browser_click, - browser_drag, - browser_hover, - browser_type, - browser_select_option, - browser_tab_list, - browser_tab_new, - browser_tab_select, - browser_tab_close, - browser_wait_for, + +def sandbox_tool_adapter(func): + """ + Sandbox Tool Adapter. + Wraps a sandbox tool function so that its output is always converted + into a ToolResponse object, which is required by the Toolkit. + This adapter preserves the original function signature and docstring + so that JSON schemas can be correctly generated by the Toolkit. + Args: + func: Original sandbox tool function (may return dict, string, etc.). + Returns: + A callable that produces ToolResponse instead of raw data. + """ + + @functools.wraps(func) + def wrapper(*args, **kwargs): + res = func(*args, **kwargs) + if isinstance(res, ToolResponse): + return res + # TODO: fix this + return ToolResponse(content=[TextBlock(type="text", text=str(res))]) + + return wrapper + + +desktop_url = "" + + +def init(): + agent_app = AgentApp( + app_name="Friday", + app_description="A helpful assistant", + ) + + @agent_app.init + async def init_func(self): + self.state_service = InMemoryStateService() + self.session_service = InMemorySessionHistoryService() + self.sandbox_service = SandboxService() + + await self.state_service.start() + await self.session_service.start() + await self.sandbox_service.start() + + @agent_app.shutdown + async def shutdown_func(self): + await self.state_service.stop() + await self.session_service.stop() + await self.sandbox_service.stop() + + @agent_app.query(framework="agentscope") + async def query_func( + self, + msgs, + request: AgentRequest = None, + ): + session_id = request.session_id + user_id = request.user_id + + state = await self.state_service.export_state( + session_id=session_id, + user_id=user_id, + ) + # Get sandbox + sandboxes = self.sandbox_service.connect( + session_id=session_id, + user_id=user_id, + sandbox_types=["browser"], + ) + + sandbox = sandboxes[0] + global desktop_url + desktop_url = sandbox.desktop_url + + browser_tools = [ + sandbox.browser_navigate, + sandbox.browser_take_screenshot, + sandbox.browser_snapshot, + sandbox.browser_click, + sandbox.browser_type, ] - self.agent = AgentScopeAgent( + + toolkit = Toolkit() + for tool in browser_tools: + toolkit.register_tool_function(sandbox_tool_adapter(tool)) + + agent = ReActAgent( name="Friday", model=DashScopeChatModel( "qwen-max", api_key=os.getenv("DASHSCOPE_API_KEY"), + enable_thinking=True, + stream=True, ), - agent_config={ - "sys_prompt": SYSTEM_PROMPT, - }, - tools=self.tools, - agent_builder=ReActAgent, + sys_prompt=SYSTEM_PROMPT, + toolkit=toolkit, + memory=AgentScopeSessionHistoryMemory( + service=self.session_service, + session_id=session_id, + user_id=user_id, + ), + formatter=DashScopeChatFormatter(), ) - async def connect(self) -> None: - session_history_service = InMemorySessionHistoryService() + if state: + agent.load_state_dict(state) - await session_history_service.create_session( - user_id=USER_ID, - session_id=SESSION_ID, + async for msg, last in stream_printing_messages( + agents=[agent], + coroutine_task=agent(msgs), + ): + yield msg, last + + state = agent.state_dict() + + await self.state_service.save_state( + user_id=user_id, + session_id=session_id, + state=state, ) - self.mem_service = InMemoryMemoryService() - await self.mem_service.start() - self.sandbox_service = SandboxService() - await self.sandbox_service.start() + def run_agent_app(): + agent_app.run(host="127.0.0.1", port=PORT) - self.context_manager = ContextManager( - memory_service=self.mem_service, - session_history_service=session_history_service, - ) - self.environment_manager = EnvironmentManager( - sandbox_service=self.sandbox_service, - ) - sandboxes = self.sandbox_service.connect( - session_id=SESSION_ID, - user_id=USER_ID, - tools=self.tools, - ) + threading.Thread(target=run_agent_app, daemon=True).start() + return agent_app - if len(sandboxes) > 0: - sandbox = sandboxes[0] - self.desktop_url = sandbox.desktop_url - else: - self.desktop_url = "" - runner = Runner( - agent=self.agent, - context_manager=self.context_manager, - environment_manager=self.environment_manager, - ) - self.runner = runner +class AgentscopeBrowseruseAgent: + def __init__(self) -> None: + self.agent = init() + self.desktop_url = "" async def chat( self, chat_messages: List[Dict], ) -> AsyncGenerator[Dict, None]: - convert_messages = [] - for chat_message in chat_messages: - convert_messages.append( - { - "role": chat_message["role"], - "content": [ - { - "type": "text", - "text": chat_message["content"], - }, - ], - }, - ) - request = AgentRequest(input=convert_messages, session_id=SESSION_ID) - request.tools = [] - async for message in self.runner.stream_query( - user_id=USER_ID, - request=request, - ): - if ( - message.object == "message" - and RunStatus.Completed == message.status - ): - yield message.content + client = OpenAI(base_url=f"http://127.0.0.1:{PORT}/compatible-mode/v1") + + stream = client.responses.create( + model="any_name", + input=chat_messages[-1]["content"], + stream=True, + ) + global desktop_url + + self.desktop_url = desktop_url + for chunk in stream: + if hasattr(chunk, "delta"): + yield chunk.delta + else: + yield {} + # if chunk.choices[0].delta.content is not None: + # yield chunk.choices[0].delta.content async def close(self) -> None: await self.sandbox_service.stop() diff --git a/browser_use/browser_use_fullstack_runtime/backend/async_quart_service.py b/browser_use/browser_use_fullstack_runtime/backend/async_quart_service.py index a81d668..a63b186 100644 --- a/browser_use/browser_use_fullstack_runtime/backend/async_quart_service.py +++ b/browser_use/browser_use_fullstack_runtime/backend/async_quart_service.py @@ -1,17 +1,18 @@ # -*- coding: utf-8 -*- -import asyncio import json import logging import os import time +from quart import Quart, Response, jsonify, request +from quart_cors import cors + from agentscope_browseruse_agent import AgentscopeBrowseruseAgent from agentscope_runtime.engine.schemas.agent_schemas import ( DataContent, TextContent, ) -from quart import Quart, Response, jsonify, request -from quart_cors import cors + app = Quart(__name__) app = cors(app, allow_origin="*") @@ -35,9 +36,8 @@ if os.path.exists(".env"): async def user_mode(input_data): messages = input_data.get("messages", []) last_name = "" - async for item_list in agent.chat(messages): - if item_list: - item = item_list[0] + async for item in agent.chat(messages): + if item: res = "" if isinstance(item, TextContent): res = item.text @@ -48,8 +48,9 @@ async def user_mode(input_data): continue res = "I will use the tool" + json.dumps(item.data["name"]) last_name = json.dumps(item.data["name"]) - - yield simple_yield(res + "\n") + elif isinstance(item, str): + res = item + yield simple_yield(res) else: yield simple_yield() @@ -105,5 +106,5 @@ async def get_env_info(): if __name__ == "__main__": - asyncio.run(agent.connect()) + agent.chat([{"role": "user", "content": "hello"}]) app.run(host="0.0.0.0", port=9000) diff --git a/browser_use/browser_use_fullstack_runtime/backend/requirements.txt b/browser_use/browser_use_fullstack_runtime/backend/requirements.txt index 15dd1ac..3a39251 100644 --- a/browser_use/browser_use_fullstack_runtime/backend/requirements.txt +++ b/browser_use/browser_use_fullstack_runtime/backend/requirements.txt @@ -1,5 +1,6 @@ pyyaml>=6.0.2 quart>=0.8.0 quart-cors>=0.8.0 -agentscope-runtime==0.2.0 +agentscope-runtime>=1.0.0 agentscope[full]>=1.0.5 +openai>=2.8.1 \ No newline at end of file diff --git a/browser_use/browser_use_fullstack_runtime/frontend/src/App.tsx b/browser_use/browser_use_fullstack_runtime/frontend/src/App.tsx index 7bebbd1..558e7e8 100644 --- a/browser_use/browser_use_fullstack_runtime/frontend/src/App.tsx +++ b/browser_use/browser_use_fullstack_runtime/frontend/src/App.tsx @@ -94,16 +94,19 @@ const App: React.FC = () => { setMessages(newMessages); setIsTyping(true); + await processMessageToChatGPT(newMessages); + await get_desktop_url(); }; async function processMessageToChatGPT(chatMessages: ChatMessage) { - let apiMessages = chatMessages + const apiMessages = chatMessages .map((messageObject) => { if (messageObject.message.trim() === "") { return null; } - let role = messageObject.sender === "assistant" ? "assistant" : "user"; + const role = + messageObject.sender === "assistant" ? "assistant" : "user"; return { role, content: messageObject.message }; }) .filter(Boolean); @@ -143,7 +146,9 @@ const App: React.FC = () => { ]); while (true) { const { done, value } = await reader.read(); - if (done) break; + if (done) { + break; + } const chunk = decoder.decode(value); accumulatedMessage += chunk; @@ -152,7 +157,9 @@ const App: React.FC = () => { accumulatedMessage = lines.pop() || ""; for (const line of lines) { - if (line.trim() === "") continue; + if (line.trim() === "") { + continue; + } try { const parsed = JSON.parse(line.split("data: ")[1]); diff --git a/conversational_agents/chatbot_fullstack_runtime/backend/.env.template b/conversational_agents/chatbot_fullstack_runtime/backend/.env.template index 6cac48c..5ac3956 100644 --- a/conversational_agents/chatbot_fullstack_runtime/backend/.env.template +++ b/conversational_agents/chatbot_fullstack_runtime/backend/.env.template @@ -1,6 +1,5 @@ DASHSCOPE_API_KEY= -DASHSCOPE_BASE_URL= +DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 SERVER_PORT=8080 -SERVER_ENDPOINT=agent SERVER_HOST=localhost USER_MANAGER_STORAGE=user.json \ No newline at end of file diff --git a/conversational_agents/chatbot_fullstack_runtime/backend/agent_server.py b/conversational_agents/chatbot_fullstack_runtime/backend/agent_server.py index 9017746..f1de2e3 100644 --- a/conversational_agents/chatbot_fullstack_runtime/backend/agent_server.py +++ b/conversational_agents/chatbot_fullstack_runtime/backend/agent_server.py @@ -1,70 +1,109 @@ # -*- coding: utf-8 -*- -import asyncio +# pylint:disable=redefined-outer-name, unused-argument + + import os +from agentscope.formatter import DashScopeChatFormatter +from agentscope.tool import Toolkit, execute_python_code +from agentscope.pipeline import stream_printing_messages + from agentscope.agent import ReActAgent from agentscope.model import DashScopeChatModel -from agentscope_runtime.engine import LocalDeployManager, Runner -from agentscope_runtime.engine.agents.agentscope_agent import AgentScopeAgent -from agentscope_runtime.engine.services.context_manager import ContextManager + +from agentscope_runtime.engine import AgentApp +from agentscope_runtime.engine.schemas.agent_schemas import AgentRequest +from agentscope_runtime.adapters.agentscope.memory import ( + AgentScopeSessionHistoryMemory, +) +from agentscope_runtime.engine.services.agent_state import ( + InMemoryStateService, +) +from agentscope_runtime.engine.services.session_history import ( + InMemorySessionHistoryService, +) def local_deploy(): - asyncio.run(_local_deploy()) - - -async def _local_deploy(): from dotenv import load_dotenv load_dotenv() server_port = int(os.environ.get("SERVER_PORT", "8090")) - server_endpoint = os.environ.get("SERVER_ENDPOINT", "agent") - model = DashScopeChatModel( - model_name="qwen-turbo", - api_key=os.getenv("DASHSCOPE_API_KEY"), - ) - agent = AgentScopeAgent( - name="Friday", - model=model, - agent_config={ - "sys_prompt": "A simple LLM agent to generate a short response", - }, - agent_builder=ReActAgent, + # server_endpoint = os.environ.get("SERVER_ENDPOINT", "process") + + agent_app = AgentApp( + app_name="Friday", + app_description="A simple LLM agent to generate a short response", ) - context_manager = ContextManager() + @agent_app.init + async def init_func(self): + self.state_service = InMemoryStateService() + self.session_service = InMemorySessionHistoryService() - runner = Runner( - agent=agent, - context_manager=context_manager, - ) + await self.state_service.start() + await self.session_service.start() - deploy_manager = LocalDeployManager(host="localhost", port=server_port) - try: - deployment_info = await runner.deploy( - deploy_manager, - endpoint_path=f"/{server_endpoint}", + @agent_app.shutdown + async def shutdown_func(self): + await self.state_service.stop() + await self.session_service.stop() + + @agent_app.query(framework="agentscope") + async def query_func( + self, + msgs, + request: AgentRequest = None, + **kwargs, + ): + session_id = request.session_id + user_id = request.user_id + + state = await self.state_service.export_state( + session_id=session_id, + user_id=user_id, ) - print("✅ Service deployed successfully!") - print(f" URL: {deployment_info['url']}") - print(f" Endpoint: {deployment_info['url']}/{server_endpoint}") - print("\nAgent Service is running in the background.") + toolkit = Toolkit() + toolkit.register_tool_function(execute_python_code) - while True: - await asyncio.sleep(1) + agent = ReActAgent( + name="Friday", + model=DashScopeChatModel( + "qwen-turbo", + api_key=os.getenv("DASHSCOPE_API_KEY"), + enable_thinking=True, + stream=True, + ), + sys_prompt="You're a helpful assistant named Friday.", + toolkit=toolkit, + memory=AgentScopeSessionHistoryMemory( + service=self.session_service, + session_id=session_id, + user_id=user_id, + ), + formatter=DashScopeChatFormatter(), + ) - except (KeyboardInterrupt, asyncio.CancelledError): - # This block will be executed when you press Ctrl+C. - print("\nShutdown signal received. Stopping the service...") - if deploy_manager.is_running: - await deploy_manager.stop() - print("✅ Service stopped.") - except Exception as e: - print(f"An error occurred: {e}") - if deploy_manager.is_running: - await deploy_manager.stop() + if state: + agent.load_state_dict(state) + + async for msg, last in stream_printing_messages( + agents=[agent], + coroutine_task=agent(msgs), + ): + yield msg, last + + state = agent.state_dict() + + await self.state_service.save_state( + user_id=user_id, + session_id=session_id, + state=state, + ) + + agent_app.run(host="127.0.0.1", port=server_port) if __name__ == "__main__": diff --git a/conversational_agents/chatbot_fullstack_runtime/backend/requirements.txt b/conversational_agents/chatbot_fullstack_runtime/backend/requirements.txt index 2aab90b..97ee324 100644 --- a/conversational_agents/chatbot_fullstack_runtime/backend/requirements.txt +++ b/conversational_agents/chatbot_fullstack_runtime/backend/requirements.txt @@ -1,5 +1,6 @@ flask>=3.1.2 flask_cors>=6.0.1 -agentscope-runtime==0.2.0 +agentscope-runtime>=1.0.0 agentscope-runtime[agentscope] -flask_sqlalchemy>=3.1.1 \ No newline at end of file +flask_sqlalchemy>=3.1.1 +openai>=2.8.1 \ No newline at end of file diff --git a/conversational_agents/chatbot_fullstack_runtime/backend/web_server.py b/conversational_agents/chatbot_fullstack_runtime/backend/web_server.py index 9ce54db..ea220de 100644 --- a/conversational_agents/chatbot_fullstack_runtime/backend/web_server.py +++ b/conversational_agents/chatbot_fullstack_runtime/backend/web_server.py @@ -5,6 +5,8 @@ import os from datetime import datetime import requests + +from openai import OpenAI from dotenv import load_dotenv from flask import Flask, request, jsonify from flask_cors import CORS @@ -152,29 +154,25 @@ def sse_client(url, data=None): pass -def call_runner(query, query_user_id, query_session_id): +def call_runner(query): server_port = int(os.environ.get("SERVER_PORT", "8090")) - server_endpoint = os.environ.get("SERVER_ENDPOINT", "agent") server_host = os.environ.get("SERVER_HOST", "localhost") - url = f"http://{server_host}:{server_port}/{server_endpoint}" - data_arg = { - "input": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": query, - }, - ], - }, - ], - "session_id": query_session_id, - "user_id": query_user_id, - } - for content in sse_client(url, data=data_arg): - yield content + client = OpenAI( + base_url=f"http://{server_host}:{server_port}/compatible-mode/v1", + ) + + stream = client.responses.create( + model="any_name", + input=query, + stream=True, + ) + + for chunk in stream: + if hasattr(chunk, "delta"): + yield chunk.delta + else: + yield "" # API routes @@ -359,11 +357,9 @@ def send_message(conversation_id): ai_response_text = "" question = text - conversation_id_str = str(conversation_id) + for item in call_runner( question, - conversation_id_str, - conversation_id_str, ): ai_response_text += item diff --git a/conversational_agents/chatbot_fullstack_runtime/frontend/src/App.jsx b/conversational_agents/chatbot_fullstack_runtime/frontend/src/App.jsx index 9729df8..e9ef3a6 100644 --- a/conversational_agents/chatbot_fullstack_runtime/frontend/src/App.jsx +++ b/conversational_agents/chatbot_fullstack_runtime/frontend/src/App.jsx @@ -1,24 +1,24 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { MessageCircle, User, Send, Plus, LogOut, Menu, X, Bot } from 'lucide-react'; +import React, { useState, useEffect, useRef } from "react"; +import { MessageCircle, User, Send, Plus, LogOut, Menu, X, Bot } from "lucide-react"; const App = () => { const [isLoggedIn, setIsLoggedIn] = useState(false); - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); const [currentUser, setCurrentUser] = useState(null); const [conversations, setConversations] = useState([]); const [activeConversation, setActiveConversation] = useState(null); - const [message, setMessage] = useState(''); + const [message, setMessage] = useState(""); const [isMenuOpen, setIsMenuOpen] = useState(false); const [loading, setLoading] = useState(false); const messagesEndRef = useRef(null); // API base URL - const API_BASE = 'http://localhost:5100/api'; + const API_BASE = "http://localhost:5100/api"; // Auto scroll to bottom of messages useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [activeConversation?.messages]); // Fetch user conversations @@ -34,7 +34,7 @@ const App = () => { } } } catch (error) { - console.error('Error fetching conversations:', error); + console.error("Error fetching conversations:", error); } }; @@ -47,7 +47,7 @@ const App = () => { setActiveConversation(data); } } catch (error) { - console.error('Error loading conversation:', error); + console.error("Error loading conversation:", error); } }; @@ -58,9 +58,9 @@ const App = () => { try { const response = await fetch(`${API_BASE}/login`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify({ username, password }), }); @@ -72,11 +72,11 @@ const App = () => { await fetchConversations(userData.id); } else { const errorData = await response.json(); - alert(errorData.error || 'Login failed'); + alert(errorData.error || "Login failed"); } } catch (error) { - console.error('Login error:', error); - alert('Network error. Please try again.'); + console.error("Login error:", error); + alert("Network error. Please try again."); } finally { setLoading(false); } @@ -86,8 +86,8 @@ const App = () => { const handleLogout = () => { setIsLoggedIn(false); setCurrentUser(null); - setUsername(''); - setPassword(''); + setUsername(""); + setPassword(""); setConversations([]); setActiveConversation(null); setIsMenuOpen(false); @@ -95,16 +95,18 @@ const App = () => { // Create new conversation const createNewConversation = async () => { - if (!currentUser) return; + if (!currentUser) { + return; + } setLoading(true); try { const response = await fetch(`${API_BASE}/users/${currentUser.id}/conversations`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, - body: JSON.stringify({ title: 'New Conversation' }), + body: JSON.stringify({ title: "New Conversation" }), }); if (response.ok) { @@ -113,7 +115,7 @@ const App = () => { await loadConversation(newConversation.id); } } catch (error) { - console.error('Error creating conversation:', error); + console.error("Error creating conversation:", error); } finally { setLoading(false); setIsMenuOpen(false); @@ -122,17 +124,19 @@ const App = () => { // Send message const sendMessage = async () => { - if (!message.trim() || !activeConversation) return; + if (!message.trim() || !activeConversation) { + return; + } setLoading(true); try { // Send user message const userMessageResponse = await fetch(`${API_BASE}/conversations/${activeConversation.id}/messages`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, - body: JSON.stringify({ text: message, sender: 'user' }), + body: JSON.stringify({ text: message, sender: "user" }), }); if (userMessageResponse.ok) { @@ -142,16 +146,16 @@ const App = () => { const updatedConversation = { ...activeConversation, messages: [...activeConversation.messages, userMessage], - title: activeConversation.messages.length === 1 ? message.slice(0, 20) + (message.length > 20 ? '...' : '') : activeConversation.title + title: activeConversation.messages.length === 1 ? message.slice(0, 20) + (message.length > 20 ? "..." : "") : activeConversation.title }; setActiveConversation(updatedConversation); - setMessage(''); + setMessage(""); // Fetch updated conversation to get AI response await loadConversation(activeConversation.id); } } catch (error) { - console.error('Error sending message:', error); + console.error("Error sending message:", error); } finally { setLoading(false); } @@ -159,9 +163,9 @@ const App = () => { // Format timestamp const formatTime = (timestamp) => { - return new Date(timestamp).toLocaleTimeString('en-US', { - hour: '2-digit', - minute: '2-digit' + return new Date(timestamp).toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit" }); }; @@ -206,7 +210,7 @@ const App = () => { disabled={loading} className="w-full bg-indigo-600 text-white py-3 rounded-lg font-medium hover:bg-indigo-700 transition-colors focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-50" > - {loading ? 'Logging in...' : 'Login'} + {loading ? "Logging in..." : "Login"} @@ -283,7 +287,7 @@ const App = () => { setIsMenuOpen(false); }} className={`p-4 border-b border-gray-100 cursor-pointer hover:bg-gray-50 transition-colors ${ - activeConversation?.id === conversation.id ? 'bg-indigo-50 border-l-4 border-l-indigo-500' : '' + activeConversation?.id === conversation.id ? "bg-indigo-50 border-l-4 border-l-indigo-500" : "" }`} >
- {conversation.preview || 'New conversation'} + {conversation.preview || "New conversation"}
{msg.text}
-+
{formatTime(msg.created_at)}