Skip to content

Commit

Permalink
refactor fill
Browse files Browse the repository at this point in the history
  • Loading branch information
nsbradford committed Jan 25, 2024
1 parent 8fc82a7 commit 1cce215
Show file tree
Hide file tree
Showing 34 changed files with 197 additions and 228 deletions.
11 changes: 0 additions & 11 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "TalkForm",
"image": "mcr.microsoft.com/devcontainers/javascript-node:0-18-bullseye",
"remoteUser": "root",
"containerUser": "root",

// https://community.doppler.com/t/vscode-container-support/104
// https://community.doppler.com/t/doppler-and-github-codespaces/989/2
"containerEnv": {
"DOPPLER_TOKEN": "${localEnv:DOPPLER_CLI_TOKEN}"
},


// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/ellipsis-dev/devcontainer/cli:0.2.7": {},
"ghcr.io/metcalfc/devcontainer-features/doppler:0.1.1": {}
},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
3000,
65455 // Required for Ellipsis
Expand All @@ -36,13 +30,8 @@
"requireLocalPort": true
}
},
"postStartCommand": "nohup bash -c 'ellipsis listener start . &'", // This allows Ellipsis to write file changes in the codespace.

// TODO can't get the yarn install within Dockerfile to show up in dev container.
// This also seems to be what the Microsoft example devcontainers do.
"postCreateCommand": "yarn install --frozen-lockfile",

// Configure tool-specific properties.
"customizations": {
"vscode": {
"settings": {},
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ jobs:
with:
node-version: 18
- name: Install dependencies
# run: npm ci - https://stackoverflow.com/questions/58482655/what-is-the-closest-to-npm-ci-in-yarn
run: yarn install --frozen-lockfile
- name: Install Playwright Browsers
run: npx playwright install --with-deps
Expand All @@ -40,8 +39,7 @@ jobs:
path: playwright-report/
retention-days: 30

# There isn't an easy way to load the devcontainer itself,
# so we make do with just the dockerfile
# TODO use devcontainer CLI
build_devcontainer:
name: Build devcontainer
runs-on: ubuntu-latest
Expand All @@ -52,5 +50,4 @@ jobs:
- name: Build container
run: |
docker build -t devcontainer-instance .
# docker run devcontainer <your-test-command>
12 changes: 0 additions & 12 deletions devcontainer.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
# Use the specified image
# this had trouble with the `yarn install`
# FROM mcr.microsoft.com/devcontainers/typescript-node:1-18-bullseye
# FROM node:18.15.0-alpine
FROM node:18.15.0-bullseye-slim

# codespaces automatically clones the repo into /workspaces/<repo-name>
# WORKDIR /app

# necessary for some of the npm packages
RUN apt-get update && apt-get install -y \
Expand All @@ -20,14 +15,7 @@ RUN apt-get update && apt-get install -y apt-transport-https ca-certificates cur
apt-get update && \
apt-get -y install doppler

# codespaces automatically clones the repo into /workspaces/<repo-name>
# COPY package.json yarn.lock ./

# codespaces automatically clones the repo into /workspaces/<repo-name>
RUN yarn install --frozen-lockfile

# Copy the rest of the project files into the working directory
# COPY . .

# Expose the port your app runs on
EXPOSE 3000
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"openai": "^4.3.1",
"postcss": "8.4.28",
"posthog-js": "^1.77.2",
"promptlayer": "^0.0.7",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "3.3.3",
Expand Down
38 changes: 38 additions & 0 deletions src/components/CreateFormInner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Form } from '@/models';
import { getFormFromSupabase } from '@/utils';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import React, { useEffect, useState } from 'react';
import { Database } from '../../types/supabase';
import { ErrorBox } from './ErrorBox';
import { InnerChat } from '@/components/InnerChat';


export function CreateFormInner(props: { formId: string; }) {
const { formId } = props;
const supabase = createClientComponentClient<Database>();
const [form, setForm] = useState<Form | null>(null);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!form) {
getFormFromSupabase(formId, supabase).then((maybeForm) => {
if (maybeForm instanceof Error) {
console.error(maybeForm.message);
setError(maybeForm);
} else {
setForm(maybeForm);
}
});
}
}, []); // The empty array ensures this effect runs only once on mount
return form ? (
<InnerChat form={form} supabase={supabase} />
) : (
<>
{error ? (
ErrorBox(error)
) : (
<h1 className="text-3xl font-extrabold mb-6">Loading...</h1>
)}
</>
);
}
12 changes: 12 additions & 0 deletions src/components/ErrorBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

export function ErrorBox(
error: Error
): React.ReactNode {
return (
<div className="w-4/5 md:w-1/2 lg:w-1/3 bg-red-300 shadow-md p-6 rounded-lg mt-4">
<h1 className="text-xl font-extrabold mb-6">Error</h1>
<p className="font-mono text-sm whitespace-pre-wrap">{error.message}</p>
</div>
);
}
168 changes: 37 additions & 131 deletions src/pages/forms/fill/[id].tsx → src/components/InnerChat.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,25 @@
import { MessageUI } from '@/components/chat';
import NavBar from '@/components/home/NavBar';
import { workSans } from '@/components/misc';
import { PROMPT_FILL } from '@/prompts';
import { ChatMessage, Form } from '@/types';
import { ChatMessage, Form } from '@/models';
import {
callLLM,
getFormFromSupabase,
submitResponseToSupabase,
submitResponseToSupabase
} from '@/utils';
import { faArrowRight } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
SupabaseClient,
createClientComponentClient,
} from '@supabase/auth-helpers-nextjs';
import { useRouter } from 'next/router';
import { SupabaseClient } from '@supabase/auth-helpers-nextjs';
import React, { useEffect, useRef, useState } from 'react';
import { Database } from '../../../../types/supabase';
import { Database } from '../../types/supabase';
import { SubmissionBox } from './SubmissionBox';
import { ErrorBox } from './ErrorBox';
import { MiniSpinner } from './MiniSpinner';

// Makes it much easier to track renders/fetches by wrapping the component.
export default function CreateForm() {
const router = useRouter();
// If the page is still loading (especially during ISR or fallback scenarios), show a loading state
const formId = router.query.id as string;
return (
<>
<div
className={`bg-gradient-to-br from-indigo-200 via-red-200 to-yellow-100 min-h-screen`}
>
<NavBar />
<div
className={`${workSans.className} flex flex-col items-center min-h-screen py-20 px-4`}
>
{router.isFallback || typeof formId !== 'string' ? (
<h1 className="text-3xl font-extrabold mb-6">Loading...</h1>
) : (
<CreateFormInner formId={formId} />
)}
</div>
</div>
</>
);
}

export function CreateFormInner(props: { formId: string }) {
const { formId } = props;
const supabase = createClientComponentClient<Database>();
const [form, setForm] = useState<Form | null>(null);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!form) {
getFormFromSupabase(formId, supabase).then((maybeForm) => {
if (maybeForm instanceof Error) {
console.error(maybeForm.message);
setError(maybeForm);
} else {
setForm(maybeForm);
}
});
}
}, []); // The empty array ensures this effect runs only once on mount
return form ? (
<InnerChat form={form} supabase={supabase} />
) : (
<>
{error ? (
ErrorBox(error)
) : (
<h1 className="text-3xl font-extrabold mb-6">Loading...</h1>
)}
</>
);
}
export function InnerChat(props: {
form: Form;
supabase: SupabaseClient<Database>;
}) {
/**
* Main form-filling chat UI
* @param props
* @returns
*/
export function InnerChat(props: { form: Form; supabase: SupabaseClient<Database>;}) {
const { form } = props;
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [inputValue, setInputValue] = useState('');
Expand All @@ -85,17 +29,29 @@ export function InnerChat(props: {
const [submission, setSubmission] = useState<object | null>(null);
const [error, setError] = useState<Error | null>(null);

useEffect(() => {
if (!isWaiting && inputRef.current) {
inputRef.current.focus();
}
}, [isWaiting]);

useEffect(() => {
if (messages.length === 0) {
handleSubmit();
}
}, []);


const handleSubmit = async (userMessage?: string) => {
const messagesToSend =
userMessage && userMessage.trim()
? [
...messages,
{
role: 'user' as const,
content: `{ "user_message": "${userMessage.trim()}" }`, // extra JSON to keep model behaving
},
]
: messages;
const messagesToSend = userMessage && userMessage.trim()
? [
...messages,
{
role: 'user' as const,
content: `{ "user_message": "${userMessage.trim()}" }`,
},
]
: messages;
setMessages(messagesToSend);
setInputValue('');
setIsWaiting(true);
Expand Down Expand Up @@ -147,18 +103,6 @@ export function InnerChat(props: {
handleSubmit(inputValue);
}
};
useEffect(() => {
if (!isWaiting && inputRef.current) {
// Ensure the input gets focus when isWaiting transitions to false
inputRef.current.focus();
}
}, [isWaiting]); // Track changes to the isWaiting state

useEffect(() => {
if (messages.length === 0) {
handleSubmit();
}
}, []); // The empty array ensures this effect runs only once on mount

return (
<>
Expand All @@ -172,8 +116,7 @@ export function InnerChat(props: {
<MessageUI
key={index}
role={message.role}
content={message.content}
/>
content={message.content} />
))}
<div className="mt-4 flex">
<input
Expand All @@ -183,8 +126,7 @@ export function InnerChat(props: {
onChange={(e) => setInputValue(e.target.value)}
disabled={isWaiting || isDone}
onKeyPress={handleKeyPress}
ref={inputRef}
/>
ref={inputRef} />
<button
className="ml-2 py-2 px-4 bg-rose-400 hover:bg-rose-300 text-white rounded-lg disabled:bg-gray-300"
onClick={() => handleSubmit(inputValue)}
Expand All @@ -204,39 +146,3 @@ export function InnerChat(props: {
</>
);
}

function MiniSpinner() {
return (
<div
className="inline-block h-5 w-5 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"
role="status"
>
<span className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]">
Loading...
</span>
</div>
);
}

function ErrorBox(error: Error): React.ReactNode {
return (
<div className="w-4/5 md:w-1/2 lg:w-1/3 bg-red-300 shadow-md p-6 rounded-lg mt-4">
<h1 className="text-xl font-extrabold mb-6">Error</h1>
<p className="font-mono text-sm whitespace-pre-wrap">{error.message}</p>
</div>
);
}

function SubmissionBox(submission: object): React.ReactNode {
return (
<div
id="submissionBox"
className="w-4/5 md:w-1/2 lg:w-1/3 bg-white shadow-md p-6 rounded-lg mt-4"
>
<h1 className="text-xl font-extrabold mb-6">Submission</h1>
<p className="font-mono text-sm whitespace-pre-wrap">
{JSON.stringify(submission, null, 2)}
</p>
</div>
);
}
10 changes: 10 additions & 0 deletions src/components/MiniSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

export function MiniSpinner() {
return (
<div
className="inline-block h-5 w-5 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"
role="status"
><span className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]">Loading...</span></div>
);
}
15 changes: 15 additions & 0 deletions src/components/SubmissionBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

export function SubmissionBox(submission: object): React.ReactNode {
return (
<div
id="submissionBox"
className="w-4/5 md:w-1/2 lg:w-1/3 bg-white shadow-md p-6 rounded-lg mt-4"
>
<h1 className="text-xl font-extrabold mb-6">Submission</h1>
<p className="font-mono text-sm whitespace-pre-wrap">
{JSON.stringify(submission, null, 2)}
</p>
</div>
);
}
Loading

0 comments on commit 1cce215

Please sign in to comment.