Skip to content

Commit

Permalink
chore: merge with latest changes from dev
Browse files Browse the repository at this point in the history
  • Loading branch information
cbrzn committed Jan 26, 2024
2 parents 397a1d9 + 2144d6b commit 469593c
Show file tree
Hide file tree
Showing 40 changed files with 1,406 additions and 391 deletions.
342 changes: 342 additions & 0 deletions ops/poetry.lock

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions ops/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[tool.poetry]
name = "scripts"
version = "0.1.0"
description = ""
authors = []

[tool.poetry.dependencies]
python = "~3.10.6"

[tool.poetry.group.dev.dependencies]
omymodels = "^0.15.1"

[tool.poetry.scripts]
generate-types = "scripts.generate_types:run"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
92 changes: 92 additions & 0 deletions ops/scripts/generate_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import subprocess
import sys
import re
from omymodels import create_models

def add_class_config(input_text):
# Regex pattern to match class definitions
class_pattern = r"(class\s+\w+\(BaseModel\):)([\s\S]+?)(\n\n|\Z)"
replacement = r"\1\2\n\n model_config = ConfigDict(\n populate_by_name=True\n )\3"
return re.sub(class_pattern, replacement, input_text)

def snake_to_camel(snake_str):
components = snake_str.split('_')
# Capitalize the first letter of each component except the first one
# and join them together.
return components[0] + ''.join(x.title() for x in components[1:])

def add_alias_no_default(input_text):
# Regex pattern to match properties without a default value
property_pattern = r"(\s+)(\w+_\w+)(:\s+\w+)\s*\n"
def edit(match):
name, type_def = match.group(2), match.group(3)
camel_case = snake_to_camel(name)
return f"{match.group(1)}{name}{type_def} = Field(..., alias=\"{camel_case}\")\n"
return re.sub(property_pattern, edit, input_text)

def add_alias_with_default(input_text):
# Regex pattern to match properties with a default value
property_with_default_pattern = r"(\s+)(\w+_\w+)(:\s+Optional\[\w+\.?\w*\]\s*=\s*None)\n"
def edit(match):
name, type_def = match.group(2), match.group(3)
# Extract the type without Optional and the default value
type_only = re.search(r'Optional\[(\w+\.?\w*)\]', type_def).group(1)
camel_case = snake_to_camel(name)
return f"{match.group(1)}{name}: Optional[{type_only}] = Field(default=None, alias=\"{camel_case}\")\n"
return re.sub(property_with_default_pattern, edit, input_text)

def run():
# Run `supabase db dump --local` to get the db schema
result = subprocess.run(
["npx", "supabase", "db", "dump", "--local"],
capture_output=True,
cwd="../web"
)
if result.returncode != 0:
print("Failed to run 'supabase db dump --local'")
print(result.stderr.decode())
sys.exit(1)

db_schema = result.stdout.decode()

# Split the schema by statement (ending in ;)
statements = [stmt.strip() for stmt in db_schema.split(';')]
# Extract only the "CREATE TABLE" and "CREATE TYPE" statements
create_table_statements = [stmt + ';' for stmt in statements if (
stmt.strip().startswith('CREATE TABLE IF NOT EXISTS "public".') or
stmt.strip().startswith('CREATE TYPE "public".')
)]
create_table_statements = [stmt.replace('CREATE TABLE IF NOT EXISTS "public".', 'CREATE TABLE ') for stmt in create_table_statements]
create_table_statements = [stmt.replace('"public".', '') for stmt in create_table_statements]
# Remove some unsupported SQL features that break omymodels
create_table_statements = [stmt.replace('DEFAULT "gen_random_uuid"() NOT NULL', '') for stmt in create_table_statements]
create_table_statements = [stmt.replace('with time zone DEFAULT "now"() NOT NULL', '') for stmt in create_table_statements]
create_table_statements = [stmt.replace('with time zone', '') for stmt in create_table_statements]
create_table_statements = [re.sub(r'(?m)CONSTRAINT.*\n?', '', stmt) for stmt in create_table_statements]
db_schema = '\n\n'.join(create_table_statements)

# Generate pydantic types using omymodels
types = create_models(
db_schema,
models_type="pydantic",
dump=False
)["code"]

# Convert "= false" and "= true" to proper Python
types = re.sub(r'= false', '= False', types)
types = re.sub(r'= true', '= Talse', types)

