Skip to content

Commit

Permalink
Add toasts
Browse files Browse the repository at this point in the history
  • Loading branch information
UdaraJay committed Nov 11, 2023
1 parent 60ff82f commit 44af516
Show file tree
Hide file tree
Showing 14 changed files with 462 additions and 111 deletions.
109 changes: 56 additions & 53 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { TimelineContextProvider } from './context/TimelineContext';
import { AIContextProvider } from './context/AIContext';
import { HighlightsContextProvider } from './context/HighlightsContext';
import { LinksContextProvider } from './context/LinksContext';
import { ToastsContextProvider } from './context/ToastsContext';

if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
Expand Down Expand Up @@ -49,64 +50,66 @@ export default function App() {

return (
<PilesContextProvider>
<AIContextProvider>
<IndexContextProvider>
<HighlightsContextProvider>
<TagsContextProvider>
<TimelineContextProvider>
<LinksContextProvider>
<AnimatePresence mode="wait">
<Routes location={location} key={location.pathname}>
<Route
path="/"
element={
<AnimatedPage _key="home">
<Home />
</AnimatedPage>
}
/>
<Route
path="/license"
element={
<AnimatedPage _key="license">
<License />
</AnimatedPage>
}
/>
<Route
path="/credits"
element={
<AnimatedPage _key="credits">
<Credits />
</AnimatedPage>
}
/>
<Route
path="/new-pile"
element={
<AnimatedPage _key="new-pile">
<CreatePile />
</AnimatedPage>
}
/>
<Route path="/pile">
<ToastsContextProvider>
<AIContextProvider>
<IndexContextProvider>
<HighlightsContextProvider>
<TagsContextProvider>
<TimelineContextProvider>
<LinksContextProvider>
<AnimatePresence mode="wait">
<Routes location={location} key={location.pathname}>
<Route
path=":pileName"
path="/"
element={
<AnimatedPage down _key="pile">
<Pile />
<AnimatedPage _key="home">
<Home />
</AnimatedPage>
}
/>
</Route>
</Routes>
</AnimatePresence>
</LinksContextProvider>
</TimelineContextProvider>
</TagsContextProvider>
</HighlightsContextProvider>
</IndexContextProvider>
</AIContextProvider>
<Route
path="/license"
element={
<AnimatedPage _key="license">
<License />
</AnimatedPage>
}
/>
<Route
path="/credits"
element={
<AnimatedPage _key="credits">
<Credits />
</AnimatedPage>
}
/>
<Route
path="/new-pile"
element={
<AnimatedPage _key="new-pile">
<CreatePile />
</AnimatedPage>
}
/>
<Route path="/pile">
<Route
path=":pileName"
element={
<AnimatedPage down _key="pile">
<Pile />
</AnimatedPage>
}
/>
</Route>
</Routes>
</AnimatePresence>
</LinksContextProvider>
</TimelineContextProvider>
</TagsContextProvider>
</HighlightsContextProvider>
</IndexContextProvider>
</AIContextProvider>
</ToastsContextProvider>
</PilesContextProvider>
);
}
17 changes: 2 additions & 15 deletions src/renderer/context/AIContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,11 @@ import {
} from 'react';
import OpenAI from 'openai';

const processKey = (k) => {
if (k.startsWith('unms-')) {
k = k.substring(5);
const reversedStr = k.split('').reverse().join('');
return 'sk-' + reversedStr;
}

return k;
};

export const AIContext = createContext();

