Skip to content

Commit

Permalink
Merge branch 'joriskalz-chat-with-youtube'
Browse files Browse the repository at this point in the history
  • Loading branch information
enricoros committed Apr 11, 2024
2 parents efee23b + b1c3be0 commit 3ef43fc
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 7 deletions.
65 changes: 61 additions & 4 deletions src/apps/chat/components/persona-selector/PersonaSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import { shallow } from 'zustand/shallow';
import { v4 as uuidv4 } from 'uuid';

import type { SxProps } from '@mui/joy/styles/types';
import { Alert, Avatar, Box, Button, Card, CardContent, Checkbox, IconButton, Input, List, ListItem, ListItemButton, Textarea, Tooltip, Typography } from '@mui/joy';
Expand All @@ -10,17 +11,19 @@ import EditNoteIcon from '@mui/icons-material/EditNote';
import SearchIcon from '@mui/icons-material/Search';
import TelegramIcon from '@mui/icons-material/Telegram';

import { SystemPurposeData, SystemPurposeId, SystemPurposes } from '../../../../data';

import { bareBonesPromptMixer } from '~/modules/persona/pmix/pmix';
import { useChatLLM } from '~/modules/llms/store-llms';

import { DConversationId, useChatStore } from '~/common/state/store-chats';
import { DConversationId, DMessage, useChatStore } from '~/common/state/store-chats';
import { ExpanderControlledBox } from '~/common/components/ExpanderControlledBox';
import { lineHeightTextareaMd } from '~/common/app.theme';
import { navigateToPersonas } from '~/common/app.routes';
import { useChipBoolean } from '~/common/components/useChipBoolean';
import { useUIPreferencesStore } from '~/common/state/store-ui';

import { SystemPurposeData, SystemPurposeId, SystemPurposes } from '../../../../data';
import { YouTubeURLInput } from './YouTubeURLInput';
import { usePurposeStore } from './store-purposes';


Expand Down Expand Up @@ -116,6 +119,8 @@ export function PersonaSelector(props: { conversationId: DConversationId, runExa
const [searchQuery, setSearchQuery] = React.useState('');
const [filteredIDs, setFilteredIDs] = React.useState<SystemPurposeId[] | null>(null);
const [editMode, setEditMode] = React.useState(false);
const [isYouTubeTranscriberActive, setIsYouTubeTranscriberActive] = React.useState(false);


// external state
const showFinder = useUIPreferencesStore(state => state.showPersonaFinder);
Expand Down Expand Up @@ -153,11 +158,52 @@ export function PersonaSelector(props: { conversationId: DConversationId, runExa

// Handlers

// Modify the handlePurposeChanged function to check for the YouTube Transcriber
const handlePurposeChanged = React.useCallback((purposeId: SystemPurposeId | null) => {
if (purposeId && setSystemPurposeId)
setSystemPurposeId(props.conversationId, purposeId);
if (purposeId) {
if (purposeId === 'YouTubeTranscriber') {
// If the YouTube Transcriber tile is clicked, set the state accordingly
setIsYouTubeTranscriberActive(true);
} else {
setIsYouTubeTranscriberActive(false);
}
if (setSystemPurposeId) {
setSystemPurposeId(props.conversationId, purposeId);
}
}
}, [props.conversationId, setSystemPurposeId]);

React.useEffect(() => {
const isTranscriberActive = systemPurposeId === 'YouTubeTranscriber';
setIsYouTubeTranscriberActive(isTranscriberActive);
}, [systemPurposeId]);


// Implement handleAddMessage function
const handleAddMessage = (messageText: string) => {
// Retrieve the appendMessage action from the useChatStore
const { appendMessage } = useChatStore.getState();

const conversationId = props.conversationId;

// Create a new message object
const newMessage: DMessage = {
id: uuidv4(),
text: messageText,
sender: 'Bot',
avatar: null,
typing: false,
role: 'assistant' as 'assistant',
tokenCount: 0,
created: Date.now(),
updated: null,
};

// Append the new message to the conversation
appendMessage(conversationId, newMessage);
};


const handleCustomSystemMessageChange = React.useCallback((v: React.ChangeEvent<HTMLTextAreaElement>): void => {
// TODO: persist this change? Right now it's reset every time.
// maybe we shall have a "save" button just save on a state to persist between sessions
Expand Down Expand Up @@ -418,6 +464,17 @@ export function PersonaSelector(props: { conversationId: DConversationId, runExa
/>
)}

{/* [row -1] YouTube URL */}
{isYouTubeTranscriberActive && (
<YouTubeURLInput
onSubmit={(url) => handleAddMessage(url)}
isFetching={false}
sx={{
gridColumn: '1 / -1',
}}
/>
)}

</Box>

</Box>
Expand Down
74 changes: 74 additions & 0 deletions src/apps/chat/components/persona-selector/YouTubeURLInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react';

import { Box, Button, Input } from '@mui/joy';
import YouTubeIcon from '@mui/icons-material/YouTube';

import type { SxProps } from '@mui/joy/styles/types';
import { useYouTubeTranscript, YTVideoTranscript } from '~/modules/youtube/useYouTubeTranscript';


interface YouTubeURLInputProps {
onSubmit: (transcript: string) => void;
isFetching: boolean;
sx?: SxProps;
}

export const YouTubeURLInput: React.FC<YouTubeURLInputProps> = ({ onSubmit, isFetching, sx }) => {
const [url, setUrl] = React.useState('');
const [submitFlag, setSubmitFlag] = React.useState(false);

// Function to extract video ID from URL
function extractVideoID(videoURL: string): string | null {
const regExp = /^(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([^#&?]*).*/;
const match = videoURL.match(regExp);
return (match && match[1]?.length == 11) ? match[1] : null;
}

const videoID = extractVideoID(url);

// Callback function to handle new transcript
const handleNewTranscript = (newTranscript: YTVideoTranscript) => {
onSubmit(newTranscript.transcript); // Pass the transcript text to the onSubmit handler
setSubmitFlag(false); // Reset submit flag after handling
};

const { transcript, isFetching: isTranscriptFetching, isError, error } = useYouTubeTranscript(videoID && submitFlag ? videoID : null, handleNewTranscript);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setUrl(event.target.value);
};

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // Prevent form from causing a page reload
setSubmitFlag(true); // Set flag to indicate a submit action
};

return (
<Box sx={{ mb: 1, ...sx }}>
<form onSubmit={handleSubmit}>
<Input
required
type='url'
fullWidth
disabled={isFetching || isTranscriptFetching}
variant='outlined'
placeholder='Enter YouTube Video URL'
value={url}
onChange={handleChange}
startDecorator={<YouTubeIcon sx={{ color: '#f00' }} />}
sx={{ mb: 1.5, backgroundColor: 'background.popup' }}
/>
<Button
type='submit'
variant='solid'
disabled={isFetching || isTranscriptFetching || !url}
loading={isFetching || isTranscriptFetching}
sx={{ minWidth: 140 }}
>
Get Transcript
</Button>
{isError && <div>Error fetching transcript. Please try again.</div>}
</form>
</Box>
);
};
9 changes: 7 additions & 2 deletions src/apps/chat/components/persona-selector/store-purposes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const usePurposeStore = create<PurposeStore>()(
(set) => ({

// default state
hiddenPurposeIDs: ['Developer', 'Designer'],
hiddenPurposeIDs: ['Developer', 'Designer', 'YouTubeTranscriber'],

toggleHiddenPurposeId: (purposeId: string) => {
set(state => {
Expand All @@ -37,14 +37,19 @@ export const usePurposeStore = create<PurposeStore>()(

/* versioning:
* 1: hide 'Developer' as 'DeveloperPreview' is best
* 2: add a hidden 'YouTubeTranscriber' purpose
*/
version: 1,
version: 2,

migrate: (state: any, fromVersion: number): PurposeStore => {
// 0 -> 1: rename 'enterToSend' to 'enterIsNewline' (flip the meaning)
if (state && fromVersion === 0)
if (!state.hiddenPurposeIDs.includes('Developer'))
state.hiddenPurposeIDs.push('Developer');
// 1 -> 2: add a hidden 'YouTubeTranscriber' purpose
if (state && fromVersion === 1)
if (!state.hiddenPurposeIDs.includes('YouTubeTranscriber'))
state.hiddenPurposeIDs.push('YouTubeTranscriber');
return state;
},
}),
Expand Down
12 changes: 11 additions & 1 deletion src/data.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';

export type SystemPurposeId = 'Catalyst' | 'Custom' | 'Designer' | 'Developer' | 'DeveloperPreview' | 'Executive' | 'Generic' | 'Scientist';
export type SystemPurposeId = 'Catalyst' | 'Custom' | 'Designer' | 'Developer' | 'DeveloperPreview' | 'Executive' | 'Generic' | 'Scientist' | 'YouTubeTranscriber';

export const defaultSystemPurposeId: SystemPurposeId = 'Generic';

Expand Down Expand Up @@ -110,4 +110,14 @@ Current date: {{LocaleNow}}
call: { starters: ['What\'s the task?', 'What can I do?', 'Ready for your task.', 'Yes?'] },
voices: { elevenLabs: { voiceId: 'flq6f7yk4E4fJM5XTYuZ' } },
},
YouTubeTranscriber: {
title: 'YouTube Transcriber',
description: 'Enter a YouTube URL to get the transcript and chat about the content.',
systemMessage: 'You are an expert in understanding video transcripts and answering questions about video content.',
symbol: '📺',
examples: ['Analyze the sentiment of this video', 'Summarize the key points of the lecture'],
call: { starters: ['Enter a YouTube URL to begin.', 'Ready to transcribe YouTube content.', 'Paste the YouTube link here.'] },
voices: { elevenLabs: { voiceId: 'z9fAnlkpzviPz146aGWa' } },
},

};

0 comments on commit 3ef43fc

Please sign in to comment.