Skip to content

Commit

Permalink
Merge branch 'namesty/progress-page' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
namesty committed Jan 26, 2024
2 parents 8ddabfa + 6735581 commit f5890da
Show file tree
Hide file tree
Showing 17 changed files with 624 additions and 175 deletions.
5 changes: 2 additions & 3 deletions web/app/strategy/[id]/page.tsx → web/app/r/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export default async function StrategyPage({
}: {
params: { id: string };
}) {
const workerId = params.id;
const supabase = createSupabaseServerClient();

// // Fetch the runs for this worker
Expand All @@ -25,13 +24,13 @@ export default async function StrategyPage({
)
`
)
.eq("worker_id", workerId)
.eq("id", params.id)
.order("created_at", { ascending: false })
.single();

if (runs.error || !runs.data) {
console.error(runs.error);
throw Error(`Runs with worker_id ${workerId} not found.`);
throw Error(`Runs with id ${params.id} not found.`);
}

const data = runs.data.strategy_entries as unknown as StrategyWithProjects;
Expand Down
40 changes: 40 additions & 0 deletions web/app/r/[id]/progress/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Logs from "@/components/Logs";
import TextField from "@/components/TextField";
import { createSupabaseServerClient } from "@/utils/supabase-server";

async function PromptField(props: { runId: string }) {
const supabase = createSupabaseServerClient()

const { data: run } = await supabase.from('runs').select(`
id,
prompt
`).eq("id", props.runId).single()

if (!run) {
throw new Error(`Run with ID '${props.runId}' not found`)
}

return <TextField
label='Results for'
value={run.prompt}
readOnly={true}
/>
}

export default function ProgressPage(props: {
params: {
id: string
}
}) {
return (
<div className="w-full flex justify-center h-full p-16">
<div className="w-full max-w-3xl flex flex-col gap-8">
<div className="flex flex-col gap-2">
<PromptField runId={props.params.id} />
</div>
<div className="w-full h-[1px] bg-indigo-500" />
<Logs runId={props.params.id} />
</div>
</div>
)
}
5 changes: 3 additions & 2 deletions web/components/LoadingCircle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import clsx from "clsx";
interface LoadingCircleProps {
strokeWidth?: number;
className?: string;
hideText?: boolean
}

const LoadingCircle = ({ strokeWidth = 12, className }: LoadingCircleProps) => {
const LoadingCircle = ({ strokeWidth = 12, className, hideText }: LoadingCircleProps) => {
return (
<div role="status">
<svg
Expand Down Expand Up @@ -34,7 +35,7 @@ const LoadingCircle = ({ strokeWidth = 12, className }: LoadingCircleProps) => {
strokeLinecap="round"
/>
</svg>
<span className="sr-only">Loading...</span>
{ hideText ? <></> : <span className="sr-only">Loading...</span> }
</div>
);
};
Expand Down
30 changes: 30 additions & 0 deletions web/components/Logs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use server"

import { createSupabaseServerClient } from "@/utils/supabase-server";
import RealtimeLogs from "./RealtimeLogs";

export default async function Logs(props: { runId: string }) {
const supabase = createSupabaseServerClient()

const { data: run } = await supabase.from('runs').select(`
id,
prompt,
logs(
id,
run_id,
created_at,
value,
ended_at,
status,
step_name
)
`).eq("id", props.runId).single()

if (!run) {
throw new Error(`Run with ID '${props.runId}' not found`)
}

return (
<RealtimeLogs logs={run.logs} run={run} />
)
}
44 changes: 4 additions & 40 deletions web/components/Prompt.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
"use client";

import { ChangeEvent, useEffect, useState } from "react";
import { ChangeEvent, useState } from "react";
import TextField from "./TextField";
import ChatInputButton from "./ChatInputButton";
import { SparkleIcon } from "./Icons";
import Image from "next/image";
import { startWorker } from "@/app/actions";
import { createSupabaseBrowserClient } from "@/utils/supabase-browser";
import { Tables } from "@/supabase/dbTypes";
import { useRouter } from "next/navigation";
import LoadingCircle from "./LoadingCircle";
import PromptInput from "./PromptInput";
Expand All @@ -25,60 +22,27 @@ const PROMPT_SUGESTIONS = [
export default function Prompt() {
const [prompt, setPrompt] = useState<string>("");
const [isWaiting, setIsWaiting] = useState(false);
const [workerId, setWorkerId] = useState<string>();
const [runId, setRunId] = useState<string>();
const [status, setStatus] = useState<string>();

const router = useRouter();
const supabase = createSupabaseBrowserClient();

const sendPrompt = async (prompt: string) => {
setIsWaiting(true);
try {
const response = await startWorker(prompt);
setWorkerId(response.workerId);
setRunId(response.runId);
router.push(`/r/${response.runId}/progress`)
} finally {
setIsWaiting(false);
}
};

useEffect(() => {
if (runId) {
const channel = supabase
.channel("logs-added")
.on(
"postgres_changes",
{
event: "INSERT",
table: "logs",
schema: "public",
filter: `run_id=eq.${runId}`,
},
(payload: { new: Tables<"logs"> }) => {
if (payload.new.message === "STRATEGY_CREATED") {
router.push(`strategy/${workerId}`);
return;
}
setStatus(payload.new.message);
}
)
.subscribe();

return () => {
supabase.removeChannel(channel);
};
}
}, [workerId, supabase, runId, workerId]);

return (
<>
<div className='mx-auto max-w-screen-md'>
{status ? (
{isWaiting ? (
<div className='w-full'>
<div className='flex flex-col justify-center items-center gap-2'>
<LoadingCircle className='w-[40px] h-[40px] ml-10' />
<div className='flex text-md pl-12 text-center'>{status}</div>
<div className='flex text-md pl-12 text-center'>Loading</div>
</div>
</div>
) : (
Expand Down
142 changes: 142 additions & 0 deletions web/components/RealtimeLogs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"use client"

import { Tables } from "@/supabase/dbTypes";
import { createSupabaseBrowserClient } from "@/utils/supabase-browser";
import clsx from "clsx";
import { useRouter } from "next/navigation";
import { useState, useEffect } from "react";
import LoadingCircle from "./LoadingCircle";
import Button from "./Button";

const UNSTARTED_TEXTS: Record<Tables<"logs">["step_name"], string> = {
FETCH_PROJECTS: "Search for relevant projects",
EVALUATE_PROJECTS: "Evaluate proof of impact",
ANALYZE_FUNDING: "Analyze funding needs",
SYNTHESIZE_RESULTS: "Synthesize results",
}

const LOADING_TEXTS: Record<Tables<"logs">["step_name"], string> = {
FETCH_PROJECTS: "Searching for relevant projects...",
EVALUATE_PROJECTS: "Evaluating proof of impact...",
ANALYZE_FUNDING: "Analyzing funding needs...",
SYNTHESIZE_RESULTS: "Synthesizing results...",
}

const STEPS_ORDER: Record<Tables<"logs">["step_name"], number> = {
FETCH_PROJECTS: 1,
EVALUATE_PROJECTS: 2,
ANALYZE_FUNDING: 3,
SYNTHESIZE_RESULTS: 4,
}

const getLogMessage = (log: Tables<"logs">) => {
switch (log.status) {
case "NOT_STARTED": return UNSTARTED_TEXTS[log.step_name]
case "IN_PROGRESS": return LOADING_TEXTS[log.step_name]
case "COMPLETED": return log.value ?? `Completed: ${UNSTARTED_TEXTS[log.step_name]}`
case "ERRORED": return `Error while ${LOADING_TEXTS[log.step_name].toLowerCase()}`
}
}

const checkIfFinished = (logs: Tables<"logs">[]) => {
const sortedLogs = logs.sort((a, b) => {
return STEPS_ORDER[a.step_name] - STEPS_ORDER[b.step_name]
})
const lastStep = sortedLogs.slice(-1)[0];
const isFinished = lastStep.status === "COMPLETED" && lastStep.step_name === "SYNTHESIZE_RESULTS"

return isFinished
}

export default function RealtimeLogs(props: {
logs: Tables<"logs">[]
run: {
id: string;
prompt: string;
}
}) {
const [logs, setLogs] = useState<Tables<"logs">[]>(props.logs)
const supabase = createSupabaseBrowserClient();
const router = useRouter()

const sortedLogsWithSteps = logs.sort((a, b) => {
return STEPS_ORDER[a.step_name] - STEPS_ORDER[b.step_name]
})

const isFinished = checkIfFinished(sortedLogsWithSteps)

const navigateToStrategy = () => {
router.push(`./`)
}

useEffect(() => {
const channel = supabase
.channel("logs-added")
.on(
"postgres_changes",
{
event: "UPDATE",
table: "logs",
schema: "public",
filter: `run_id=eq.${props.run.id}`,
},
async () => {
const response = await supabase.from("logs").select(`
id,
run_id,
created_at,
value,
ended_at,
status,
step_name
`).eq("run_id", props.run.id)
const updatedLogs = response.data

if (!updatedLogs) {
throw new Error(`Logs for Run with ID '${props.run.id}' not found`)
}

setLogs([...updatedLogs])

if (checkIfFinished(updatedLogs)) {
navigateToStrategy()
return;
}
}
)
.subscribe()

return () => {
supabase.removeChannel(channel);
};
}, [supabase, props.run.id]);

return (
<>
<div className="flex flex-col gap-4">
<p>Results:</p>
<div className="flex flex-col gap-2">
{ sortedLogsWithSteps.map(log => (
<div key={log.id} className={clsx(
"p-4 flex flex-nowrap items-center gap-2 border border-indigo-500 rounded-lg bg-indigo-500/50 cursor-pointer",
log.status === "IN_PROGRESS" ? "text-indigo-50" : ""
)}>
{ log.status === "IN_PROGRESS" ? <LoadingCircle hideText={true} className="!stroke-indigo-500 text-indigo-200" /> : <></>}
<p className={clsx(
"flex-1",
log.status === "NOT_STARTED" ? "text-indigo-50" :
log.status === "IN_PROGRESS" ? "text-indigo-500" :
log.status === "COMPLETED" ? "text-indigo-800" :
""
)}>{ getLogMessage(log) }</p>
<div className="w-6 h-6"></div>
</div>
)) }
</div>
</div>
<Button disabled={!isFinished} onClick={() => navigateToStrategy()}>
View Results
</Button>
</>
)
}
Loading

0 comments on commit f5890da

Please sign in to comment.