export const AIContextProvider = ({ children }) => {
const [ai, setAi] = useState(null);

// this keeps track of async tasks that the user is notified about
const [pendingJobs, setPendingJobs] = useState([]);

const prompt =
'You are an AI within a journaling app. Your job is to help the user reflect on their thoughts in a thoughtful and kind manner. The user can never directly address you or directly respond to you. Try not to repeat what the user said, instead try to seed new ideas, encourage or debate. Keep your responses concise, but meaningful.';

Expand All @@ -37,10 +24,10 @@ export const AIContextProvider = ({ children }) => {

if (!key) return;

const processedKey = processKey(key);
const openaiInstance = new OpenAI({
apiKey: processedKey,
apiKey: key,
});

setAi(openaiInstance);
};

Expand Down
7 changes: 7 additions & 0 deletions src/renderer/context/LinksContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import {
import { useLocation } from 'react-router-dom';
import { usePilesContext } from './PilesContext';
import { useAIContext } from './AIContext';
import { useToastsContext } from './ToastsContext';

export const LinksContext = createContext();

export const LinksContextProvider = ({ children }) => {
const { currentPile, getCurrentPilePath } = usePilesContext();
const { ai, getCompletion } = useAIContext();
const { addNotification, updateNotification, removeNotification } =
useToastsContext();

const getLink = useCallback(
async (url) => {
Expand All @@ -23,11 +27,14 @@ export const LinksContextProvider = ({ children }) => {
url
);

addNotification(url, 'thinking', 'Generating preview...');

// return cached preview if available
if (preview) {
return preview;
}

updateNotification(url, 'waiting', 'Generating preview...');
// otherwise generate a new preview
const _preview = await getPreview(url);
const aiCard = await generateMeta(url).catch(() => {
Expand Down
85 changes: 85 additions & 0 deletions src/renderer/context/ToastsContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
useState,
createContext,
useContext,
useEffect,
useCallback,
useRef,
} from 'react';

export const ToastsContext = createContext();

export const ToastsContextProvider = ({ children }) => {
// this keeps track of async tasks that the user is notified about
// by the AI. This can include general app notifications as well.
const [notifications, setNotifications] = useState([]);
const notificationTimeoutsRef = useRef({});

useEffect(() => {
return () => {
Object.values(notificationTimeoutsRef.current).forEach(clearTimeout);
};
}, []);

const addNotification = (
targetId,
type = 'info',
message,
autoDismiss = true
) => {
const newNotification = { id: targetId, type, message, autoDismiss };

setNotifications((currentNotifications) => [
...currentNotifications,
newNotification,
]);

if (autoDismiss) {
if (notificationTimeoutsRef.current[targetId]) {
clearTimeout(notificationTimeoutsRef.current[targetId]);
}

notificationTimeoutsRef.current[targetId] = setTimeout(() => {
removeNotification(targetId);
delete notificationTimeoutsRef.current[targetId];
}, 30000);
}
};

const updateNotification = (targetId, newType, newMessage) => {
setNotifications((currentNotifications) =>
currentNotifications.map((notification) =>
notification.id === targetId
? { ...notification, type: newType, message: newMessage }
: notification
)
);
};

const removeNotification = (targetId) => {
setNotifications((currentNotifications) =>
currentNotifications.filter(
(notification) => notification.id !== targetId
)
);
if (notificationTimeoutsRef.current[targetId]) {
clearTimeout(notificationTimeoutsRef.current[targetId]);
delete notificationTimeoutsRef.current[targetId];
}
};

const ToastsContextValue = {
notifications,
addNotification,
updateNotification,
removeNotification,
};

return (
<ToastsContext.Provider value={ToastsContextValue}>
{children}
</ToastsContext.Provider>
);
};

export const useToastsContext = () => useContext(ToastsContext);
56 changes: 26 additions & 30 deletions src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreview/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,36 +132,32 @@ export default function LinkPreview({ url }) {
};

return (
<AnimatePresence>
<motion.div
initial={{
opacity: 0,
transform: 'scale(0.9)',
transformOrigin: 'top left',
}}
animate={{ opacity: 1, transform: 'scale(1)' }}
exit={{ opacity: 0, transform: 'scale(0.9)' }}
transition={{ delay: 0.3 }}
>
<div className={styles.card} onClick={toggleExpand}>
{renderImage()}
<div className={styles.content}>
<a href={url} target="_blank" className={styles.title}>
{preview.title}
</a>
</div>
{renderAICard()}
<div className={styles.footer}>
<img className={styles.favicon} src={preview.favicon} />{' '}
{preview?.aiCard?.category && (
<span className={styles.category}>
{preview?.aiCard?.category}
</span>
)}
{preview?.host}
</div>
<motion.div
initial={{
opacity: 0,
transform: 'scale(0.9)',
transformOrigin: 'top left',
}}
animate={{ opacity: 1, transform: 'scale(1)' }}
exit={{ opacity: 0, transform: 'scale(0.9)' }}
transition={{ delay: 0.3 }}
>
<div className={styles.card} onClick={toggleExpand}>
{renderImage()}
<div className={styles.content}>
<a href={url} target="_blank" className={styles.title}>
{preview.title}
</a>
</div>
</motion.div>
</AnimatePresence>
{renderAICard()}
<div className={styles.footer}>
<img className={styles.favicon} src={preview.favicon} />{' '}
{preview?.aiCard?.category && (
<span className={styles.category}>{preview?.aiCard?.category}</span>
)}
{preview?.host}
</div>
</div>
</motion.div>
);
}
4 changes: 2 additions & 2 deletions src/renderer/pages/Pile/Editor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { Extension } from '@tiptap/core';
import { useEditor, EditorContent } from '@tiptap/react';
import Link from '@tiptap/extension-link';
import StarterKit from '@tiptap/starter-kit';
import CharacterCount from '@tiptap/extension-character-count';
import Typography from '@tiptap/extension-typography';
import Placeholder from '@tiptap/extension-placeholder';
import CharacterCount from '@tiptap/extension-character-count';
import { DiscIcon, PhotoIcon, TrashIcon, TagIcon } from 'renderer/icons';
import { motion, AnimatePresence } from 'framer-motion';
import { postFormat } from 'renderer/utils/fileOperations';
Expand Down Expand Up @@ -55,7 +55,7 @@ export default function Editor({
placeholder: isAI ? 'AI is thinking...' : 'What are you thinking?',
}),
CharacterCount.configure({
limit: 100000,
limit: 10000,
}),
],
autofocus: true,
Expand Down
Loading

0 comments on commit 44af516

Please sign in to comment.