Skip to content

Commit 4ce149d

Browse files
committed
adding settings, refining prompts
1 parent a440f49 commit 4ce149d

File tree

11 files changed

+203
-69
lines changed

11 files changed

+203
-69
lines changed

app/src/app/api/search/route.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ const VIDEO_SEARCH_SERVICE = process.env.VIDEO_SEARCH_SERVICE;
44
const SEARCH_API = `${VIDEO_SEARCH_SERVICE}/api/videos/search`;
55

66
export async function GET(request: NextRequest) {
7-
const searchParams = request.nextUrl.searchParams;
8-
const question = searchParams.get('question');
9-
const response = await fetch(`${SEARCH_API}?question=${question}`);
7+
const response = await fetch(`${SEARCH_API}${request.nextUrl.search}`);
108
const data = await response.json();
119

1210
return Response.json({ ...data });

app/src/app/page.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Modal from '@/components/Modal';
77
import UploadButton from '@/components/UploadButton';
88
import VideoForm from '@/components/VideoForm';
99
import Markdown from '@/components/Markdown';
10+
import SettingsIcon from '@/components/SettingsIcon';
11+
import SettingsMenu from '@/components/SettingsMenu';
1012

1113
interface VideoSearchResult {
1214
videos: VideoDocument[];
@@ -16,6 +18,8 @@ interface VideoSearchResult {
1618
export default function Home() {
1719
const [results, setResults] = useState<VideoSearchResult>();
1820
const [showModal, setShowModal] = useState(false);
21+
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
22+
const [selectedOption, setSelectedOption] = useState('OpenAI');
1923

2024
const handleSubmit = async (videos: string[]) => {
2125
// API call with the videos URLs
@@ -31,11 +35,23 @@ export default function Home() {
3135
const handleSearch = async (question: string) => {
3236
// Replace with your API call
3337
setResults(undefined);
34-
const response = await fetch(`/api/search?question=${question}`);
38+
const response = await fetch(
39+
`/api/search?api=${selectedOption.toLowerCase()}&question=${question}`,
40+
);
3541
const data: VideoSearchResult = await response.json();
3642
setResults(data);
3743
};
3844

45+
const handleToggleSettings = () => {
46+
setIsSettingsOpen(!isSettingsOpen);
47+
};
48+
49+
const handleSelectionChange = (
50+
event: React.ChangeEvent<HTMLInputElement>,
51+
) => {
52+
setSelectedOption(event.target.value);
53+
};
54+
3955
return (
4056
<>
4157
<main className="flex min-h-screen flex-col items-center justify-between p-24">
@@ -53,14 +69,20 @@ export default function Home() {
5369
)}
5470
</div>
5571
</main>
56-
72+
<SettingsMenu
73+
isOpen={isSettingsOpen}
74+
onSelectionChange={handleSelectionChange}
75+
selectedOption={selectedOption}
76+
/>
5777
<Modal
5878
show={showModal}
5979
onClose={() => {
6080
setShowModal(false);
6181
}}>
6282
<VideoForm onSubmit={handleSubmit} />
6383
</Modal>
84+
85+
<SettingsIcon onToggle={handleToggleSettings} />
6486
<UploadButton
6587
onClick={() => {
6688
setShowModal(true);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export interface SettingsIconProps {
2+
onToggle: () => void;
3+
}
4+
5+
function SettingsIcon({ onToggle }: SettingsIconProps) {
6+
return (
7+
<button
8+
onClick={onToggle}
9+
className="fixed top-4 right-4 bg-indigo-600 hover:bg-indigo-700 text-white p-4 rounded-full shadow-lg">
10+
<svg
11+
className="w-6 h-6"
12+
fill="none"
13+
stroke="currentColor"
14+
viewBox="0 0 24 24"
15+
xmlns="http://www.w3.org/2000/svg">
16+
<path
17+
fillRule="evenodd"
18+
clipRule="evenodd"
19+
d="M12.7848 0.449982C13.8239 0.449982 14.7167 1.16546 14.9122 2.15495L14.9991 2.59495C15.3408 4.32442 17.1859 5.35722 18.9016 4.7794L19.3383 4.63233C20.3199 4.30175 21.4054 4.69358 21.9249 5.56605L22.7097 6.88386C23.2293 7.75636 23.0365 8.86366 22.2504 9.52253L21.9008 9.81555C20.5267 10.9672 20.5267 13.0328 21.9008 14.1844L22.2504 14.4774C23.0365 15.1363 23.2293 16.2436 22.7097 17.1161L21.925 18.4339C21.4054 19.3064 20.3199 19.6982 19.3382 19.3676L18.9017 19.2205C17.1859 18.6426 15.3408 19.6754 14.9991 21.405L14.9122 21.845C14.7167 22.8345 13.8239 23.55 12.7848 23.55H11.2152C10.1761 23.55 9.28331 22.8345 9.08781 21.8451L9.00082 21.4048C8.65909 19.6754 6.81395 18.6426 5.09822 19.2205L4.66179 19.3675C3.68016 19.6982 2.59465 19.3063 2.07505 18.4338L1.2903 17.1161C0.770719 16.2436 0.963446 15.1363 1.74956 14.4774L2.09922 14.1844C3.47324 13.0327 3.47324 10.9672 2.09922 9.8156L1.74956 9.52254C0.963446 8.86366 0.77072 7.75638 1.2903 6.8839L2.07508 5.56608C2.59466 4.69359 3.68014 4.30176 4.66176 4.63236L5.09831 4.77939C6.81401 5.35722 8.65909 4.32449 9.00082 2.59506L9.0878 2.15487C9.28331 1.16542 10.176 0.449982 11.2152 0.449982H12.7848ZM12 15.3C13.8225 15.3 15.3 13.8225 15.3 12C15.3 10.1774 13.8225 8.69998 12 8.69998C10.1774 8.69998 8.69997 10.1774 8.69997 12C8.69997 13.8225 10.1774 15.3 12 15.3Z"
20+
fill="#fff"
21+
/>
22+
</svg>
23+
</button>
24+
);
25+
}
26+
27+
export default SettingsIcon;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export interface SettingsMenuProps {
2+
isOpen: boolean;
3+
onSelectionChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
4+
selectedOption: string;
5+
}
6+
7+
function SettingsMenu({
8+
isOpen,
9+
onSelectionChange,
10+
selectedOption,
11+
}: SettingsMenuProps) {
12+
if (!isOpen) return null;
13+
14+
return (
15+
<div className="fixed top-16 right-4 bg-white border rounded shadow p-4">
16+
<form>
17+
<label>
18+
<input
19+
type="radio"
20+
value="OpenAI"
21+
checked={selectedOption === 'OpenAI'}
22+
onChange={onSelectionChange}
23+
/>
24+
OpenAI
25+
</label>
26+
<br />
27+
<label>
28+
<input
29+
type="radio"
30+
value="Google"
31+
checked={selectedOption === 'Google'}
32+
onChange={onSelectionChange}
33+
/>
34+
Google
35+
</label>
36+
</form>
37+
</div>
38+
);
39+
}
40+
41+
export default SettingsMenu;

app/src/components/UploadButton.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ function UploadButton({ onClick }: UploadButtonProps) {
1414
viewBox="0 0 24 24"
1515
xmlns="http://www.w3.org/2000/svg">
1616
<path
17-
strokeLinecap="round"
18-
strokeLinejoin="round"
19-
strokeWidth="2"
20-
d="M4 17v2a2 2 0 0 1 2 2h2m8 0h2a2 2 0 0 1 2-2v-2m-4-5l-4-4m0 0l-4 4m4-4v12"></path>
17+
xmlns="http://www.w3.org/2000/svg"
18+
d="M12.5535 2.49392C12.4114 2.33852 12.2106 2.25 12 2.25C11.7894 2.25 11.5886 2.33852 11.4465 2.49392L7.44648 6.86892C7.16698 7.17462 7.18822 7.64902 7.49392 7.92852C7.79963 8.20802 8.27402 8.18678 8.55352 7.88108L11.25 4.9318V16C11.25 16.4142 11.5858 16.75 12 16.75C12.4142 16.75 12.75 16.4142 12.75 16V4.9318L15.4465 7.88108C15.726 8.18678 16.2004 8.20802 16.5061 7.92852C16.8118 7.64902 16.833 7.17462 16.5535 6.86892L12.5535 2.49392Z"
19+
fill="#fff"
20+
/>
21+
<path
22+
xmlns="http://www.w3.org/2000/svg"
23+
d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z"
24+
fill="#fff"
25+
/>
2126
</svg>
2227
</button>
2328
);

app/src/components/VideoForm.tsx

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,86 @@
1-
import { Formik, Form, Field, FieldArray } from 'formik';
1+
import { Formik, Form, Field } from 'formik';
2+
import CircularProgress from './CircularProgress';
23

34
export interface VideoFormProps {
45
onSubmit: (videos: string[]) => Promise<void> | void;
56
}
67

78
function VideoForm({ onSubmit }: VideoFormProps) {
9+
const isValidYouTubeUrlOrId = (url: string) => {
10+
const regExp = /^.*(youtu.be\/|v\/|e\/|u\/\w+\/|embed\/|v=)?([^#&?]*).*/;
11+
const match = url.match(regExp);
12+
return (
13+
Array.isArray(match) && (match[2].length === 11 || url.length === 11)
14+
);
15+
};
16+
17+
const validateYouTubeLinks = ({ videos }: { videos: string }) => {
18+
const errors: { videos?: string } = {};
19+
if (typeof videos !== 'string') {
20+
errors.videos = 'Please enter YouTube links.';
21+
22+
return errors;
23+
}
24+
25+
const urls = videos.split(',');
26+
const invalidUrls = urls.filter(
27+
(url) => !isValidYouTubeUrlOrId(url.trim()),
28+
);
29+
30+
if (invalidUrls.length > 0) {
31+
errors.videos = [
32+
'The following values are not valid YouTube IDs or links.',
33+
...invalidUrls,
34+
].join(',');
35+
36+
return errors;
37+
}
38+
};
39+
840
return (
941
<Formik
10-
initialValues={{ videos: [''] }}
11-
onSubmit={(values) => {
12-
void onSubmit(values.videos);
13-
}}>
14-
{({ values }) => (
15-
<Form className="space-y-4">
16-
<FieldArray name="videos">
17-
{({ insert, remove }) => (
18-
<div>
19-
{values.videos.map((video, index) => (
20-
<div
21-
key={index}
22-
className="flex flex-wrap items-center gap-2">
23-
<Field
24-
name={`videos.${index}`}
25-
placeholder="Enter YouTube URL"
26-
className="flex-1 px-3 py-2 border rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-600"
27-
/>
28-
<button
29-
type="button"
30-
onClick={() => remove(index)}
31-
className="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600 focus:outline-none">
32-
Remove
33-
</button>
34-
<button
35-
type="button"
36-
onClick={() => {
37-
insert(index, '');
38-
}}
39-
className="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600 focus:outline-none">
40-
Add
41-
</button>
42-
</div>
43-
))}
44-
</div>
45-
)}
46-
</FieldArray>
47-
<div className="flex justify-center">
42+
initialValues={{ videos: '' }}
43+
onSubmit={async ({ videos }, { setSubmitting }) => {
44+
const videoIds = videos.split(',').map((url) => url.trim());
45+
setSubmitting(true);
46+
try {
47+
await onSubmit(videoIds);
48+
} catch (e) {}
49+
setSubmitting(false);
50+
}}
51+
validate={validateYouTubeLinks}>
52+
{({ errors, touched, isSubmitting }) => (
53+
<Form>
54+
<Field
55+
name="videos"
56+
as="textarea"
57+
placeholder="Enter comma-separated YouTube links or IDs, e.g., https://youtu.be/dQw4w9WgXcQ, 3fumBcKC6RE"
58+
className="w-full p-2 border rounded shadow-sm"
59+
/>
60+
{typeof errors.videos === 'string' && touched.videos === true && (
61+
<ul>
62+
{errors.videos.split(',').map((error) => (
63+
<li key={error}>{error}</li>
64+
))}
65+
</ul>
66+
)}
67+
{isSubmitting
68+
? (
69+
<button
70+
onClick={(e) => {
71+
e.preventDefault();
72+
}}
73+
className="mt-2 bg-blue-500 text-white p-2 rounded">
74+
<CircularProgress />
75+
</button>
76+
)
77+
: (
4878
<button
4979
type="submit"
50-
className="py-2 px-4 bg-indigo-600 text-white rounded hover:bg-indigo-700 focus:outline-none">
80+
className="mt-2 bg-blue-500 text-white p-2 rounded">
5181
Submit
5282
</button>
53-
</div>
83+
)}
5484
</Form>
5585
)}
5686
</Formik>

services/video-search/src/api/search.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,17 @@ export default function initialize({
4949

5050
const haveAnswers = await answerVectorStore.checkIndexExists();
5151

52-
if (haveAnswers) {
53-
log.debug(`Searching for closest answer to question: ${question}`, {
54-
location: `${prefix}.search.getAnswer`,
55-
question,
56-
});
52+
if (haveAnswers && config.searches.answerCache) {
53+
log.debug(
54+
`Searching for closest answer to question: ${semanticQuestion}`,
55+
{
56+
location: `${prefix}.search.getAnswer`,
57+
question,
58+
},
59+
);
5760

5861
const [result] = await answerVectorStore.similaritySearchWithScore(
59-
question,
62+
semanticQuestion,
6063
1,
6164
);
6265

@@ -65,15 +68,13 @@ export default function initialize({
6568
`Found closest answer with score: ${String(result[1])}`,
6669
{
6770
location: `${prefix}.search.getAnswer`,
68-
answer: result[0],
6971
score: result[1],
7072
},
7173
);
7274

7375
if (Array.isArray(result) && result.length > 0) {
74-
log.debug(`Found answer: ${result[0].metadata.answer}`, {
76+
log.debug(`Found answer to question ${semanticQuestion}`, {
7577
location: `${prefix}.search.getAnswer`,
76-
result: result[0].metadata,
7778
});
7879

7980
return result[0].metadata;

services/video-search/src/api/store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default function initialize({
3333
indexName: `${prefix}-${config.redis.ANSWER_INDEX_NAME}`,
3434
keyPrefix: `${prefix}-${config.redis.ANSWER_PREFIX}`,
3535
indexOptions: {
36-
ALGORITHM: VectorAlgorithms.HNSW,
36+
ALGORITHM: VectorAlgorithms.FLAT,
3737
DISTANCE_METRIC: 'L2',
3838
},
3939
});

services/video-search/src/api/templates/answers.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { PromptTemplate } from 'langchain/prompts';
22

33
const answerTemplate = `
4-
You are an expert in answering questions about Redis.
4+
You are an expert in answering questions about Redis and Redis Stack.
55
Your goal is to take a question and some relevant information extracted from videos and return the answer to the question.
6-
Try to mostly use the provided video info, but if you can't find the answer there you can use other resources.
7-
Make sure your answer is related to Redis. All questions are about Redis. For example, if a question is asking about strings, it is asking about Redis strings.
8-
Make the answer look pretty by formatting it using markdown.
6+
7+
- Try to mostly use the provided video info, but if you can't find the answer there you can use other resources.
8+
- Make sure your answer is related to Redis. All questions are about Redis. For example, if a question is asking about strings, it is asking about Redis strings.
9+
- The answer should be formatted as a reference document using markdown. Make all headings and links bold, and add new paragraphs around any code blocks.
10+
- Your answer should include as much detail as possible and be no shorter than 500 words.
911
1012
Here is some extracted video information relevant to the question: {data}
1113

services/video-search/src/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export default {
5151
PORT: PORT ?? 8000,
5252
},
5353
searches: {
54-
KNN: 10,
54+
KNN: 3,
55+
answerCache: false,
5556
},
5657
log: {
5758
LEVEL: LOG_LEVEL ?? 'info',

0 commit comments

Comments
 (0)