diff --git a/README.md b/README.md index c04c9d1..0f3081c 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,12 @@ if we build this from scratch than i dont even need supabase ## backlog -- [ ] add cyberpunk mode to map - if the user clicks one of the major cities here futuristic gen ai images of the city are displayed \ No newline at end of file +- [ ] add cyberpunk mode to map - if the user clicks one of the major cities here futuristic gen ai images of the city are displayed + - [ ] make all the spots which have a name in the map already like golden gate park + - [ ] make everything clickable + - [ ] show current location +- [ ] add star on github button +- [ ] add more agentic functions + - [ ] go to X + +returning functions to call from the chatinterface would be the best \ No newline at end of file diff --git a/api/index.py b/api/index.py index ec01dc0..44287f5 100644 --- a/api/index.py +++ b/api/index.py @@ -1,11 +1,14 @@ -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect +from typing import List from fastapi.responses import StreamingResponse from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from supabase import create_client, Client -from openai import AsyncOpenAI +from openai import AsyncOpenAI, OpenAI import os import logging +import json +from pydantic import BaseModel # Set up logging logging.basicConfig(level=logging.INFO) @@ -40,7 +43,6 @@ async def get_events(): if not supabase: logger.error("Supabase client not initialized") raise HTTPException(status_code=500, detail="Supabase client not initialized") - try: response = supabase.table("resident_advisor").select("*").order('created_at', desc=True).limit(1).execute() logger.info(f"Supabase response: {response}") @@ -57,11 +59,69 @@ async def get_events(): logger.error(f"Exception occurred: {e}") raise HTTPException(status_code=500, detail=str(e)) +tools = [ + { + "type": "function", + "function": { + "name": "go_to_place_in_map", + "description": "Go to a place in the map", + "parameters": { + "type": "object", + "properties": { + "place": { + "type": "string", + "description": "The name of the place to navigate to.", + } + }, + "required": ["place"], + }, + }, + } +] -#how to build a good agent system with tools - thinking of just using openai function calling +def check_for_function_call(): + system=f""" + You are a chatbot on top of a map. Your job is to help the user navigate the map. You can use the available functions to help you with this task. + """ + #todo this needs to be messages from the frontend + user_query="Go to berlin." + user_content=f""" + You are giving a chat conversation and based on this conversation you need to decide if an function from the available functions needs to be called. the available functions are: + + --- + {tools} + --- + + in the case a function can be called return the function name and the arguments that need to be passed to the function in JSON. Its important that the name of the key for the function name is `function_name` and the name of the key for the arguments is `arguments`. In the case a function cannot be called return an empty JSON object. + + Following the chat conversation: + --- + {user_query} + --- + """ + completion = client.chat.completions.create( + model="gpt-3.5-turbo", + response_format={ "type": "json_object" }, + temperature=0.0, + messages=[ + {"role": "system", "content": system}, + {"role": "user", "content": user_content} + ] + ) + response_content = completion.choices[0].message.content + return response_content + +def final_response(): + + pass + + +#how to build a good agent system with tools - thinking of just using openai function calling @app.post("/api/ask") async def ask(req: dict): + print("req: ", req) + #check_for_function_call() logger.info("Received request for /api/ask with data: %s", req) try: stream = await client.chat.completions.create( @@ -69,7 +129,6 @@ async def ask(req: dict): model="gpt-3.5-turbo", stream=True, ) - async def generator(): async for chunk in stream: yield chunk.choices[0].delta.content or "" diff --git a/api/tools.py b/api/tools.py new file mode 100644 index 0000000..e69de29 diff --git a/public/app.js b/public/app.js index 553eaa6..1f43b54 100644 --- a/public/app.js +++ b/public/app.js @@ -14,6 +14,53 @@ function initializeMap() { }); } +function initializeWebSocket() { + const socket = new WebSocket("ws://localhost:8000/ws"); + + socket.onopen = function(event) { + console.log("WebSocket is open now."); + }; + + socket.onmessage = function(event) { + console.log("WebSocket message received:", event.data); + test(event.data); // Call the test function with the received message + }; + + socket.onclose = function(event) { + console.log("WebSocket is closed now."); + }; + + socket.onerror = function(error) { + console.error("WebSocket error observed:", error); + }; +} + +async function go_to_place(placeName = 'Berlin') { + //all what needs to happen is that this endpoint is getting called + const geocodingUrl = `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(placeName)}.json?access_token=${mapboxgl.accessToken}`; + + try { + const response = await fetch(geocodingUrl); + const data = await response.json(); + + if (data.features && data.features.length > 0) { + const coordinates = data.features[0].center; + + map.flyTo({ + center: coordinates, + zoom: 10, // Adjust this value to set the desired zoom level + essential: true // This animation is considered essential with respect to prefers-reduced-motion + }); + } else { + console.error('Location not found'); + } + } catch (error) { + console.error('Error fetching location:', error); + } +} + + + function loadEvents() { fetch('/api/events') .then(response => response.json()) @@ -47,4 +94,7 @@ function displayEvents(events) { } // Call initializeMap when the window loads -window.onload = initializeMap; \ No newline at end of file +window.onload = function() { + initializeMap(); + initializeWebSocket(); +}; \ No newline at end of file diff --git a/public/chat.js b/public/chat.js index 052f8cd..8f4d971 100644 --- a/public/chat.js +++ b/public/chat.js @@ -3,8 +3,7 @@ document.addEventListener("DOMContentLoaded", function() { const chatInput = document.getElementById("chatInput"); const chatMessages = document.getElementById("chatMessages"); let isStreaming = false; // Flag to track if a response is being streamed - - //const baseURL = window.location.hostname === "localhost" ? "http://localhost:8000" : "https://www.pantheon.so"; + let chatHistory = []; // Array to store the chat history function sendMessage() { if (isStreaming) { @@ -18,6 +17,9 @@ document.addEventListener("DOMContentLoaded", function() { return; } + // Add user message to chat history + chatHistory.push({"role": "user", "content": message}); + // Append user message to chat appendMessage("user", message); @@ -32,9 +34,7 @@ document.addEventListener("DOMContentLoaded", function() { "Content-Type": "application/json" }, body: JSON.stringify({ - messages: [ - {"role": "user", "content": message} - ] + messages: chatHistory // Send the entire chat history }) }) .then(response => { @@ -62,6 +62,8 @@ document.addEventListener("DOMContentLoaded", function() { reader.read().then(({ done, value }) => { if (done) { console.log("Final result:", result); + // Add assistant's response to chat history + chatHistory.push({"role": "assistant", "content": result}); isStreaming = false; // Set the flag to false when streaming is complete return; } @@ -76,7 +78,6 @@ document.addEventListener("DOMContentLoaded", function() { isStreaming = false; // Set the flag to false if an error occurs }); } - read(); }) .catch(error => {