Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/f 57 render ai response in markdown #20

Merged
merged 19 commits into from
Nov 18, 2024

Conversation

ahmtslmngcl
Copy link
Collaborator

@ahmtslmngcl ahmtslmngcl commented Nov 10, 2024

  • fixed animation bug which skipped some letters randomly
  • added markdown support with 'react-markdown' and custom component design
  • supported markdown components:
    - headings (h1, h2, h3)
    - bold text
    - bullet points
    - ordered lists
    - blockquotes
    - url
    - strikethrough
    - tables
    - tasklists
  • security: added 'rehype-sanitize' for sanitizer. Will add some system prompting to back-end as well

Edit:

  • fixed re-animation when chatbot was reopened
  • added system prompts for markdown specification (see back-end repo)
  • added continuous typing animation when leaving chatbot and returning back to it

Copy link

netlify bot commented Nov 10, 2024

Deploy Preview for wfp-hungermap ready!

Name Link
🔨 Latest commit e7e6010
🔍 Latest deploy log https://app.netlify.com/sites/wfp-hungermap/deploys/673a527344677b0008a145da
😎 Deploy Preview https://deploy-preview-20--wfp-hungermap.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@ahmtslmngcl ahmtslmngcl force-pushed the feature/f-57-render-ai-response-in-markdown branch from b41ae3c to ce0ebea Compare November 13, 2024 01:27
Comment on lines 64 to 73
const startNewChat = (): void => {
const newChat: IChat = { id: chats.length + 1, title: `Chat ${chats.length + 1}`, messages: [] };
setChats([...chats, newChat]);
setCurrentChatIndex(chats.length);
if (isMobile) {
setIsSidebarOpen(false);
if (isResponseAnimated) {
const newChat: IChat = { id: chats.length + 1, title: `Chat ${chats.length + 1}`, messages: [], isTyping: false };
setChats([...chats, newChat]);
setCurrentChatIndex(chats.length);
if (isMobile) {
setIsSidebarOpen(false);
}
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting a new chat is not working

Copy link
Collaborator

@marinovl7 marinovl7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done generally. Code Refactoring is necessary, because at first look I didn't get the code and its structure, especially with all the complex typing functions, chats etc. Additionally the newChat button is not working. Please revisit and refactor the code a bi + fix the errors in functionality. Generally I think that the job is well done in terms of functionality, but its execution should also be good in terms of quality and maintainablity

Comment on lines 86 to 92
const setTypingStatus = async (chatIndex: number, isTyping: boolean): Promise<void> => {
return new Promise((resolve) => {
setChats((prevChats) => prevChats.map((chat, index) => (index === chatIndex ? { ...chat, isTyping } : chat)));
resolve();
});
};

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why this is a promise?

@@ -115,26 +125,33 @@ export default function HungerMapChatbot() {
* @param promptText is requested text from user
*/
const handleSubmit = (fromEvent: React.FormEvent, promptText: string | null = null): void => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is fromEvent => precise naming

Comment on lines 130 to 138
fromEvent.preventDefault();
const text = promptText || input;
chats[currentChatIndex].isTyping = true;
if (text.trim()) {
const updatedChats = structuredClone(chats);
updatedChats[currentChatIndex].messages.push({ id: crypto.randomUUID(), content: text, role: SenderRole.USER });
if (updatedChats[currentChatIndex].title === `Chat ${updatedChats[currentChatIndex].id}`) {
updatedChats[currentChatIndex].title = text.slice(0, 30) + (text.length > 30 ? '...' : '');
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire thing has nothing to do with the component => should go into an operations file

Comment on lines 159 to 174
// use to scroll to the end of the chat when new messages are added
useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chats, currentChatIndex]);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should work as well. So when the message exceedes the chatbox window we should scroll

Comment on lines 310 to 312
onTypingComplete={() => {
handleTypingComplete();
}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

() => handleTypingComplete no {}

Comment on lines 12 to 84
const markdownComponents: Components = {
h1: ({ children, ...props }) => (
<h1 className="text-xl sm:text-2xl md:text-3xl font-bold mb-2" {...props}>
{children}
</h1>
),

h2: ({ children, ...props }) => (
<h2 className="text-lg sm:text-xl md:text-2xl font-bold mb-2 mt-0" {...props}>
{children}
</h2>
),

h3: ({ children, ...props }) => (
<h3 className="text-base sm:text-lg md:text-xl font-bold mb-1" {...props}>
{children}
</h3>
),

p: ({ children, ...props }) => (
<p className="text-sm sm:text-base my-2 leading-relaxed" {...props}>
{children}
</p>
),

ul: ({ children, ...props }) => (
<ul className="list-disc pl-2 sm:pl-4 my-2 space-y-1 text-sm sm:text-base" {...props}>
{children}
</ul>
),

ol: ({ children, ...props }) => (
<ol className="list-decimal pl-2 sm:pl-4 my-2 space-y-1 text-sm sm:text-base" {...props}>
{children}
</ol>
),

li: ({ children, ...props }) => (
<li className="ml-2" {...props}>
{children}
</li>
),

blockquote: ({ children, ...props }) => (
<blockquote
className="border-l-4 border-gray-300 dark:border-gray-700 pl-2 sm:pl-3 my-2 italic text-sm sm:text-base"
{...props}
>
{children}
</blockquote>
),

// may be removed later since code rendering not supported
code: ({ children, ...props }) => (
<code className="bg-gray-100 dark:bg-gray-800 rounded px-1 py-0.5 font-mono text-xs sm:text-sm" {...props}>
{children}
</code>
),

a: ({ children, href, ...props }) => (
<a
href={href}
className="text-brand hover:text-brandHover underline text-sm sm:text-base"
target="_blank"
rel="noopener noreferrer"
{...props}
>
{children}
</a>
),

hr: (props) => <hr className="my-4 border-gray-300 dark:border-gray-700" {...props} />,
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract this into a separate file

* sets isTyping to false so that dot animation
* gets cancelled
*/
const handleTypingStart = async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type of the function

/**
* Handles typing animation completion
*/
const handleTypingComplete = () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function return type

/**
* Restores typing progress from localStorage
*/
const restoreProgress = () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type of the function + this function could be extracted into operations file

Comment on lines 183 to 216
const animateText = () => {
if (currentIndex < normalizedText.length) {
const delay = calculateDelay(currentIndex);
setDisplayedText(normalizedText.slice(0, currentIndex + 1));

// Save progress
const progress: TypingProgress = {
index: currentIndex + 1,
timestamp: Date.now(),
};
localStorage.setItem(progressKey, JSON.stringify(progress));

currentIndex += 1;
setTimeout(animateText, delay);
} else {
handleTypingComplete();
}
};

handleTypingStart();

// Check if animation is already completed
if (localStorage.getItem(completionKey) === 'true') {
setDisplayedText(normalizedText);
} else {
// Restore progress and continue animation
currentIndex = restoreProgress();
setDisplayedText(normalizedText.slice(0, currentIndex));

if (currentIndex < normalizedText.length) {
animateText();
} else {
handleTypingComplete();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid having such long functions. This should be split into simple, easy to understand functions

function orderPizza(): void {
unlockPhone()
chooseResaurant()
chooseMeal()
orderMeal()
payMeal()
closePhone()
}

And not

function orderPizza(): void {
phone.password = 'PizzePizza' 
phone.faceUnlock = true
phone.unlocking = true
phone.unlocked = false
try {
phone.forceUnlock
}
etcetctc.....
}

Copy link
Collaborator

@marinovl7 marinovl7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done generally. Code Refactoring is necessary, because at first look I didn't get the code and its structure, especially with all the complex typing functions, chats etc. Additionally the newChat button is not working. Please revisit and refactor the code a bi + fix the errors in functionality. Generally I think that the job is well done in terms of functionality, but its execution should also be good in terms of quality and maintainablity

Copy link
Collaborator

@marinovl7 marinovl7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I see the improvement in code quality already and there is no single place where I can say something. I'm happy that you are learning in the most perfect way possible. Awesome job Ahmet!

@marinovl7 marinovl7 merged commit f0af01b into main Nov 18, 2024
5 checks passed
@marinovl7 marinovl7 deleted the feature/f-57-render-ai-response-in-markdown branch November 18, 2024 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants