Skip to content

Commit

Permalink
Add basic search agent functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Pixep committed Jun 18, 2024
1 parent f506f62 commit da578aa
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/app/api
Submodule api updated from c0729d to 937023
145 changes: 145 additions & 0 deletions src/app/find-project/FindProjectForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
'use client';

import React, { useState, useCallback, createRef } from 'react';

import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Alert from '@mui/material/Alert';
import TextField from '@mui/material/TextField';
import IconButton from '@mui/material/IconButton';
import CircularProgress from '@mui/material/CircularProgress';
import SendIcon from '@mui/icons-material/Send';

import ReCAPTCHA from 'react-google-recaptcha';
import { RECAPTCHA_SITE_KEY } from '@/constants/constants';

import invokeAgent from './invokeAgent';
import Skeleton from '@mui/material/Skeleton';
import { List, ListItem, ListItemText } from '@mui/material';

const enum FormState {
NotSubmitted,
Submitted,
Error,
Answered,
}

export default function FindProjectForm({ host }: { host: string }) {
const [formState, setFormState] = useState(FormState.NotSubmitted);
const [errorMessage, setErrorMessage] = useState('');
const [prompt, setPrompt] = useState('');
const [previousPrompt, setPreviousPrompt] = useState('');
const [answer, setAnswer] = useState('');
const [projectList, setProjectList] = useState<Array<string>>([]);
const recaptchaRef = createRef<ReCAPTCHA>();

const submitCallback = useCallback((): void => {
if (!recaptchaRef.current) {
return;
}

recaptchaRef.current.reset();
setPreviousPrompt(prompt);
setPrompt('');
setAnswer('');
setErrorMessage('');
setFormState(FormState.Submitted);
recaptchaRef.current.execute();
}, [prompt, recaptchaRef]);

const recaptchaHandler = useCallback(
(token: string | null): void => {
if (!token) {
setErrorMessage('Captcha error');
setFormState(FormState.Error);
return;
}

invokeAgent(host, previousPrompt, token)
.then((answer: string) => {
const sentences = answer.split('- ').map((sentence) => sentence.trim());
setAnswer(sentences[0]);
setProjectList(sentences.slice(1));
setFormState(FormState.Answered);
})
.catch((err) => {
setErrorMessage(`${err}`);
setFormState(FormState.Error);
});
},
[host, previousPrompt]
);

const handleChange = useCallback((event: any) => {
const { value } = event.target;
setPrompt(value);
}, []);

const loading = formState === FormState.Submitted;
const buttonDisabled = loading || prompt.length === 0;

return (
<>
<ReCAPTCHA
ref={recaptchaRef}
size="invisible"
sitekey={RECAPTCHA_SITE_KEY}
onChange={recaptchaHandler}
/>
<TextField
label="Request"
name="request"
onChange={handleChange}
onKeyDown={(event) => {
if (event.key === 'Enter' && !buttonDisabled) {
event.preventDefault();
submitCallback();
}
}}
value={prompt}
sx={{ m: 1, width: '100ch' }}
inputProps={{ maxLength: 100 }}
/>
<IconButton
aria-label="send request"
disabled={buttonDisabled}
onClick={submitCallback}
style={{ height: 50, width: 50 }}
>
{loading ? <CircularProgress size={25} /> : <SendIcon />}
</IconButton>
<br />
<br />
{formState !== FormState.NotSubmitted && (
<Card style={{ marginRight: '20%' }}>
<CardContent>{previousPrompt}</CardContent>
</Card>
)}
<br />
<Card style={{ marginLeft: '20%' }}>
{formState === FormState.Submitted && (
<CardContent>
<Skeleton />
<Skeleton width="75%" />
<Skeleton width="60%" />
</CardContent>
)}
{formState === FormState.Answered && (
<CardContent>
{answer.replace('\n', '')}
<List>
{projectList.map((project, index) => (
<ListItem key={index}>
<ListItemText primary={project} />
</ListItem>
))}
</List>
</CardContent>
)}
{formState === FormState.Error && formState === FormState.Error && (
<Alert severity="error">{errorMessage}</Alert>
)}
</Card>
</>
);
}
32 changes: 32 additions & 0 deletions src/app/find-project/invokeAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

export default async function sendPrompt(
host: string,
prompt: string,
recaptcha: string
) {
try {
const endpoint = `${host}/api/invoke-agent`;
const data = { prompt, recaptcha };
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});

if (!res.ok) {
const errData = await res.json();
const { reason } = errData;
throw new Error(`Failed to invoke agent: ${reason}`);
}

const { answer } = await res.json();
return answer;
} catch (err: any) {
// eslint-disable-next-line no-console
console.log(`Error invoking agent: ${err}`);
throw new Error(`Failed to invoke agent`);
}
}
27 changes: 27 additions & 0 deletions src/app/find-project/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 'use server';

import React, { Suspense } from 'react';
import { Metadata } from 'next';
import Typography from '@mui/material/Typography';

import getHost from '@/utils/getHost';

import FindProjectForm from './FindProjectForm';

export const metadata: Metadata = {
title: 'Ask and find your project!',
};

export default async function FindProject() {
return (
<>
<Typography variant="h1">Find my project!</Typography>
<p>Ask away, and let the AI agent recommend projects for you to contribute to.</p>
<Typography variant="h2">I am looking for ...</Typography>
<br />
<Suspense>
<FindProjectForm host={getHost()} />
</Suspense>
</>
);
}
8 changes: 8 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ function HeaderLinks() {
>
About
</Button>
<Button
/* @ts-ignore: color type not properly recognized */
color="neutral"
component={Link}
href="/find-project"
>
Find projects!
</Button>
</>
);
}
Expand Down

0 comments on commit da578aa

Please sign in to comment.