# Default Optional types = None
types = re.sub(r'Optional\[(.*?)\]', r'Optional[\1] = None', types)

# Add aliases for all snake case classes
types = add_class_config(types)
types = add_alias_no_default(types)
types = add_alias_with_default(types)
types = types.replace("from pydantic import BaseModel, Json", "from pydantic import BaseModel, Json, Field, ConfigDict")

# Write the types to a file
with open("../workers/fund_public_goods/db/entities.py", "w") as file:
file.write(types)

sys.exit(0)
13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@
"web"
],
"scripts": {
"postinstall": "cd workers && poetry install",
"postinstall": "yarn workers:install && yarn ops:install",
"codegen": "yarn web:codegen && yarn ops:codegen",
"build": "yarn web:build && yarn workers:build",
"dev": "npx concurrently \"yarn web:dev\" \"yarn workers:dev\" \"yarn events:dev\" -k -n web,workers,events",
"web:codegen": "cd web && yarn db:generate-types",
"web:build": "yarn web:env && cd web && yarn build",
"web:dev": "yarn web:env && cd web && yarn dev",
"web:env": "if [ \"$CICD\" != \"true\" ]; then cp .env ./web/.env; fi",
"db:start": "cd web && yarn db:start",
"db:reset": "cd web && yarn db:reset",
"db:stop": "cd web && yarn db:stop",
"workers:install": "cd workers && poetry install",
"workers:build": "cd workers && poetry run build-check",
"workers:dev": "yarn workers:env && cd workers && poetry run python -m uvicorn fund_public_goods.main:app --reload",
"workers:env": "if [ \"$CICD\" != \"true\" ]; then cp .env ./workers/.env; fi",
"events:dev": "npx inngest-cli dev"
"workers:types": "cd ops && poetry run generate-types",
"events:dev": "npx inngest-cli dev",
"ops:install": "cd ops && poetry install",
"ops:codegen": "cd ops && poetry run generate-types"
},
"dependencies": {
"concurrently": "8.2.2",
Expand Down
4 changes: 4 additions & 0 deletions web/app/actions/startWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const startWorker = async (
},
});

if (response.status !== 200) {
throw Error(`Error starting new worker. Status: ${response.status}\nMessage: ${response.statusText}`);
}

const result = await response.json();
if (!result.worker_id || !result.run_id) {
throw new Error("Error starting new worker");
Expand Down
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 (run.error || !run.data) {
console.error(run.error);
throw Error(`Runs with worker_id ${workerId} not found.`);
throw Error(`Runs with id ${params.id} not found.`);
}

const data = run.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>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import { FundingEntry } from "@/components/FundingTable";
import { createSupabaseServerClient } from "@/utils/supabase-server";

export default async function Page({ params }: { params: { id: string } }) {
const workerId = params.id;
const supabase = createSupabaseServerClient();
const run = await supabase
.from("funding_entries_view")
.select("*")
.eq("worker_id", workerId);
.eq("run_id", params.id);

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

return <FundingReview entries={run.data as FundingEntry[]} />;
Expand Down
8 changes: 4 additions & 4 deletions web/components/FundingReview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ export default function FundingReview(props: { entries: FundingEntry[] }) {

// TODO: Handle interaction of funding in multiple chains
const selectedNetwork = projects[0].network as NetworkId;
const selectedToken = projects[0].token || "WETH"
const selectedToken = projects[0].token

const networkIndex = Object.values(SUPPORTED_NETWORKS).indexOf(selectedNetwork)
const networkIndex = Object.values(SUPPORTED_NETWORKS).indexOf(11155111)
const networkName = Object.keys(SUPPORTED_NETWORKS)[networkIndex] as NetworkName
const token = getTokensForNetwork(networkName).find(t => t.name == selectedToken)
const token = getTokensForNetwork(networkName).find(t => t.name == "WETH")

if (!token) {
throw new Error(`Token with name: ${selectedToken} is not valid`)
Expand All @@ -39,7 +39,7 @@ export default function FundingReview(props: { entries: FundingEntry[] }) {
try {
await splitTransferFunds(
// TODO: Modify this with project.recipient; this is just for testing purposes
projects.map((project) => "ADD_YOUR_ADDRESS"),
projects.map((project) => "0xAC39C85F4E54797e4909f70a302d9e11E428135D"),
amounts,
signer,
token.address,
Expand Down
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
Loading

0 comments on commit 469593c

Please sign in to comment.