From 9d87c50815d2dd490ef0049be24fb7995fe02c21 Mon Sep 17 00:00:00 2001 From: hayden outlaw Date: Tue, 5 Dec 2023 09:44:29 -0600 Subject: [PATCH 01/17] git branch test --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 47689661..3add7f39 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Work in Progress +## Tulane Dev Integration Branch + Sawt is a tool designed to bridge the communication gap between New Orleanians and their city council representatives. ## Prerequisites From f966245c0833834f65eaf5089e0ea2cfac09d2b2 Mon Sep 17 00:00:00 2001 From: hayden outlaw Date: Tue, 5 Dec 2023 09:48:56 -0600 Subject: [PATCH 02/17] adding transcription packages. should theoretically not conflict with existing code in any way --- packages/transcription/.gitignore | 8 + packages/transcription/transcribe/README.md | 19 ++ packages/transcription/transcribe/monitor.py | 71 +++++ packages/transcription/transcribe/oauth.py | 21 ++ .../transcription/transcribe/transcripts.py | 67 +++++ .../transcription/whisper-model/README.md | 28 ++ .../whisper-model/long_transcription.ipynb | 247 ++++++++++++++++++ .../transcription/whisper-model/transcribe.py | 75 ++++++ .../whisper-model/transcribe_config.yml | 12 + packages/whisper/README.md | 28 ++ packages/whisper/long_transcription.ipynb | 247 ++++++++++++++++++ packages/whisper/transcribe.py | 75 ++++++ packages/whisper/transcribe_config.yml | 12 + 13 files changed, 910 insertions(+) create mode 100644 packages/transcription/.gitignore create mode 100644 packages/transcription/transcribe/README.md create mode 100644 packages/transcription/transcribe/monitor.py create mode 100644 packages/transcription/transcribe/oauth.py create mode 100644 packages/transcription/transcribe/transcripts.py create mode 100644 packages/transcription/whisper-model/README.md create mode 100644 packages/transcription/whisper-model/long_transcription.ipynb create mode 100644 packages/transcription/whisper-model/transcribe.py create mode 100644 packages/transcription/whisper-model/transcribe_config.yml create mode 100644 packages/whisper/README.md create mode 100644 packages/whisper/long_transcription.ipynb create mode 100644 packages/whisper/transcribe.py create mode 100644 packages/whisper/transcribe_config.yml diff --git a/packages/transcription/.gitignore b/packages/transcription/.gitignore new file mode 100644 index 00000000..333e1469 --- /dev/null +++ b/packages/transcription/.gitignore @@ -0,0 +1,8 @@ +.env +.log +__pycache__/ +transcripts-data/ +audio/ +cred/ +.vscode/ + diff --git a/packages/transcription/transcribe/README.md b/packages/transcription/transcribe/README.md new file mode 100644 index 00000000..b91b8e41 --- /dev/null +++ b/packages/transcription/transcribe/README.md @@ -0,0 +1,19 @@ +## TU Capstone- Transcription + +A generic API for fetching YouTube Audio and Transcripts. + +#### Required Credentials + - YOUTUBE_API_KEY + - GOOGLE_APPLICATION_CREDENTIALS +Create a cred folder containing cred.env variables according to dotenv configuration. + +### transcripts.py +Retrieves & downloads the x-most recent video transcripts from a YouTube Channel. + +### monitor.py +Retrieves & downloads the x-most recent video audio mp4s from a YouTube Channel. Future implemention should consider using Windows Task Scheduler to periodically monitor channel for new videos. + +#### Oauth.py +Helper authentication function. + + diff --git a/packages/transcription/transcribe/monitor.py b/packages/transcription/transcribe/monitor.py new file mode 100644 index 00000000..f9863326 --- /dev/null +++ b/packages/transcription/transcribe/monitor.py @@ -0,0 +1,71 @@ +from googleapiclient.discovery import build +#import youtube_dl Has BEEN DEPRECATED BY GERMAN GOVERNMENT +import os +from dotenv import load_dotenv +from pytube import YouTube +import oauth +# Initialize the YouTube Data API client + +env_vars = oauth.import_env_vars() +YOUTUBE_API_KEY = env_vars.get('YOUTUBE_API_KEY') +youtube = build('youtube', 'v3', developerKey=YOUTUBE_API_KEY) + +# Specify the YouTube channel ID +channel_id = 'UC8oPEsQe9a0v6TdJ4K_QXoA' # New Orleans City Council + +def get_latest_videos(channel_id, max_results=5): + """ + Fetches the latest x-number of videos from a YouTube channel. + + Args: + channel_id (str): The ID of the YouTube channel to monitor. + max_results (int): The maximum number of latest videos to fetch. Default is 5. + + Returns: + list: A list of video IDs for the latest videos. + """ + # Fetch channel details to get the ID of the uploads playlist + request = youtube.channels().list( + part='contentDetails', + id=channel_id + ) + response = request.execute() + + if not response.get('items'): + raise ValueError(f"No channel found with ID {channel_id}") + + playlist_id = response['items'][0]['contentDetails']['relatedPlaylists']['uploads'] + + request = youtube.playlistItems().list( + part='snippet', + playlistId=playlist_id, + maxResults=max_results + ) + response = request.execute() + + video_ids = [item['snippet']['resourceId']['videoId'] for item in response['items']] + + return video_ids + +def download_audio(video_ids): + """ + Downloads the audio of a list of YouTube videos using pytube. + + Args: + video_ids (list): A list of YouTube video IDs to download the audio for. + + Downloads: mp4 audio files of the desired Youtube videos. + """ + for video_id in video_ids: + yt = YouTube(f'https://www.youtube.com/watch?v={video_id}') + ys = yt.streams.filter(only_audio=True).first() + + # Download the audio stream to the specified output path + print(f'Downloading audio for {video_id}...') + ys.download(output_path=r'transcripts-data\audio', filename=video_id+".mp4") + +# Get the latest videos +video_ids = get_latest_videos(channel_id, 10) + +# Download the audio of the new videos +download_audio(video_ids) \ No newline at end of file diff --git a/packages/transcription/transcribe/oauth.py b/packages/transcription/transcribe/oauth.py new file mode 100644 index 00000000..07082eac --- /dev/null +++ b/packages/transcription/transcribe/oauth.py @@ -0,0 +1,21 @@ + +import os +from dotenv import load_dotenv + +def import_env_vars(): + os.chdir(r"packages\transcription") + load_dotenv(r"cred\cred.env") + + # Get credentials from environment variables + YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY") + CLIENT_ID = os.getenv("CLIENT_ID") + CLIENT_SECRET = os.getenv("CLIENT_SECRET") + GOOGLE_APPLICATION_CREDENTIALS= os.getenv("GOOGLE_APPLICATION_CREDENTIALS") + + os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = GOOGLE_APPLICATION_CREDENTIALS + + return { "YOUTUBE_API_KEY": YOUTUBE_API_KEY, + "CLIENT_ID": CLIENT_ID, + "CLIENT_SECRET": CLIENT_SECRET, + "GOOGLE_APPLICATION_CREDENTIALS": GOOGLE_APPLICATION_CREDENTIALS + } \ No newline at end of file diff --git a/packages/transcription/transcribe/transcripts.py b/packages/transcription/transcribe/transcripts.py new file mode 100644 index 00000000..4c256121 --- /dev/null +++ b/packages/transcription/transcribe/transcripts.py @@ -0,0 +1,67 @@ +from youtube_transcript_api import YouTubeTranscriptApi +from googleapiclient.discovery import build +import oauth +import json +import os + +# Get credentials from environment variables +env_vars = oauth.import_env_vars() +YOUTUBE_API_KEY = env_vars.get("YOUTUBE_API_KEY") +CLIENT_ID = env_vars.get("CLIENT_ID") +CLIENT_SECRET = env_vars.get("CLIENT_SECRET") +GOOGLE_APPLICATION_CREDENTIALS= env_vars.get("GOOGLE_APPLICATION_CREDENTIALS") + +def get_latest_videos(channel_id, max_results=5): + + """ + Fetches the latest x-number of videos from a YouTube channel. + + Args: + channel_id (str): The ID of the YouTube channel to monitor. + max_results (int): The maximum number of latest videos to fetch. Default is 5. + + Returns: + list: A list of video IDs for the latest videos. + """ + youtube = build('youtube', 'v3', developerKey=YOUTUBE_API_KEY) + + # Fetch channel details to get the ID of the uploads playlist + request = youtube.channels().list( + part='contentDetails', + id=channel_id + ) + response = request.execute() + + if not response.get('items'): + raise ValueError(f"No channel found with ID {channel_id}") + + playlist_id = response['items'][0]['contentDetails']['relatedPlaylists']['uploads'] + + request = youtube.playlistItems().list( + part='snippet', + playlistId=playlist_id, + maxResults=max_results + ) + response = request.execute() + + video_ids = [item['snippet']['resourceId']['videoId'] for item in response['items']] + + return video_ids + +def download_transcripts(video_ids): + for video_id in video_ids: + try: + # Grabs transcript for the video + transcript = YouTubeTranscriptApi.get_transcript(video_id) + print(transcript) + with open(f'transcripts-data\\YT_transcripts\\{video_id}_transcript.json', 'w+', encoding='utf-8') as file: + json.dump(transcript, file) + + print(f'Transcript for {video_id} saved successfully.') + + except Exception as e: + print(f'An error occurred while fetching the transcript for {video_id}: {e}') + +channel_id = "UC8oPEsQe9a0v6TdJ4K_QXoA" +video_ids = get_latest_videos(channel_id, 10) +download_transcripts(video_ids) \ No newline at end of file diff --git a/packages/transcription/whisper-model/README.md b/packages/transcription/whisper-model/README.md new file mode 100644 index 00000000..92dcc82e --- /dev/null +++ b/packages/transcription/whisper-model/README.md @@ -0,0 +1,28 @@ +# HF Whisper Transcript App +Application of [OpenAI Whisper-V2](https://huggingface.co/openai/whisper-large-v2) for audio file transcription. + + +## To Run +Configure [README.md]('README.md') +```yml +model: + #model size + #tiny, base, small, medium, large, large_v2 + size: "tiny" + # device for pytorch processing + device: "cpu" + # chunk length for audio processing + chunk_length: "10" + # batch size + batch_size: 1 +audio: + # path to audio file to process + path: "audio/trial_meeting.mp3" +transcript: + # location to save transcript + save_loc: "transcripts/trial_meeting_transcript.txt" +``` +Execute from CL: +```bash +python transcribe.py transcribe_config.yml +``` \ No newline at end of file diff --git a/packages/transcription/whisper-model/long_transcription.ipynb b/packages/transcription/whisper-model/long_transcription.ipynb new file mode 100644 index 00000000..65595181 --- /dev/null +++ b/packages/transcription/whisper-model/long_transcription.ipynb @@ -0,0 +1,247 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "582718ec5efa4efa9c8e3e2f6cbc1dfc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Downloading (…)lve/main/config.json: 0%| | 0.00/1.94k [00:00 Date: Tue, 5 Dec 2023 10:59:11 -0600 Subject: [PATCH 03/17] web components --- packages/web/components/BetaCard.tsx | 35 +++ packages/web/components/CommentBoxes.tsx | 45 ++++ packages/web/components/Navbar.tsx | 4 + packages/web/components/Rubric.tsx | 78 ++++++ packages/web/components/ThreeCardLayout.tsx | 262 ++++++++++++++++++++ 5 files changed, 424 insertions(+) create mode 100644 packages/web/components/CommentBoxes.tsx create mode 100644 packages/web/components/Rubric.tsx create mode 100644 packages/web/components/ThreeCardLayout.tsx diff --git a/packages/web/components/BetaCard.tsx b/packages/web/components/BetaCard.tsx index 881e91ff..e4257c98 100644 --- a/packages/web/components/BetaCard.tsx +++ b/packages/web/components/BetaCard.tsx @@ -77,6 +77,41 @@ const BetaCard = ({ card }: { card: ICard }) => { }; }, [card.id]); + const handleCommentSubmit = async () => { + const newComment = { + card_id: card.id, + content: commentContent, + display_name: displayName, + created_at: new Date(), + }; + + + setComments((prevComments) => + prevComments + ? prevComments.filter((comment) => comment !== newComment) + : null + ); + + setDisplayName(""); // Resetting display name + setCommentContent(""); // Resetting comment content + + try { + const { data, error } = await supabase + .from("comments") + .insert([newComment]); + if (error) throw error; + setDisplayName(""); // Resetting display name after successful post + setCommentContent(""); // Resetting comment content after successful post + } catch (error) { + // If there's an error, revert the change to the comments + setComments((prevComments) => + prevComments + ? prevComments.filter((comment) => comment !== newComment) + : null + ); + } + }; + return (
{/* Card Header */} diff --git a/packages/web/components/CommentBoxes.tsx b/packages/web/components/CommentBoxes.tsx new file mode 100644 index 00000000..9968d491 --- /dev/null +++ b/packages/web/components/CommentBoxes.tsx @@ -0,0 +1,45 @@ +import { ICard } from "@/lib/api"; +import { faComment } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useState } from "react"; + +interface CommentBoxProps { + card: ICard; + onSubmit: ( data: { comment: string, card: ICard }) => void; +} + +export default function CommentBox({ onSubmit, card }: CommentBoxProps) { + const [comment, setComment] = useState(""); + const handleSubmit = () => { + onSubmit( { comment , card}); + setComment(""); + }; + + + return ( +
+
+ + +
+ +
+ ); +} \ No newline at end of file diff --git a/packages/web/components/Navbar.tsx b/packages/web/components/Navbar.tsx index 2ebaf410..3ae1e3a2 100644 --- a/packages/web/components/Navbar.tsx +++ b/packages/web/components/Navbar.tsx @@ -15,6 +15,10 @@ export const navLinks = [ id: "tips", title: "How to use", }, + { + id: "userFeedback", + title: "User Feedback", + }, ]; const Navbar = () => { diff --git a/packages/web/components/Rubric.tsx b/packages/web/components/Rubric.tsx new file mode 100644 index 00000000..a66d21ad --- /dev/null +++ b/packages/web/components/Rubric.tsx @@ -0,0 +1,78 @@ +"use client" +import React, { useState } from 'react'; + +const Rubric = ({ criteria }) => { + // This state will hold the scores for each criterion + const [scores, setScores] = useState(criteria.reduce((acc, criterion) => { + acc[criterion.id] = 1; // Initialize all criteria with a score of 1 + return acc; + }, {})); + + const handleScoreChange = (criterionId, score) => { + setScores(prevScores => ({ ...prevScores, [criterionId]: score })); + }; + + const saveScores = () => { + console.log(scores); + // TODO: + // Bundle with the userComments then + // send scores to supabase. + // Submit to userFeedback table. + + }; + + const circleButtonStyle = (score, criterionId) => ({ + width: '40px', // Circle diameter + height: '40px', // Circle diameter + borderRadius: '50%', // Make it round + margin: '5px', + fontWeight: scores[criterionId] === score ? 'bold' : 'normal', + outline: 'none', + border: scores[criterionId] === score ? '2px solid blue' : '1px solid grey' + }); + const submitButtonStyle = { + padding: '10px 20px', + fontSize: '16px', + color: 'white', + backgroundColor: '#007bff', + border: 'none', + borderRadius: '5px', + cursor: 'pointer', + outline: 'none', + marginTop: '20px', + }; + + const containerStyle = { + display: 'flex', + justifyContent: 'center', // Center children horizontally + alignItems: 'center', // Center children vertically + flexDirection: 'column', // Stack children vertically + padding: '20px', // Add padding around the container + }; + + return ( +
+
+ {criteria.map(criterion => ( +
+ +
+ {[1, 2, 3, 4, 5].map(score => ( + + ))} +
+
+ ))} + +
+
+ ); + }; + + export default Rubric; \ No newline at end of file diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx new file mode 100644 index 00000000..5959f4d7 --- /dev/null +++ b/packages/web/components/ThreeCardLayout.tsx @@ -0,0 +1,262 @@ +"use client"; + +/* +TODO: + - Database schema for user feedback + - UserIDs? + - Some identifiers + - Enter email? Or Name + DeviceID to create unique identifier. + + - Next button + - Refresh 3 new responses to question + +*/ + + + +import { ICard } from "@/lib/api"; +import { CARD_SHOW_PATH, getPageURL } from "@/lib/paths"; +import { supabase } from "@/lib/supabase/supabaseClient"; +import { + faSpinner, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import moment from "moment"; +import Link from "next/link"; +import { useState } from "react"; +import useClipboardApi from "use-clipboard-api"; +import { v4 as uuidv4 } from "uuid"; //will need +//import CommentBox from "./CommentBoxes"; + +import Rubric from '@/components/Rubric'; + +const criteria = [ + { id: 'criterion1', description: 'Criterion 1 Description' }, + { id: 'criterion2', description: 'Criterion 2 Description' }, + // Add more criteria as needed +]; + + + +const MAX_CHARACTERS_PREVIEW = 300; + +const LOADING_MESSAGES = [ + "Processing your request...", + "About 30 seconds remaining...", + "Processing your request...", + "About 25 seconds remaining...", + "About 25 seconds remaining...", + "Processing your request...", + "About 20 seconds remaining...", + "About 20 seconds remaining...", + "Processing your request...", + "Processing your request...", + "About 15 seconds remaining...", + "About 15 seconds remaining...", + "Processing your request...", + "About 10 seconds remaining...", + "About 10 seconds remaining...", + "Hang tight...", + "Hang tight...", + "Hang tight...", + "About 5 seconds remaining...", + "About 5 seconds remaining...", + "Finishing up...", +]; + +const WAIT_MS = 2500; +const POLL_INTERVAL = 10000; + +type SupabaseRealtimePayload = { + old: T; + new: T; +}; + + +interface CommentBoxProps { + card: ICard; + onSubmit: ( data: { comment: string, card: ICard }) => void; +} + + +function CommentBox({ onSubmit, card }: CommentBoxProps) { + const [comment, setComment] = useState(""); + const handleSubmit = () => { + onSubmit( { comment , card}); + setComment(""); + }; + + const submitButtonStyle = { + padding: '10px 20px', + fontSize: '16px', + color: 'white', + backgroundColor: '#007bff', + border: 'none', + borderRadius: '5px', + cursor: 'pointer', + outline: 'none', + marginTop: '20px', + }; + + return ( +
+
+ + +
+ +
+ ); +} + +export default function ThreeCardLayout({ cards }: { cards: ICard }) { + + const [msgIndex, setMsgIndex] = useState(0); + const isLoading = !cards.responses || cards.responses.length <= 0; + const [value, copy] = useClipboardApi(); + const currentUrl = getPageURL(`${CARD_SHOW_PATH}/${cards.id}`); + const [recentlyCopied, setRecentlyCopied] = useState(false); + const [prettyCreatedAt, setPrettyCreatedAt] = useState( + !!cards.created_at && new Date(cards.created_at) < new Date() + ? moment(cards.created_at).fromNow() + : moment().fromNow() + ); + + //Function that sends comments to supabase under respective card.comment + const submitCommentFeedback = async ({ + comment, + card + }: { + comment: string; + card: ICard; + }) => { + try { + + /* + This commented out code is for if we want to update previous comments. + Like if they submit their comment and then realize they want to add more. + */ + + // let { data: cards, error: fetchError } = await supabase + // .from("cards") + // .select("comment") + // .eq("id", card.id) + // .single(); + + // if (fetchError) { + // throw fetchError; + // } + + // const newFeedbackId = uuidv4(); + + // const newComment = { id: newFeedbackId, type: comment }; + + // const existingComment = + // existingCard.comment && Array.isArray(existingCard.comment) + // ? existingCard.comment + // : []; + // const updatedComment = [...existingComment, newComment]; + + const { data, error } = await supabase + .from("cards") + .update([{ comment: comment }]) + .eq("id", card.id) + .select() + + if (error) { + throw error; + } + } catch (error) {} + }; + + + return ( +
+ {cards && cards.map((card, index) => ( +
+ +
+

{card.title}

+
+ + {card.is_mine ? "You | " : null} + + {prettyCreatedAt} +
+ + {!isLoading && !!card.responses ? ( +

+ {card.responses[0].response.substring(0, MAX_CHARACTERS_PREVIEW)} + {card.responses[0].response.length > MAX_CHARACTERS_PREVIEW + ? "..." + : null} +

+ ) : ( +

+ + {LOADING_MESSAGES[msgIndex]} +

+ )} +
+ + + +
+ + + +
+ )) + } + +
+ ); +} +/* +${isLoading ? "border-4 border-blue-500" : ""} + + {card.responses[0].response} + + +{!isLoading && !!card.responses ? ( +

+ {card.responses[0].response.substring(0, MAX_CHARACTERS_PREVIEW)} + {card.responses[0].response.length > MAX_CHARACTERS_PREVIEW + ? "..." + : null} +

+ ) : ( +

+ + {LOADING_MESSAGES[msgIndex]} +

+ )} +*/ \ No newline at end of file From f274ce5209c45fab9bdc8c84e70c30dc3d7c3ef2 Mon Sep 17 00:00:00 2001 From: mikafur32 Date: Tue, 5 Dec 2023 11:52:13 -0600 Subject: [PATCH 04/17] updated with most recent UI changes --- packages/web/app/userFeedback/page.tsx | 112 +++++++++ packages/web/components/CommentBoxes.tsx | 30 ++- packages/web/components/Rubric.tsx | 49 ++-- packages/web/components/ThreeCardLayout.tsx | 264 +++++++------------- 4 files changed, 250 insertions(+), 205 deletions(-) create mode 100644 packages/web/app/userFeedback/page.tsx diff --git a/packages/web/app/userFeedback/page.tsx b/packages/web/app/userFeedback/page.tsx new file mode 100644 index 00000000..eea48db2 --- /dev/null +++ b/packages/web/app/userFeedback/page.tsx @@ -0,0 +1,112 @@ + +"use client"; + +// Import necessary modules and components +import { supabase } from '../../lib/supabase/supabaseClient'; +import ThreeCardLayout from '../../components/ThreeCardLayout'; +// import NextButton from '@/components/NextButton'; +import { useState, useEffect } from "react"; +import { ICard } from '@/lib/api'; +import Rubric from '@/components/Rubric'; + +export const dynamic = "force-dynamic"; +// export const question_idArray = Array.from({ length: 99 }, (_, index) => index); +export const question_idArray = [0,1,2]; + +export default function UserFeedback() { + // const [currentIndex, setCurrentIndex] = useState(randint(0,177)); + const [userName, setUserName] = useState(""); + const [currentIndex, setCurrentIndex] = useState(0); + const [cardArray, setCardArray] = useState | null>(null); + const [rubricScores, setRubricScores] = useState>({}); + + const handlePrevClick = () => { + if (currentIndex > 0) { + setCurrentIndex(currentIndex - 1); + } + // setRubricScores(createDefaultScores()); + } + + + + const handleNextClick = () => { + if (currentIndex < question_idArray.length - 1) { + setCurrentIndex(currentIndex + 1); + } + // setRubricScores(createDefaultScores()); + } + + //potentially could set rubric back to 1s + // const createDefaultScores = () => { + // return criteria.reduce((acc, criterion) => { + // acc[criterion.id] = 1; + // return acc; + // }, {}); + // }; + + const handleNameChange = (e) => { + setUserName(e.target.value); + } + + useEffect(() => { + const getCards = async () => { + try { + const cardsArray = []; + for (let i = 1; i <= question_idArray.length; i++) { + const { data: cards, error } = await supabase + .from('sawt_cards') + .select('*') + .eq("question_id", i); + // .eq("questionID", currentIndex) + + + if (error) { + console.error("Error fetching cards: ", error); + // Handle the error appropriately in your UI + } + cardsArray.push(cards); + } + setCardArray(cardsArray); + + } catch (error) { + console.error("Error fetching cards: ", error); + // Handle the error appropriately in your UI + } + }; + + getCards(); + }, []); // Run this effect only once when the component mounts + + if (!cardArray) { + return
Loading...
; + } + + return ( + <> +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+ + ); +} diff --git a/packages/web/components/CommentBoxes.tsx b/packages/web/components/CommentBoxes.tsx index 9968d491..03d5cfd5 100644 --- a/packages/web/components/CommentBoxes.tsx +++ b/packages/web/components/CommentBoxes.tsx @@ -1,22 +1,30 @@ + import { ICard } from "@/lib/api"; import { faComment } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; +import Rubric from '@/components/Rubric'; interface CommentBoxProps { + scores: Record card: ICard; - onSubmit: ( data: { comment: string, card: ICard }) => void; + onSubmit: (data: { comment: string, card: ICard, scores: Record }) => void; + onReset: () => void; } -export default function CommentBox({ onSubmit, card }: CommentBoxProps) { +export default function CommentBox({ onSubmit, card, scores, onReset }: CommentBoxProps) { const [comment, setComment] = useState(""); + // const [scores, setRubricScores] = useState>({}); + const handleSubmit = () => { - onSubmit( { comment , card}); + onSubmit({ comment, card, scores}); setComment(""); + onReset(); // Reset the scores after submission }; return ( +
- +
+ +
); } \ No newline at end of file diff --git a/packages/web/components/Rubric.tsx b/packages/web/components/Rubric.tsx index a66d21ad..bc59d689 100644 --- a/packages/web/components/Rubric.tsx +++ b/packages/web/components/Rubric.tsx @@ -1,27 +1,20 @@ -"use client" -import React, { useState } from 'react'; -const Rubric = ({ criteria }) => { - // This state will hold the scores for each criterion - const [scores, setScores] = useState(criteria.reduce((acc, criterion) => { - acc[criterion.id] = 1; // Initialize all criteria with a score of 1 - return acc; - }, {})); - - const handleScoreChange = (criterionId, score) => { - setScores(prevScores => ({ ...prevScores, [criterionId]: score })); - }; - - const saveScores = () => { - console.log(scores); - // TODO: - // Bundle with the userComments then - // send scores to supabase. - // Submit to userFeedback table. - }; +interface RubricProps { + criteria: Array<{ + id: string; + description: string; + }>; + scores: Record; + onScoreChange: (criterionId: string, score: number) => void; +} + + +const Rubric: React.FC = ({ criteria, scores, onScoreChange }) => { // This state will hold the scores for each criterion + + - const circleButtonStyle = (score, criterionId) => ({ + const circleButtonStyle = (score: number, criterionId: string) => ({ width: '40px', // Circle diameter height: '40px', // Circle diameter borderRadius: '50%', // Make it round @@ -48,8 +41,10 @@ const Rubric = ({ criteria }) => { alignItems: 'center', // Center children vertically flexDirection: 'column', // Stack children vertically padding: '20px', // Add padding around the container - }; - + }; + + + return (
@@ -61,15 +56,17 @@ const Rubric = ({ criteria }) => { ))}
))} - + {/* */} + {/* */} +
); diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index 5959f4d7..0feba5c0 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -1,18 +1,5 @@ -"use client"; - -/* -TODO: - - Database schema for user feedback - - UserIDs? - - Some identifiers - - Enter email? Or Name + DeviceID to create unique identifier. - - - Next button - - Refresh 3 new responses to question - -*/ - +"use client"; import { ICard } from "@/lib/api"; import { CARD_SHOW_PATH, getPageURL } from "@/lib/paths"; @@ -26,18 +13,19 @@ import Link from "next/link"; import { useState } from "react"; import useClipboardApi from "use-clipboard-api"; import { v4 as uuidv4 } from "uuid"; //will need -//import CommentBox from "./CommentBoxes"; +import CommentBox from "./CommentBoxes"; import Rubric from '@/components/Rubric'; + const criteria = [ - { id: 'criterion1', description: 'Criterion 1 Description' }, - { id: 'criterion2', description: 'Criterion 2 Description' }, + { id: 'Accuracy', description: 'Accuracy' }, + { id: 'Helpfulness', description: 'Helpfulness' }, + { id: 'Balance', description: 'Balance' } // Add more criteria as needed ]; - const MAX_CHARACTERS_PREVIEW = 300; const LOADING_MESSAGES = [ @@ -72,63 +60,12 @@ type SupabaseRealtimePayload = { new: T; }; +export default function ThreeCardLayout({ cards, userName }: { cards: ICard, userName: string }) { + + // ... other imports ... + -interface CommentBoxProps { - card: ICard; - onSubmit: ( data: { comment: string, card: ICard }) => void; -} - - -function CommentBox({ onSubmit, card }: CommentBoxProps) { - const [comment, setComment] = useState(""); - const handleSubmit = () => { - onSubmit( { comment , card}); - setComment(""); - }; - - const submitButtonStyle = { - padding: '10px 20px', - fontSize: '16px', - color: 'white', - backgroundColor: '#007bff', - border: 'none', - borderRadius: '5px', - cursor: 'pointer', - outline: 'none', - marginTop: '20px', - }; - - return ( -
-
- - -
- -
- ); -} - -export default function ThreeCardLayout({ cards }: { cards: ICard }) { - + const [msgIndex, setMsgIndex] = useState(0); const isLoading = !cards.responses || cards.responses.length <= 0; const [value, copy] = useClipboardApi(); @@ -140,46 +77,55 @@ export default function ThreeCardLayout({ cards }: { cards: ICard }) { : moment().fromNow() ); + + const [scores, setScores] = useState>({}); + + // Function to update scores + const handleScoreChange = (criterionId: string, score: number) => { + setScores(prevScores => ({ ...prevScores, [criterionId]: score })); + }; + + // Function to reset scores + const resetScores = () => { + // Reset logic - assuming each score should reset to 1 + const resettedScores = Object.keys(scores).reduce((acc, criterionId) => { + acc[criterionId] = 1; + return acc; + }, {}); + setScores(resettedScores); + }; + //Function that sends comments to supabase under respective card.comment const submitCommentFeedback = async ({ + scores, comment, card }: { + scores: Record; comment: string; card: ICard; }) => { try { + const { data: existingCard, error: fetchError } = await supabase + .from("sawt_cards") + .select("question_id, response_id") + .eq("response_id", card.response_id) + .single(); + + if (fetchError) { + throw fetchError; + } + const user_id = `${userName}_${Date.now()}`; - /* - This commented out code is for if we want to update previous comments. - Like if they submit their comment and then realize they want to add more. - */ - - // let { data: cards, error: fetchError } = await supabase - // .from("cards") - // .select("comment") - // .eq("id", card.id) - // .single(); - - // if (fetchError) { - // throw fetchError; - // } - - // const newFeedbackId = uuidv4(); - - // const newComment = { id: newFeedbackId, type: comment }; - // const existingComment = - // existingCard.comment && Array.isArray(existingCard.comment) - // ? existingCard.comment - // : []; - // const updatedComment = [...existingComment, newComment]; const { data, error } = await supabase - .from("cards") - .update([{ comment: comment }]) - .eq("id", card.id) - .select() + .from("UserFeedback") + .insert([ + { question_id: existingCard.question_id, response_id: existingCard.response_id, user_id: user_id, comment: comment, accuracy: scores["Accuracy"], helpfulness: scores["Helpfulness"], balance: scores["Balance"] }, + ]) + .select() + if (error) { throw error; @@ -187,76 +133,56 @@ export default function ThreeCardLayout({ cards }: { cards: ICard }) { } catch (error) {} }; - return ( -
- {cards && cards.map((card, index) => ( -
- -
-

{card.title}

-
- - {card.is_mine ? "You | " : null} - - {prettyCreatedAt} -
- - {!isLoading && !!card.responses ? ( -

- {card.responses[0].response.substring(0, MAX_CHARACTERS_PREVIEW)} - {card.responses[0].response.length > MAX_CHARACTERS_PREVIEW - ? "..." - : null} -

- ) : ( -

- - {LOADING_MESSAGES[msgIndex]} -

- )} -
- - - -
- - +
+ {cards && cards.map((card, index) => ( +
+
+

{card.query}

+ + {/* */} + { /* LINK DOES the modal-- Need to change card.id to questionID to refer back to original card ID */} +
+
+ + {card.is_mine ? "You | " : null} + +
+ +
+ {card.responses.map((element, index) => ( + +

+ {element.response} + +

+ ))} +
+
+
+ {/* */} + + + + {/* */} + + + +
- )) - } +
+ )) + } + +
-
); -} -/* -${isLoading ? "border-4 border-blue-500" : ""} - - {card.responses[0].response} - - -{!isLoading && !!card.responses ? ( -

- {card.responses[0].response.substring(0, MAX_CHARACTERS_PREVIEW)} - {card.responses[0].response.length > MAX_CHARACTERS_PREVIEW - ? "..." - : null} -

- ) : ( -

- - {LOADING_MESSAGES[msgIndex]} -

- )} -*/ \ No newline at end of file +} \ No newline at end of file From ada131cf805565c7af1b17c6b76b8c280ad7ec47 Mon Sep 17 00:00:00 2001 From: hayden outlaw Date: Tue, 5 Dec 2023 13:40:09 -0600 Subject: [PATCH 05/17] commenting out db client calls for local deployment --- .../functions/getanswer/inquirer.py | 3 +++ .../googlecloud/functions/getanswer/main.py | 21 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/googlecloud/functions/getanswer/inquirer.py b/packages/googlecloud/functions/getanswer/inquirer.py index 9bb1b3c2..ca005b98 100644 --- a/packages/googlecloud/functions/getanswer/inquirer.py +++ b/packages/googlecloud/functions/getanswer/inquirer.py @@ -171,7 +171,9 @@ def get_indepth_response_from_query(df, db, query, k): query = transform_query_for_date(query) doc_list = db.similarity_search_with_score(query, k=k) + docs = sort_retrived_documents(doc_list) + docs_page_content = append_metadata_to_content(doc_list) template = """ @@ -245,3 +247,4 @@ def answer_query( final_response = route_question(df, db_general, db_in_depth, query, response_type) return final_response + diff --git a/packages/googlecloud/functions/getanswer/main.py b/packages/googlecloud/functions/getanswer/main.py index 07717e30..d841ce2a 100644 --- a/packages/googlecloud/functions/getanswer/main.py +++ b/packages/googlecloud/functions/getanswer/main.py @@ -2,23 +2,28 @@ import time import math -import google.cloud.logging +# blocking google cloud for local deploy +#import google.cloud.logging import functions_framework from supabase import create_client - +from dotenv import find_dotenv, load_dotenv from helper import parse_field, get_dbs from inquirer import answer_query import os import json -logging_client = google.cloud.logging.Client() -logging_client.setup_logging() +#logging_client = google.cloud.logging.Client() +#logging_client.setup_logging() API_VERSION = "0.0.1" db_general, db_in_depth, voting_roll_df = get_dbs() # Setup Supabase client +load_dotenv(find_dotenv()) + +# disabled for local deploy, reenable for web +""" try: supabase_url = os.environ["SUPABASE_URL_PRODUCTION"] supabase_key = os.environ["SUPABASE_SERVICE_KEY_PRODUCTION"] @@ -30,6 +35,7 @@ raise ValueError("Supabase URL and key must be set in environment variables") supabase = create_client(supabase_url, supabase_key) +""" def update_supabase(responses, citations, card_id, processing_time_ms): transformed_citations = [] @@ -115,8 +121,15 @@ def getanswer(request): end = time.time() elapsed = int((end - start) * 1000) + + # + return(answer) + + # disabled for local deployment, reenable for web + """ update_supabase(responses_data, citations_data, card_id, elapsed) logging.info(f"Completed getanswer in {elapsed} seconds") print(f"\n\t--------- Completed getanswer in {elapsed} seconds --------\n") return ("Answer successfully submitted to Supabase", 200, headers) + """ From 6137269f24e8e27af7a752e1c1173c41debc2346 Mon Sep 17 00:00:00 2001 From: hayden outlaw Date: Tue, 5 Dec 2023 19:15:46 -0600 Subject: [PATCH 06/17] external script for varying answers --- .../getanswer/process_public_queries.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 packages/googlecloud/functions/getanswer/process_public_queries.py diff --git a/packages/googlecloud/functions/getanswer/process_public_queries.py b/packages/googlecloud/functions/getanswer/process_public_queries.py new file mode 100644 index 00000000..32f080fc --- /dev/null +++ b/packages/googlecloud/functions/getanswer/process_public_queries.py @@ -0,0 +1,50 @@ +import pandas as pd +import numpy as np +import requests +import csv +import json +from tqdm import tqdm + +# Input CSV file with 'title' column +input_csv = "/Users/haydenoutlaw/Desktop/card_rows_export_2023-11-29.csv" +output_csv = "/Users/haydenoutlaw/Desktop/gpt4-varied-11-29.csv" + +# point to getanswer server +api_endpoint = "http://localhost:8080" + +# list of k values +k_list = [5, 10, 15] + +# get response from local getanswer server, store answers +def make_api_call(title, k_inp): + payload = {"query": title, "response_type": "in_depth", "card_id": 1, "k": k_inp} + response = requests.post(f"{api_endpoint}", json=payload) + rdict = json.loads(response.text) + card_type_out = rdict["card_type"] + citations_out = rdict["citations"] + responses_out = rdict["responses"] + return card_type_out, citations_out, responses_out, k_inp + +# Open CSV file in append mode +with open(output_csv, 'a', newline='', encoding='utf-8') as csv_file: + # define csv out file + csv_writer = csv.writer(csv_file) + csv_writer.writerow(["query", "response_id", "card_type", "citations", "responses", "k"]) + + # read inputs + df = pd.read_csv(input_csv) + + + print("Connected to getanswer at", api_endpoint) + print("K Values", k_list) + print("Generating Responses....") + + + # for all queries, get answers and write out one at a time + tqiter = enumerate(tqdm(df["title"])) + for i, query in tqiter: + for k_val in k_list: + card_type, citations, responses, k = make_api_call(query, k_val) + csv_writer.writerow([query, i, card_type, citations, responses, k]) + +print(f"Results saved to '{output_csv}'.") \ No newline at end of file From 2b465dbe3ea9792ec51308184c1bf7d6d11e2ec5 Mon Sep 17 00:00:00 2001 From: mikafur32 Date: Tue, 5 Dec 2023 22:50:51 -0600 Subject: [PATCH 07/17] typescript cleanup --- packages/web/app/userFeedback/page.tsx | 53 ++++++------ packages/web/components/Rubric.tsx | 20 ++--- packages/web/components/ThreeCardLayout.tsx | 94 +++++---------------- 3 files changed, 54 insertions(+), 113 deletions(-) diff --git a/packages/web/app/userFeedback/page.tsx b/packages/web/app/userFeedback/page.tsx index eea48db2..ce3ae6a7 100644 --- a/packages/web/app/userFeedback/page.tsx +++ b/packages/web/app/userFeedback/page.tsx @@ -10,64 +10,59 @@ import { ICard } from '@/lib/api'; import Rubric from '@/components/Rubric'; export const dynamic = "force-dynamic"; -// export const question_idArray = Array.from({ length: 99 }, (_, index) => index); -export const question_idArray = [0,1,2]; export default function UserFeedback() { // const [currentIndex, setCurrentIndex] = useState(randint(0,177)); const [userName, setUserName] = useState(""); const [currentIndex, setCurrentIndex] = useState(0); - const [cardArray, setCardArray] = useState | null>(null); + const [cardArray, setCardArray] = useState> | null>(null); const [rubricScores, setRubricScores] = useState>({}); - - const handlePrevClick = () => { - if (currentIndex > 0) { - setCurrentIndex(currentIndex - 1); - } - // setRubricScores(createDefaultScores()); - } - + //const question_idArray = Array.from({ length: 99 }, (_, index) => index); + const question_idArray = [0,1,2]; - const handleNextClick = () => { - if (currentIndex < question_idArray.length - 1) { - setCurrentIndex(currentIndex + 1); - } - // setRubricScores(createDefaultScores()); - } - //potentially could set rubric back to 1s - // const createDefaultScores = () => { - // return criteria.reduce((acc, criterion) => { - // acc[criterion.id] = 1; - // return acc; - // }, {}); - // }; + const handlePrevClick = () => { + //wraps around + setCurrentIndex((currentIndex - 1 + question_idArray.length) % question_idArray.length); + }; - const handleNameChange = (e) => { + const handleNextClick = () => { + //wraps around + setCurrentIndex((currentIndex + 1) % question_idArray.length); + }; + + //const handleNameChange = (e) => { + const handleNameChange = (e: React.ChangeEvent) => { setUserName(e.target.value); } useEffect(() => { const getCards = async () => { try { - const cardsArray = []; + + const cardsArray: Array> = []; for (let i = 1; i <= question_idArray.length; i++) { const { data: cards, error } = await supabase .from('sawt_cards') .select('*') .eq("question_id", i); // .eq("questionID", currentIndex) - - + if (error) { console.error("Error fetching cards: ", error); // Handle the error appropriately in your UI } - cardsArray.push(cards); + + if (cards) { + cardsArray.push(cards); + } } + setCardArray(cardsArray); + setCurrentIndex(Math.floor(Math.random() * cardsArray.length)); + } catch (error) { console.error("Error fetching cards: ", error); // Handle the error appropriately in your UI diff --git a/packages/web/components/Rubric.tsx b/packages/web/components/Rubric.tsx index bc59d689..3e6dfadf 100644 --- a/packages/web/components/Rubric.tsx +++ b/packages/web/components/Rubric.tsx @@ -35,18 +35,16 @@ const Rubric: React.FC = ({ criteria, scores, onScoreChange }) => { marginTop: '20px', }; - const containerStyle = { - display: 'flex', - justifyContent: 'center', // Center children horizontally - alignItems: 'center', // Center children vertically - flexDirection: 'column', // Stack children vertically - padding: '20px', // Add padding around the container - }; - - + const containerStyle: React.CSSProperties = { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'column', + padding: '20px', + }; - return ( -
+ return ( +
{criteria.map(criterion => (
diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index 0feba5c0..6290d3d3 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -2,17 +2,10 @@ "use client"; import { ICard } from "@/lib/api"; -import { CARD_SHOW_PATH, getPageURL } from "@/lib/paths"; +// import { CARD_SHOW_PATH, getPageURL } from "@/lib/paths"; import { supabase } from "@/lib/supabase/supabaseClient"; -import { - faSpinner, -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import moment from "moment"; -import Link from "next/link"; +// import Link from "next/link"; import { useState } from "react"; -import useClipboardApi from "use-clipboard-api"; -import { v4 as uuidv4 } from "uuid"; //will need import CommentBox from "./CommentBoxes"; import Rubric from '@/components/Rubric'; @@ -25,59 +18,8 @@ const criteria = [ // Add more criteria as needed ]; - -const MAX_CHARACTERS_PREVIEW = 300; - -const LOADING_MESSAGES = [ - "Processing your request...", - "About 30 seconds remaining...", - "Processing your request...", - "About 25 seconds remaining...", - "About 25 seconds remaining...", - "Processing your request...", - "About 20 seconds remaining...", - "About 20 seconds remaining...", - "Processing your request...", - "Processing your request...", - "About 15 seconds remaining...", - "About 15 seconds remaining...", - "Processing your request...", - "About 10 seconds remaining...", - "About 10 seconds remaining...", - "Hang tight...", - "Hang tight...", - "Hang tight...", - "About 5 seconds remaining...", - "About 5 seconds remaining...", - "Finishing up...", -]; - -const WAIT_MS = 2500; -const POLL_INTERVAL = 10000; - -type SupabaseRealtimePayload = { - old: T; - new: T; -}; - -export default function ThreeCardLayout({ cards, userName }: { cards: ICard, userName: string }) { +export default function ThreeCardLayout({ cards, userName }: { cards: Array, userName: string }) { - // ... other imports ... - - - - const [msgIndex, setMsgIndex] = useState(0); - const isLoading = !cards.responses || cards.responses.length <= 0; - const [value, copy] = useClipboardApi(); - const currentUrl = getPageURL(`${CARD_SHOW_PATH}/${cards.id}`); - const [recentlyCopied, setRecentlyCopied] = useState(false); - const [prettyCreatedAt, setPrettyCreatedAt] = useState( - !!cards.created_at && new Date(cards.created_at) < new Date() - ? moment(cards.created_at).fromNow() - : moment().fromNow() - ); - - const [scores, setScores] = useState>({}); // Function to update scores @@ -88,11 +30,18 @@ export default function ThreeCardLayout({ cards, userName }: { cards: ICard, use // Function to reset scores const resetScores = () => { // Reset logic - assuming each score should reset to 1 - const resettedScores = Object.keys(scores).reduce((acc, criterionId) => { - acc[criterionId] = 1; - return acc; - }, {}); - setScores(resettedScores); + // const resettedScores = Object.keys(scores).reduce((acc, criterionId) => { + // acc[criterionId] = 1; + // return acc; + // }, {}); + + const resetScores = { + "Accuracy": 1, + "Helpfulness": 1, + "Balance": 1 + }; + + setScores(resetScores); }; //Function that sends comments to supabase under respective card.comment @@ -108,8 +57,8 @@ export default function ThreeCardLayout({ cards, userName }: { cards: ICard, use try { const { data: existingCard, error: fetchError } = await supabase .from("sawt_cards") - .select("question_id, response_id") - .eq("response_id", card.response_id) + .select("question_id, id") + .eq("id", card.id) .single(); if (fetchError) { @@ -122,11 +71,10 @@ export default function ThreeCardLayout({ cards, userName }: { cards: ICard, use const { data, error } = await supabase .from("UserFeedback") .insert([ - { question_id: existingCard.question_id, response_id: existingCard.response_id, user_id: user_id, comment: comment, accuracy: scores["Accuracy"], helpfulness: scores["Helpfulness"], balance: scores["Balance"] }, + { question_id: existingCard.question_id, response_id: existingCard.id, user_id: user_id, comment: comment, accuracy: scores["Accuracy"], helpfulness: scores["Helpfulness"], balance: scores["Balance"] }, ]) .select() - - + if (error) { throw error; } @@ -139,7 +87,7 @@ export default function ThreeCardLayout({ cards, userName }: { cards: ICard, use
-

{card.query}

+

{card.title}

{/* */} { /* LINK DOES the modal-- Need to change card.id to questionID to refer back to original card ID */} @@ -151,7 +99,7 @@ export default function ThreeCardLayout({ cards, userName }: { cards: ICard, use
- {card.responses.map((element, index) => ( + {card.responses && card.responses.map((element, index) => (

{element.response} From e1c49d9a83fc68a0e2342f2878262af066525322 Mon Sep 17 00:00:00 2001 From: mikafur32 Date: Wed, 6 Dec 2023 09:18:37 -0600 Subject: [PATCH 08/17] key error --- packages/web/app/userFeedback/page.tsx | 9 ++++----- packages/web/components/ThreeCardLayout.tsx | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/web/app/userFeedback/page.tsx b/packages/web/app/userFeedback/page.tsx index ce3ae6a7..038f4f14 100644 --- a/packages/web/app/userFeedback/page.tsx +++ b/packages/web/app/userFeedback/page.tsx @@ -7,7 +7,6 @@ import ThreeCardLayout from '../../components/ThreeCardLayout'; // import NextButton from '@/components/NextButton'; import { useState, useEffect } from "react"; import { ICard } from '@/lib/api'; -import Rubric from '@/components/Rubric'; export const dynamic = "force-dynamic"; @@ -16,10 +15,11 @@ export default function UserFeedback() { const [userName, setUserName] = useState(""); const [currentIndex, setCurrentIndex] = useState(0); const [cardArray, setCardArray] = useState> | null>(null); - const [rubricScores, setRubricScores] = useState>({}); - //const question_idArray = Array.from({ length: 99 }, (_, index) => index); - const question_idArray = [0,1,2]; + + // Not the best way to do it-- we really should make each of these a new page and the next/prev buttons + // should be linked to the next/prev page. But this is a quick fix for now. + const question_idArray = Array.from({ length: 98 }, (_, index) => index); const handlePrevClick = () => { @@ -47,7 +47,6 @@ export default function UserFeedback() { .from('sawt_cards') .select('*') .eq("question_id", i); - // .eq("questionID", currentIndex) if (error) { console.error("Error fetching cards: ", error); diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index 6290d3d3..d02f49cc 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -7,7 +7,6 @@ import { supabase } from "@/lib/supabase/supabaseClient"; // import Link from "next/link"; import { useState } from "react"; import CommentBox from "./CommentBoxes"; - import Rubric from '@/components/Rubric'; @@ -83,14 +82,14 @@ export default function ThreeCardLayout({ cards, userName }: { cards: Array - {cards && cards.map((card, index) => ( -

+ {cards && cards.map((card,index) => ( +

{card.title}

{/* */} - { /* LINK DOES the modal-- Need to change card.id to questionID to refer back to original card ID */} + {/* LINK DOES the modal-- Need to change card.id to questionID to refer back to original card ID */}
From 6a7eddf7420e67ac967be155a76a91c9c20ef94d Mon Sep 17 00:00:00 2001 From: mikafur32 Date: Wed, 6 Dec 2023 09:22:41 -0600 Subject: [PATCH 09/17] remedy error --- packages/web/components/CommentBoxes.tsx | 1 - packages/web/components/ThreeCardLayout.tsx | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/web/components/CommentBoxes.tsx b/packages/web/components/CommentBoxes.tsx index 03d5cfd5..0b7cd545 100644 --- a/packages/web/components/CommentBoxes.tsx +++ b/packages/web/components/CommentBoxes.tsx @@ -3,7 +3,6 @@ import { ICard } from "@/lib/api"; import { faComment } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; -import Rubric from '@/components/Rubric'; interface CommentBoxProps { scores: Record diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index d02f49cc..032e3533 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -82,10 +82,10 @@ export default function ThreeCardLayout({ cards, userName }: { cards: Array - {cards && cards.map((card,index) => ( -
+ {cards && cards.map((card) => ( +
-
+

{card.title}

{/* */} From c039ef90a0880afaa1ccfab26a53a732e38bf5b8 Mon Sep 17 00:00:00 2001 From: mikafur32 Date: Wed, 6 Dec 2023 11:07:17 -0600 Subject: [PATCH 10/17] performance Improvements --- packages/web/app/userFeedback/page.tsx | 89 ++++++++++++++------- packages/web/components/ThreeCardLayout.tsx | 8 ++ 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/packages/web/app/userFeedback/page.tsx b/packages/web/app/userFeedback/page.tsx index 038f4f14..f6ac43c9 100644 --- a/packages/web/app/userFeedback/page.tsx +++ b/packages/web/app/userFeedback/page.tsx @@ -14,6 +14,8 @@ export default function UserFeedback() { // const [currentIndex, setCurrentIndex] = useState(randint(0,177)); const [userName, setUserName] = useState(""); const [currentIndex, setCurrentIndex] = useState(0); + const [fullData, setFullData] = useState> | null>(null); + const [cardArray, setCardArray] = useState> | null>(null); @@ -23,13 +25,24 @@ export default function UserFeedback() { const handlePrevClick = () => { - //wraps around - setCurrentIndex((currentIndex - 1 + question_idArray.length) % question_idArray.length); + if (fullData) { + setCardArray(fullData); + //wraps around + setCurrentIndex((currentIndex - 1 + question_idArray.length) % question_idArray.length); + } else { + alert("Please wait for the rest of the cards to finish loading..."); + } }; const handleNextClick = () => { - //wraps around - setCurrentIndex((currentIndex + 1) % question_idArray.length); + if (fullData) { + setCardArray(fullData); + //wraps around + setCurrentIndex((currentIndex + 1) % question_idArray.length); + } else { + alert("Please wait for the rest of the cards to finish loading..."); + } + }; //const handleNameChange = (e) => { @@ -38,39 +51,59 @@ export default function UserFeedback() { } useEffect(() => { - const getCards = async () => { + const getCard = async () => { try { - const cardsArray: Array> = []; - for (let i = 1; i <= question_idArray.length; i++) { - const { data: cards, error } = await supabase - .from('sawt_cards') - .select('*') - .eq("question_id", i); - - if (error) { - console.error("Error fetching cards: ", error); - // Handle the error appropriately in your UI - } - - if (cards) { - cardsArray.push(cards); - } + const { data: cards, error } = await supabase + .from('sawt_cards') + .select('*') + .eq("question_id", 0); + if (cards) { + cardsArray.push(cards); } - setCardArray(cardsArray); - - setCurrentIndex(Math.floor(Math.random() * cardsArray.length)); - - } catch (error) { + console.log(cards); + }catch (error) { console.error("Error fetching cards: ", error); // Handle the error appropriately in your UI } - }; - - getCards(); + getCards(); + } + getCard(); }, []); // Run this effect only once when the component mounts + + const getCards = async () => { + const cardsArray: Array> = []; + try { + for (let i = 1; i <= question_idArray.length; i++) { + const { data: cards, error } = await supabase + .from('sawt_cards') + .select('*') + .eq("question_id", i); + + if (error) { + console.error("Error fetching cards: ", error); + // Handle the error appropriately in your UI + } + console.log(cards); + + if (cards) { + cardsArray.push(cards); + } + } + setFullData(cardsArray); + console.log(fullData); + //setCurrentIndex(Math.floor(Math.random() * cardsArray.length)); + + + } catch (error) { + console.error("Error fetching cards: ", error); + // Handle the error appropriately in your UI + } + }; + + if (!cardArray) { return
Loading...
; } diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index 032e3533..d4b07a00 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -60,6 +60,14 @@ export default function ThreeCardLayout({ cards, userName }: { cards: Array Date: Wed, 6 Dec 2023 21:42:45 -0600 Subject: [PATCH 11/17] feat: migrations and feedback UI --- packages/web/README.md | 53 ++++- .../app/{userFeedback => feedback}/page.tsx | 119 +++++----- packages/web/components/CommentBoxes.tsx | 81 ++++--- packages/web/components/Navbar.tsx | 4 +- packages/web/components/ThreeCardLayout.tsx | 221 +++++++++++------- packages/web/lib/supabase/db.ts | 2 + .../20231207000743_feedback_cards.sql | 97 ++++++++ 7 files changed, 396 insertions(+), 181 deletions(-) rename packages/web/app/{userFeedback => feedback}/page.tsx (55%) create mode 100644 packages/web/supabase/migrations/20231207000743_feedback_cards.sql diff --git a/packages/web/README.md b/packages/web/README.md index a5ddc54e..eadc5a96 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -26,14 +26,55 @@ yarn dev pnpm dev ``` -### Supabase migrations +## Migration instructions -The first time you setup a new Supabase project, migrations must be applied. +To make changes to the schema moving forward, you can update the DB directly from Supabase DB. When ready, run `supabase db remote commit`. You will need to have Docker running locally for this to succeed. When complete, there will be a new file in `supabase/migrations` with a name like `20230821153353_remote_commit.sql`. Commit this file to source control and it will automatically be applied to the DB when merged. -``` -supabase login -supabase link --project-ref $PROJECT_ID +### New project + +```bash +# from nextjs project +cd packages/web + +# connect to project +supabase link --project-ref weqbsjuunfkxuyhsutzx + +# sync project with existing migrations supabase db push + +# pull remote project changes to local code +supabase db remote commit ``` -To make changes to the schema moving forward, you can update the DB directly from Supabase DB. When ready, run `supabase db remote commit`. You will need to have Docker running locally for this to succeed. When complete, there will be a new file in `supabase/migrations` with a name like `20230821153353_remote_commit.sql`. Commit this file to source control and it will automatically be applied to the DB when merged. +This will create a new file in `packages/web/supabase/migrations`. For some reason, Supabase adds a bunch of junk that you can remove from the generated migration. + +```sql +-- delete all this +alter table "auth"."saml_relay_states" add column "flow_state_id" uuid; + +alter table "auth"."sessions" add column "ip" inet; + +alter table "auth"."sessions" add column "refreshed_at" timestamp without time zone; + +alter table "auth"."sessions" add column "user_agent" text; + +CREATE INDEX flow_state_created_at_idx ON auth.flow_state USING btree (created_at DESC); + +CREATE INDEX mfa_challenge_created_at_idx ON auth.mfa_challenges USING btree (created_at DESC); + +CREATE INDEX mfa_factors_user_id_idx ON auth.mfa_factors USING btree (user_id); + +CREATE INDEX refresh_tokens_updated_at_idx ON auth.refresh_tokens USING btree (updated_at DESC); + +CREATE INDEX saml_relay_states_created_at_idx ON auth.saml_relay_states USING btree (created_at DESC); + +CREATE INDEX sessions_not_after_idx ON auth.sessions USING btree (not_after DESC); + +alter table "auth"."saml_relay_states" add constraint "saml_relay_states_flow_state_id_fkey" FOREIGN KEY (flow_state_id) REFERENCES auth.flow_state(id) ON DELETE CASCADE not valid; + +alter table "auth"."saml_relay_states" validate constraint "saml_relay_states_flow_state_id_fkey"; +``` + +## Importing large data + +Increase session timeout: `alter role authenticator set statement_timeout = '120s';` diff --git a/packages/web/app/userFeedback/page.tsx b/packages/web/app/feedback/page.tsx similarity index 55% rename from packages/web/app/userFeedback/page.tsx rename to packages/web/app/feedback/page.tsx index f6ac43c9..65c58cd5 100644 --- a/packages/web/app/userFeedback/page.tsx +++ b/packages/web/app/feedback/page.tsx @@ -1,12 +1,12 @@ - "use client"; // Import necessary modules and components -import { supabase } from '../../lib/supabase/supabaseClient'; -import ThreeCardLayout from '../../components/ThreeCardLayout'; +import ThreeCardLayout from "../../components/ThreeCardLayout"; +import { supabase } from "../../lib/supabase/supabaseClient"; // import NextButton from '@/components/NextButton'; -import { useState, useEffect } from "react"; -import { ICard } from '@/lib/api'; +import { ICard } from "@/lib/api"; +import { TABLES } from "@/lib/supabase/db"; +import { useEffect, useState } from "react"; export const dynamic = "force-dynamic"; @@ -14,74 +14,74 @@ export default function UserFeedback() { // const [currentIndex, setCurrentIndex] = useState(randint(0,177)); const [userName, setUserName] = useState(""); const [currentIndex, setCurrentIndex] = useState(0); + const [answered, setAnswered] = useState>(new Set()); const [fullData, setFullData] = useState> | null>(null); const [cardArray, setCardArray] = useState> | null>(null); - // Not the best way to do it-- we really should make each of these a new page and the next/prev buttons // should be linked to the next/prev page. But this is a quick fix for now. const question_idArray = Array.from({ length: 98 }, (_, index) => index); - - const handlePrevClick = () => { - if (fullData) { - setCardArray(fullData); - //wraps around - setCurrentIndex((currentIndex - 1 + question_idArray.length) % question_idArray.length); - } else { - alert("Please wait for the rest of the cards to finish loading..."); - } - }; + // const handlePrevClick = () => { + // if (fullData) { + // setCardArray(fullData); + // //wraps around + // setCurrentIndex( + // (currentIndex - 1 + question_idArray.length) % question_idArray.length + // ); + // } else { + // alert("Please wait for the rest of the cards to finish loading..."); + // } + // }; const handleNextClick = () => { if (fullData) { setCardArray(fullData); //wraps around setCurrentIndex((currentIndex + 1) % question_idArray.length); + setAnswered(new Set()); } else { alert("Please wait for the rest of the cards to finish loading..."); } - }; - + //const handleNameChange = (e) => { const handleNameChange = (e: React.ChangeEvent) => { setUserName(e.target.value); - } + }; useEffect(() => { const getCard = async () => { try { const cardsArray: Array> = []; const { data: cards, error } = await supabase - .from('sawt_cards') - .select('*') + .from(TABLES.FEEDBACK_CARDS) + .select("*") .eq("question_id", 0); if (cards) { cardsArray.push(cards); } setCardArray(cardsArray); console.log(cards); - }catch (error) { + } catch (error) { console.error("Error fetching cards: ", error); // Handle the error appropriately in your UI } getCards(); - } - getCard(); + }; + getCard(); }, []); // Run this effect only once when the component mounts - const getCards = async () => { const cardsArray: Array> = []; try { for (let i = 1; i <= question_idArray.length; i++) { const { data: cards, error } = await supabase - .from('sawt_cards') - .select('*') + .from(TABLES.FEEDBACK_CARDS) + .select("*") .eq("question_id", i); - + if (error) { console.error("Error fetching cards: ", error); // Handle the error appropriately in your UI @@ -91,49 +91,54 @@ export default function UserFeedback() { if (cards) { cardsArray.push(cards); } - } + } setFullData(cardsArray); - console.log(fullData); + // console.log(fullData); //setCurrentIndex(Math.floor(Math.random() * cardsArray.length)); - - } catch (error) { console.error("Error fetching cards: ", error); // Handle the error appropriately in your UI } }; - if (!cardArray) { return
Loading...
; } return ( - <> -
- -
- +
+
+
+
+ +
+ +
+ + {answered.size === 3 && ( + + )}
- -
-
- -
- -
- -
-
- +
+
); } diff --git a/packages/web/components/CommentBoxes.tsx b/packages/web/components/CommentBoxes.tsx index 0b7cd545..f8602ef2 100644 --- a/packages/web/components/CommentBoxes.tsx +++ b/packages/web/components/CommentBoxes.tsx @@ -1,54 +1,63 @@ - import { ICard } from "@/lib/api"; import { faComment } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; interface CommentBoxProps { - scores: Record + scores: Record; + card: ICard; + onSubmit: (data: { + comment: string; card: ICard; - onSubmit: (data: { comment: string, card: ICard, scores: Record }) => void; - onReset: () => void; + scores: Record; + index: number; + }) => void; + onReset: () => void; + index: number; } -export default function CommentBox({ onSubmit, card, scores, onReset }: CommentBoxProps) { - const [comment, setComment] = useState(""); - // const [scores, setRubricScores] = useState>({}); - - const handleSubmit = () => { - onSubmit({ comment, card, scores}); - setComment(""); - onReset(); // Reset the scores after submission - }; +export default function CommentBox({ + onSubmit, + card, + scores, + onReset, + index, +}: CommentBoxProps) { + const [comment, setComment] = useState(""); + // const [scores, setRubricScores] = useState>({}); + const handleSubmit = () => { + onSubmit({ comment, card, scores, index }); + setComment(""); + onReset(); // Reset the scores after submission + }; - return ( - -
-
+ return ( +
+
-
-
- -
+
+
+ +
- ); -} \ No newline at end of file + ); +} diff --git a/packages/web/components/Navbar.tsx b/packages/web/components/Navbar.tsx index 3ae1e3a2..0f33a1d7 100644 --- a/packages/web/components/Navbar.tsx +++ b/packages/web/components/Navbar.tsx @@ -16,8 +16,8 @@ export const navLinks = [ title: "How to use", }, { - id: "userFeedback", - title: "User Feedback", + id: "feedback", + title: "Feedback", }, ]; diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index d4b07a00..471b9ed2 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -1,143 +1,204 @@ - "use client"; import { ICard } from "@/lib/api"; // import { CARD_SHOW_PATH, getPageURL } from "@/lib/paths"; import { supabase } from "@/lib/supabase/supabaseClient"; // import Link from "next/link"; +import Rubric from "@/components/Rubric"; +import { TABLES } from "@/lib/supabase/db"; +import { + faCheckCircle, + faCircleXmark, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; import CommentBox from "./CommentBoxes"; -import Rubric from '@/components/Rubric'; - const criteria = [ - { id: 'Accuracy', description: 'Accuracy' }, - { id: 'Helpfulness', description: 'Helpfulness' }, - { id: 'Balance', description: 'Balance' } + { id: "Accuracy", description: "Accuracy" }, + { id: "Helpfulness", description: "Helpfulness" }, + { id: "Balance", description: "Balance" }, // Add more criteria as needed ]; -export default function ThreeCardLayout({ cards, userName }: { cards: Array, userName: string }) { - +const NUM_CARDS_PER_SET = 3; + +export default function ThreeCardLayout({ + cards, + userName, + answered, + setAnswered, +}: { + cards: Array; + userName: string; + answered: Set; + setAnswered: (_: any) => void; +}) { const [scores, setScores] = useState>({}); + const [activeTab, setActiveTab] = useState(0); // Function to update scores const handleScoreChange = (criterionId: string, score: number) => { - setScores(prevScores => ({ ...prevScores, [criterionId]: score })); + setScores((prevScores) => ({ ...prevScores, [criterionId]: score })); }; // Function to reset scores const resetScores = () => { - // Reset logic - assuming each score should reset to 1 - // const resettedScores = Object.keys(scores).reduce((acc, criterionId) => { - // acc[criterionId] = 1; - // return acc; + // Reset logic - assuming each score should reset to 1 + // const resettedScores = Object.keys(scores).reduce((acc, criterionId) => { + // acc[criterionId] = 1; + // return acc; // }, {}); - + const resetScores = { - "Accuracy": 1, - "Helpfulness": 1, - "Balance": 1 + Accuracy: 1, + Helpfulness: 1, + Balance: 1, }; - setScores(resetScores); + setScores(resetScores); }; //Function that sends comments to supabase under respective card.comment const submitCommentFeedback = async ({ scores, comment, - card + card, + index, }: { scores: Record; comment: string; card: ICard; + index: number; }) => { try { const { data: existingCard, error: fetchError } = await supabase - .from("sawt_cards") + .from(TABLES.FEEDBACK_CARDS) .select("question_id, id") .eq("id", card.id) .single(); - // order by random // select all ids load them to an array // then independent supabase fetch the card with that random id - // --on button click - - - + // --on button click + if (fetchError) { throw fetchError; } const user_id = `${userName}_${Date.now()}`; - - const { data, error } = await supabase - .from("UserFeedback") - .insert([ - { question_id: existingCard.question_id, response_id: existingCard.id, user_id: user_id, comment: comment, accuracy: scores["Accuracy"], helpfulness: scores["Helpfulness"], balance: scores["Balance"] }, - ]) - .select() - + .from(TABLES.USER_FEEDBACK) + .insert([ + { + question_id: existingCard.question_id, + response_id: existingCard.id, + user_id: user_id, + comment: comment, + accuracy: scores["Accuracy"], + helpfulness: scores["Helpfulness"], + balance: scores["Balance"], + }, + ]) + .select(); + if (error) { throw error; } + + const newAnswered = new Set(answered); + newAnswered.add(index); + setAnswered(newAnswered); } catch (error) {} }; + const Tabs = () => { + return Array.from({ length: NUM_CARDS_PER_SET }, (_, index) => { + const isAnswered = answered.has(index); + return ( +
{ + setActiveTab(index); + }} + > + Option {index + 1}{" "} + {isAnswered ? ( + + ) : ( + + )} +
+ ); + }); + }; + return (
- {cards && cards.map((card) => ( -
- -
-

{card.title}

- - {/* */} - {/* LINK DOES the modal-- Need to change card.id to questionID to refer back to original card ID */} -
-
- - {card.is_mine ? "You | " : null} - -
- +
+ +
+ {cards && + cards.map((card, i) => + i === activeTab ? ( +
+
+

{card.title}

+ + {/* */} + {/* LINK DOES the modal-- Need to change card.id to questionID to refer back to original card ID */}
- {card.responses && card.responses.map((element, index) => ( - -

- {element.response} - -

- ))} +
+ + {card.is_mine ? "You | " : null} + +
+ +
+ {card.responses && + card.responses.map((element, index) => ( +

+ {element.response} +

+ ))} +
-
-
- {/* */} - - - - {/* */} - - - - -
-
- )) - } - -
+ {!answered.has(i) && ( +
+
+ {/* */} + + + + +
+ )} +
+
+ ) : null + )} +
); -} \ No newline at end of file +} diff --git a/packages/web/lib/supabase/db.ts b/packages/web/lib/supabase/db.ts index 2ca2224b..a7cd4888 100644 --- a/packages/web/lib/supabase/db.ts +++ b/packages/web/lib/supabase/db.ts @@ -1,4 +1,6 @@ export const TABLES = { USER_QUERIES: "user_queries", CARDS: "cards", + FEEDBACK_CARDS: "feedback_cards", + USER_FEEDBACK: "user_feedback", }; diff --git a/packages/web/supabase/migrations/20231207000743_feedback_cards.sql b/packages/web/supabase/migrations/20231207000743_feedback_cards.sql new file mode 100644 index 00000000..7c7488f9 --- /dev/null +++ b/packages/web/supabase/migrations/20231207000743_feedback_cards.sql @@ -0,0 +1,97 @@ +-- feedback cards +create table "public"."feedback_cards" ( + "id" uuid not null default gen_random_uuid(), + "title" text, + "question_id" bigint, + "card_type" text, + "citations" jsonb, + "responses" jsonb, + "k" bigint +); + + + + + +-- user feedback + +create table "public"."user_feedback" ( + "created_at" timestamp with time zone not null default now(), + "user_id" text, + "comment" jsonb, + "question_id" bigint, + "response_id" uuid, + "accuracy" integer, + "helpfulness" integer, + "balance" integer +); + + + + +alter table "public"."user_feedback" add column "id" uuid not null default gen_random_uuid(); + +CREATE UNIQUE INDEX feedback_cards_id_key ON public.feedback_cards USING btree (id); + +CREATE UNIQUE INDEX feedback_cards_pkey ON public.feedback_cards USING btree (id); + +CREATE UNIQUE INDEX user_feedback_pkey ON public.user_feedback USING btree (id); + +alter table "public"."feedback_cards" add constraint "feedback_cards_pkey" PRIMARY KEY using index "feedback_cards_pkey"; + +alter table "public"."user_feedback" add constraint "user_feedback_pkey" PRIMARY KEY using index "user_feedback_pkey"; + +alter table "public"."feedback_cards" add constraint "feedback_cards_id_key" UNIQUE using index "feedback_cards_id_key"; + +alter table "public"."feedback_cards" enable row level security; + + +create policy "Enable insert access for all users" +on "public"."feedback_cards" +as permissive +for insert +to public +with check (true); + + +create policy "Enable read access for all users" +on "public"."feedback_cards" +as permissive +for select +to public +using (true); + + +create policy "Enable update access for all users" +on "public"."feedback_cards" +as permissive +for update +to public +using (true) +with check (true); + +alter table "public"."user_feedback" enable row level security; + +create policy "anyone can insert" +on "public"."user_feedback" +as permissive +for insert +to public +with check (true); + + +create policy "anyone can read" +on "public"."user_feedback" +as permissive +for select +to public +using (true); + + +create policy "anyone can update" +on "public"."user_feedback" +as permissive +for update +to public +using (true) +with check (true); \ No newline at end of file From b0e869ee2879abcebd7ce726e6795b647519d1e7 Mon Sep 17 00:00:00 2001 From: Marvin Arnold Date: Wed, 6 Dec 2023 22:01:33 -0600 Subject: [PATCH 12/17] fix: reset index --- packages/web/app/feedback/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/app/feedback/page.tsx b/packages/web/app/feedback/page.tsx index 65c58cd5..9c7e983e 100644 --- a/packages/web/app/feedback/page.tsx +++ b/packages/web/app/feedback/page.tsx @@ -41,6 +41,7 @@ export default function UserFeedback() { //wraps around setCurrentIndex((currentIndex + 1) % question_idArray.length); setAnswered(new Set()); + setCurrentIndex(0); } else { alert("Please wait for the rest of the cards to finish loading..."); } From 994b5c98500fc877eb2827c497c3da789c8df539 Mon Sep 17 00:00:00 2001 From: Marvin Arnold Date: Wed, 6 Dec 2023 22:12:39 -0600 Subject: [PATCH 13/17] fix: real index reset --- packages/web/app/feedback/page.tsx | 1 - packages/web/components/ThreeCardLayout.tsx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/app/feedback/page.tsx b/packages/web/app/feedback/page.tsx index 9c7e983e..65c58cd5 100644 --- a/packages/web/app/feedback/page.tsx +++ b/packages/web/app/feedback/page.tsx @@ -41,7 +41,6 @@ export default function UserFeedback() { //wraps around setCurrentIndex((currentIndex + 1) % question_idArray.length); setAnswered(new Set()); - setCurrentIndex(0); } else { alert("Please wait for the rest of the cards to finish loading..."); } diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index 471b9ed2..5e4318e0 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -57,6 +57,7 @@ export default function ThreeCardLayout({ }; setScores(resetScores); + setActiveTab(0); }; //Function that sends comments to supabase under respective card.comment From 69793de9d4c1e9931f678a3b9a4b7e02f8c86b24 Mon Sep 17 00:00:00 2001 From: Marvin Arnold Date: Thu, 7 Dec 2023 00:19:16 -0600 Subject: [PATCH 14/17] feat: improve tabs --- packages/web/components/CommentBoxes.tsx | 2 +- packages/web/components/ThreeCardLayout.tsx | 45 +++++++++++++++------ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/web/components/CommentBoxes.tsx b/packages/web/components/CommentBoxes.tsx index f8602ef2..35d15d34 100644 --- a/packages/web/components/CommentBoxes.tsx +++ b/packages/web/components/CommentBoxes.tsx @@ -55,7 +55,7 @@ export default function CommentBox({ onClick={handleSubmit} className="bg-blue-500 w-full rounded bg-secondary px-4 py-2 text-lg text-white" > - Submit + Submit #{index + 1}
diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index 5e4318e0..9bc8b8ae 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -22,6 +22,20 @@ const criteria = [ ]; const NUM_CARDS_PER_SET = 3; +const RESET_SCORE = 1; + +function getRandomExcluding(set: Set, n: number): number | null { + if (set.size >= n) { + return null; // No available number + } + + let randomNum; + do { + randomNum = Math.floor(Math.random() * n); + } while (set.has(randomNum)); + + return randomNum; +} export default function ThreeCardLayout({ cards, @@ -51,9 +65,9 @@ export default function ThreeCardLayout({ // }, {}); const resetScores = { - Accuracy: 1, - Helpfulness: 1, - Balance: 1, + Accuracy: RESET_SCORE, + Helpfulness: RESET_SCORE, + Balance: RESET_SCORE, }; setScores(resetScores); @@ -111,32 +125,37 @@ export default function ThreeCardLayout({ const newAnswered = new Set(answered); newAnswered.add(index); setAnswered(newAnswered); + + console.log("Submit feedback " + newAnswered.size); + if (newAnswered.size < NUM_CARDS_PER_SET) { + const randUnscored = getRandomExcluding(newAnswered, NUM_CARDS_PER_SET); + console.log("Unscored " + randUnscored); + if (!!randUnscored) setActiveTab(randUnscored); + } } catch (error) {} }; const Tabs = () => { return Array.from({ length: NUM_CARDS_PER_SET }, (_, index) => { const isAnswered = answered.has(index); + const isSelected = activeTab === index; return (
{ setActiveTab(index); }} > - Option {index + 1}{" "} - {isAnswered ? ( - - ) : ( + + Option {index + 1}{" "} - )} +
); }); From b7dcf87b102d529fd210420af580f5cd2a48e563 Mon Sep 17 00:00:00 2001 From: Ayyub Ibrahim Date: Thu, 7 Dec 2023 09:05:49 -0600 Subject: [PATCH 15/17] edit: main func now sends processed queries from the gcloud to supabase --- .../googlecloud/functions/getanswer/main.py | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/googlecloud/functions/getanswer/main.py b/packages/googlecloud/functions/getanswer/main.py index d841ce2a..ba0ffb10 100644 --- a/packages/googlecloud/functions/getanswer/main.py +++ b/packages/googlecloud/functions/getanswer/main.py @@ -2,8 +2,7 @@ import time import math -# blocking google cloud for local deploy -#import google.cloud.logging +import google.cloud.logging import functions_framework from supabase import create_client from dotenv import find_dotenv, load_dotenv @@ -12,8 +11,8 @@ import os import json -#logging_client = google.cloud.logging.Client() -#logging_client.setup_logging() +logging_client = google.cloud.logging.Client() +logging_client.setup_logging() API_VERSION = "0.0.1" @@ -22,8 +21,7 @@ # Setup Supabase client load_dotenv(find_dotenv()) -# disabled for local deploy, reenable for web -""" + try: supabase_url = os.environ["SUPABASE_URL_PRODUCTION"] supabase_key = os.environ["SUPABASE_SERVICE_KEY_PRODUCTION"] @@ -35,7 +33,6 @@ raise ValueError("Supabase URL and key must be set in environment variables") supabase = create_client(supabase_url, supabase_key) -""" def update_supabase(responses, citations, card_id, processing_time_ms): transformed_citations = [] @@ -122,14 +119,11 @@ def getanswer(request): end = time.time() elapsed = int((end - start) * 1000) - # - return(answer) - - # disabled for local deployment, reenable for web - """ update_supabase(responses_data, citations_data, card_id, elapsed) logging.info(f"Completed getanswer in {elapsed} seconds") print(f"\n\t--------- Completed getanswer in {elapsed} seconds --------\n") return ("Answer successfully submitted to Supabase", 200, headers) - """ + + + From 75b920c118c5cf2dfdd607dd745e9e7f9ad48cb2 Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 7 Dec 2023 09:59:41 -0600 Subject: [PATCH 16/17] Shuffles questions and makes citations a drop down --- packages/web/app/feedback/page.tsx | 18 ++++-- packages/web/components/ThreeCardLayout.css | 61 +++++++++++++++++++++ packages/web/components/ThreeCardLayout.tsx | 26 +++++++++ 3 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 packages/web/components/ThreeCardLayout.css diff --git a/packages/web/app/feedback/page.tsx b/packages/web/app/feedback/page.tsx index 65c58cd5..c8a7e8fe 100644 --- a/packages/web/app/feedback/page.tsx +++ b/packages/web/app/feedback/page.tsx @@ -21,7 +21,7 @@ export default function UserFeedback() { // Not the best way to do it-- we really should make each of these a new page and the next/prev buttons // should be linked to the next/prev page. But this is a quick fix for now. - const question_idArray = Array.from({ length: 98 }, (_, index) => index); + const question_idArray = Array.from({ length: 291 }, (_, index) => index); // const handlePrevClick = () => { // if (fullData) { @@ -35,6 +35,16 @@ export default function UserFeedback() { // } // }; + const shuffleArray = (array: Number[]) => { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; + }; + + const shuffledQuestionIds = shuffleArray(question_idArray); + const handleNextClick = () => { if (fullData) { setCardArray(fullData); @@ -58,7 +68,7 @@ export default function UserFeedback() { const { data: cards, error } = await supabase .from(TABLES.FEEDBACK_CARDS) .select("*") - .eq("question_id", 0); + .eq("question_id", shuffledQuestionIds[0]); if (cards) { cardsArray.push(cards); } @@ -76,11 +86,11 @@ export default function UserFeedback() { const getCards = async () => { const cardsArray: Array> = []; try { - for (let i = 1; i <= question_idArray.length; i++) { + for (let i = 1; i < question_idArray.length; i++) { const { data: cards, error } = await supabase .from(TABLES.FEEDBACK_CARDS) .select("*") - .eq("question_id", i); + .eq("question_id", shuffledQuestionIds[i]); if (error) { console.error("Error fetching cards: ", error); diff --git a/packages/web/components/ThreeCardLayout.css b/packages/web/components/ThreeCardLayout.css new file mode 100644 index 00000000..59ed0d0b --- /dev/null +++ b/packages/web/components/ThreeCardLayout.css @@ -0,0 +1,61 @@ +/* ThreeCardLayout.css */ +.dropdown-container { + position: relative; + } + + .dropdown-header { + cursor: pointer; + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; + display: flex; + justify-content: space-between; + align-items: center; + } + + .dropdown-content { + position: absolute; + top: 100%; + left: 0; + background-color: #fff; + border: 1px solid #ccc; + border-top: none; + border-radius: 0 0 4px 4px; + padding: 8px; + z-index: 1; + overflow: scroll; + } + + /* Rotate chevron down icon when dropdown is open */ + .rotate-180 { + transform: rotate(180deg); + } + + +.dropdown-content { + position: absolute; + top: 100%; + left: 0; + background-color: #fff; + border: 1px solid #ccc; + border-top: none; + border-radius: 0 0 4px 4px; + padding: 8px; + z-index: 1; + width: 100%; + } + + + +/* Adjust styles for screens smaller than 600px */ +@media (max-width: 600px) { + .dropdown-header { + font-size: 14px; + } + + .dropdown-content { + padding: 10px; + } + } + + \ No newline at end of file diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index 9bc8b8ae..95a902e2 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -8,11 +8,13 @@ import Rubric from "@/components/Rubric"; import { TABLES } from "@/lib/supabase/db"; import { faCheckCircle, + faChevronDown, faCircleXmark, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; import CommentBox from "./CommentBoxes"; +import Citation from "./Citation"; const criteria = [ { id: "Accuracy", description: "Accuracy" }, @@ -50,6 +52,7 @@ export default function ThreeCardLayout({ }) { const [scores, setScores] = useState>({}); const [activeTab, setActiveTab] = useState(0); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); // Function to update scores const handleScoreChange = (criterionId: string, score: number) => { @@ -192,6 +195,29 @@ export default function ThreeCardLayout({ {element.response}

))} + {card.citations && card.citations.length > 0 && ( +
+
setIsDropdownOpen(!isDropdownOpen)} + > + Citations + +
+ {isDropdownOpen && ( +
+ {card.citations.map((citation, index) => ( + + ))} +
+ )} +
+ )}
From fb7c85db2cf08d1ed66d4fd3e743612706452b61 Mon Sep 17 00:00:00 2001 From: Marvin Arnold Date: Thu, 7 Dec 2023 13:39:26 -0600 Subject: [PATCH 17/17] fix: random ordering and feedback vids --- packages/web/app/feedback/page.tsx | 97 +++++++++++---------- packages/web/components/Citation.tsx | 13 ++- packages/web/components/ThreeCardLayout.tsx | 65 +++++++++----- 3 files changed, 105 insertions(+), 70 deletions(-) diff --git a/packages/web/app/feedback/page.tsx b/packages/web/app/feedback/page.tsx index c8a7e8fe..141c08df 100644 --- a/packages/web/app/feedback/page.tsx +++ b/packages/web/app/feedback/page.tsx @@ -2,10 +2,10 @@ // Import necessary modules and components import ThreeCardLayout from "../../components/ThreeCardLayout"; -import { supabase } from "../../lib/supabase/supabaseClient"; // import NextButton from '@/components/NextButton'; import { ICard } from "@/lib/api"; import { TABLES } from "@/lib/supabase/db"; +import { supabase } from "@/lib/supabase/supabaseClient"; import { useEffect, useState } from "react"; export const dynamic = "force-dynamic"; @@ -13,15 +13,15 @@ export const dynamic = "force-dynamic"; export default function UserFeedback() { // const [currentIndex, setCurrentIndex] = useState(randint(0,177)); const [userName, setUserName] = useState(""); - const [currentIndex, setCurrentIndex] = useState(0); const [answered, setAnswered] = useState>(new Set()); const [fullData, setFullData] = useState> | null>(null); + const [cards, setCards] = useState([]); const [cardArray, setCardArray] = useState> | null>(null); // Not the best way to do it-- we really should make each of these a new page and the next/prev buttons // should be linked to the next/prev page. But this is a quick fix for now. - const question_idArray = Array.from({ length: 291 }, (_, index) => index); + // const question_idArray = Array.from({ length: 291 }, (_, index) => index); // const handlePrevClick = () => { // if (fullData) { @@ -35,6 +35,10 @@ export default function UserFeedback() { // } // }; + const randQuestionId = () => { + return Math.floor(Math.random() * 291); + }; + const shuffleArray = (array: Number[]) => { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); @@ -42,18 +46,19 @@ export default function UserFeedback() { } return array; }; - - const shuffledQuestionIds = shuffleArray(question_idArray); + // const shuffledQuestionIds = shuffleArray(question_idArray); + const [currentIndex, setCurrentIndex] = useState(0); + // console.log(`index ${currentIndex}, val ${shuffledQuestionIds[0]}`); const handleNextClick = () => { - if (fullData) { - setCardArray(fullData); - //wraps around - setCurrentIndex((currentIndex + 1) % question_idArray.length); - setAnswered(new Set()); - } else { - alert("Please wait for the rest of the cards to finish loading..."); - } + // if (fullData) { + setCardArray(fullData); + //wraps around + setCurrentIndex(currentIndex + 1); + setAnswered(new Set()); + // } else { + // alert("Please wait for the rest of the cards to finish loading..."); + // } }; //const handleNameChange = (e) => { @@ -63,14 +68,16 @@ export default function UserFeedback() { useEffect(() => { const getCard = async () => { + const randId = randQuestionId(); + console.log("Fetching cards " + randId); try { const cardsArray: Array> = []; - const { data: cards, error } = await supabase + const { data: newCards, error } = await supabase .from(TABLES.FEEDBACK_CARDS) .select("*") - .eq("question_id", shuffledQuestionIds[0]); - if (cards) { - cardsArray.push(cards); + .eq("question_id", randId); + if (newCards) { + setCards(newCards); } setCardArray(cardsArray); console.log(cards); @@ -78,38 +85,38 @@ export default function UserFeedback() { console.error("Error fetching cards: ", error); // Handle the error appropriately in your UI } - getCards(); + // getCards(); }; getCard(); - }, []); // Run this effect only once when the component mounts + }, [currentIndex]); // Run this effect only once when the component mounts - const getCards = async () => { - const cardsArray: Array> = []; - try { - for (let i = 1; i < question_idArray.length; i++) { - const { data: cards, error } = await supabase - .from(TABLES.FEEDBACK_CARDS) - .select("*") - .eq("question_id", shuffledQuestionIds[i]); + // const getCards = async () => { + // const cardsArray: Array> = []; + // try { + // for (let i = 1; i < question_idArray.length; i++) { + // const { data: cards, error } = await supabase + // .from(TABLES.FEEDBACK_CARDS) + // .select("*") + // .eq("question_id", shuffledQuestionIds[i]); - if (error) { - console.error("Error fetching cards: ", error); - // Handle the error appropriately in your UI - } - console.log(cards); + // if (error) { + // console.error("Error fetching cards: ", error); + // // Handle the error appropriately in your UI + // } + // // console.log(cards); - if (cards) { - cardsArray.push(cards); - } - } - setFullData(cardsArray); - // console.log(fullData); - //setCurrentIndex(Math.floor(Math.random() * cardsArray.length)); - } catch (error) { - console.error("Error fetching cards: ", error); - // Handle the error appropriately in your UI - } - }; + // if (cards) { + // cardsArray.push(cards); + // } + // } + // setFullData(cardsArray); + // // console.log(fullData); + // //setCurrentIndex(Math.floor(Math.random() * cardsArray.length)); + // } catch (error) { + // console.error("Error fetching cards: ", error); + // // Handle the error appropriately in your UI + // } + // }; if (!cardArray) { return
Loading...
; @@ -134,7 +141,7 @@ export default function UserFeedback() {
{ +const Citation = ({ + citation: originalCitation, + index, + fullscreen = false, +}: CitationProps) => { const hasMetadata = Object.values(originalCitation).some( (value) => value !== null && value !== "" ); @@ -35,7 +40,11 @@ const Citation = ({ citation: originalCitation, index }: CitationProps) => { const isYoutube = isYouTubeURL(source_url) && getYouTubeThumbnail(source_url); return ( -
+

#{index + 1}: {title} diff --git a/packages/web/components/ThreeCardLayout.tsx b/packages/web/components/ThreeCardLayout.tsx index 95a902e2..5fd40aec 100644 --- a/packages/web/components/ThreeCardLayout.tsx +++ b/packages/web/components/ThreeCardLayout.tsx @@ -13,8 +13,8 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; -import CommentBox from "./CommentBoxes"; import Citation from "./Citation"; +import CommentBox from "./CommentBoxes"; const criteria = [ { id: "Accuracy", description: "Accuracy" }, @@ -195,29 +195,48 @@ export default function ThreeCardLayout({ {element.response}

))} - {card.citations && card.citations.length > 0 && ( -
-
setIsDropdownOpen(!isDropdownOpen)} - > - Citations - -
- {isDropdownOpen && ( -
- {card.citations.map((citation, index) => ( - - ))} -
- )} + {card.citations && card.citations.length > 0 && ( +
+
setIsDropdownOpen(!isDropdownOpen)} + > + Citations +
- )} + {isDropdownOpen && ( +
+ {card.citations.map((citation, index) => { + const { + URL: source_url, + Name: source_name, + Title: source_title, + Published: source_publish_date, + } = citation as any; + const adaptedCitation = { + source_name, + source_publish_date, + source_title, + source_url, + }; + return ( + + ); + })} +
+ )} +
+ )}