From c0f9ab91df33e2cc9eec272463bde882417807a2 Mon Sep 17 00:00:00 2001 From: Chukwuma Nwaugha Date: Thu, 5 Dec 2024 08:40:29 +0000 Subject: [PATCH 1/8] fix bug in render_category_selection --- app/src/lib/components/RenderCategorySelection.svelte | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/lib/components/RenderCategorySelection.svelte b/app/src/lib/components/RenderCategorySelection.svelte index b76ed28..47b9c25 100644 --- a/app/src/lib/components/RenderCategorySelection.svelte +++ b/app/src/lib/components/RenderCategorySelection.svelte @@ -5,9 +5,11 @@
- +
+ - + +
From 5fb138e7284e0d27bdd4552e8a6db3ba985868c3 Mon Sep 17 00:00:00 2001 From: Chukwuma Nwaugha Date: Thu, 5 Dec 2024 08:51:59 +0000 Subject: [PATCH 2/8] build the ui workflow to automatically detect content category --- .../components/RenderCategorySelection.svelte | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/app/src/lib/components/RenderCategorySelection.svelte b/app/src/lib/components/RenderCategorySelection.svelte index 47b9c25..475da14 100644 --- a/app/src/lib/components/RenderCategorySelection.svelte +++ b/app/src/lib/components/RenderCategorySelection.svelte @@ -1,15 +1,54 @@ + +
- + {#if detectingCategory} + + {:else} + + {/if}
- + {#if !detectingCategory} + + {/if}
From 0c545b7addd943d8bd7467e888746334dc6a728b Mon Sep 17 00:00:00 2001 From: Chukwuma Nwaugha Date: Thu, 5 Dec 2024 09:05:21 +0000 Subject: [PATCH 3/8] allow overloading loading state in chat_item --- app/src/lib/components/ChatListItem.svelte | 6 +++++- app/src/lib/components/RenderCategorySelection.svelte | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/lib/components/ChatListItem.svelte b/app/src/lib/components/ChatListItem.svelte index 5e416f8..fa5a426 100644 --- a/app/src/lib/components/ChatListItem.svelte +++ b/app/src/lib/components/ChatListItem.svelte @@ -29,10 +29,14 @@
{#if loading} - Generating response... + + Generating response... + {:else} {#await parse(content) then parsedContent} {@html parsedContent} + {:catch error} +

{String(error)}

{/await} {/if}
diff --git a/app/src/lib/components/RenderCategorySelection.svelte b/app/src/lib/components/RenderCategorySelection.svelte index 475da14..9a76329 100644 --- a/app/src/lib/components/RenderCategorySelection.svelte +++ b/app/src/lib/components/RenderCategorySelection.svelte @@ -42,7 +42,11 @@ {#if detectingCategory} - + + + Auto-detecting content category...Please wait + + {:else} {/if} From c9403315c735cbab294cf3f87a322a2b4f79a466 Mon Sep 17 00:00:00 2001 From: Chukwuma Nwaugha Date: Thu, 5 Dec 2024 09:28:16 +0000 Subject: [PATCH 4/8] write the logic and endpoint to detect content category --- api/src/main.py | 10 ++++ api/src/utils/detect_content_category.py | 68 ++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 api/src/utils/detect_content_category.py diff --git a/api/src/main.py b/api/src/main.py index 0409301..9336d64 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -10,6 +10,7 @@ from .services.storage import StorageManager from .utils.chat_request import chat_request from .utils.chat_utils import ( + ContentCategory, SessionChatItem, SessionChatRequest, ) @@ -25,6 +26,7 @@ ) from .utils.custom_sources.save_copied_source import CopiedPasteSourceRequest, save_copied_source from .utils.custom_sources.save_uploaded_sources import UploadedFiles +from .utils.detect_content_category import DetectContentCategoryRequest, detect_content_category from .utils.generate_audiocast import GenerateAudioCastRequest, GenerateAudiocastException, generate_audiocast from .utils.generate_audiocast_source import GenerateAudiocastSource, generate_audiocast_source from .utils.get_audiocast import get_audiocast @@ -206,3 +208,11 @@ def delete_session_endpoint(sessionId: str): """ SessionManager._delete_session(sessionId) return "Deleted" + + +@app.post("/detect-category", response_model=ContentCategory) +async def detect_category_endpoint(request: DetectContentCategoryRequest): + """ + Detect category of a given content + """ + return await detect_content_category(request.content) diff --git a/api/src/utils/detect_content_category.py b/api/src/utils/detect_content_category.py new file mode 100644 index 0000000..56dfe56 --- /dev/null +++ b/api/src/utils/detect_content_category.py @@ -0,0 +1,68 @@ +from pydantic import BaseModel + +from src.services.gemini_client import get_gemini +from src.utils.chat_utils import ContentCategory, content_categories + + +class DetectContentCategoryRequest(BaseModel): + content: str + + +def detect_category_prompt(content: str) -> str: + """ + System Prompt to detect the category of a given content + """ + return f"""You are an intelligent content type classifier. Your task is to analyze the given content and categorize it into one of the following types: + CONTENT: "{content}" + + CATEGORIES: {', '.join(content_categories)} + + + IMPORTANT NOTE: + - You must ONLY output one of these exact category names, with no additional text, explanation, preamble or formatting. + - If the content doesn't fit any of the categories, you should output "other". + + Examples: + Input: "Welcome to today's episode where we'll be discussing the fascinating world of quantum computing..." + Output: podcast + + Input: "And now, dear brothers and sisters, let us reflect on the profound message in today's scripture..." + Output: sermon + + Input: "Let's dive into today's lecture on advanced machine learning algorithms..." + Output: lecture +""" + + +def validate_category_output(output: str) -> ContentCategory: + """ + Validate that the AI output is a valid content category. + Throws ValueError if the output is not a valid category. + """ + cleaned_output = output.strip().lower() + if cleaned_output not in content_categories: + raise ValueError(f"Invalid category '{cleaned_output}'. Must be one of: {', '.join(content_categories)}") + return cleaned_output + + +async def detect_content_category(content: str) -> ContentCategory: + """ + Detect the category of the given content using Gemini Flash. + """ + client = get_gemini() + + model = client.GenerativeModel( + model_name="gemini-1.5-flash-002", + system_instruction=detect_category_prompt(content), + generation_config=client.GenerationConfig( + temperature=0.1, + max_output_tokens=30, + response_mime_type="text/plain", + ), + ) + + response = model.generate_content(["Now, please categorize the content."]) + if not response.text: + raise Exception("Error obtaining response from Gemini Flash") + + return validate_category_output(response.text) From 0b23436b871b9d21a5153196bedf236e8f80756e Mon Sep 17 00:00:00 2001 From: Chukwuma Nwaugha Date: Thu, 5 Dec 2024 09:56:42 +0000 Subject: [PATCH 5/8] add improved workflow for selecting auto-detected contentcategory --- .../components/RenderCategorySelection.svelte | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/app/src/lib/components/RenderCategorySelection.svelte b/app/src/lib/components/RenderCategorySelection.svelte index 9a76329..f5bb8e7 100644 --- a/app/src/lib/components/RenderCategorySelection.svelte +++ b/app/src/lib/components/RenderCategorySelection.svelte @@ -1,15 +1,31 @@ + + @@ -47,6 +60,26 @@ Auto-detecting content category...Please wait + {:else if detectedCategory} + {@const categoryWithArticle = `"${categoryArticleMap[detectedCategory]}"`} + {@const _detectedCategory = detectedCategory} +

+ +

+ + + + {:else} {/if} From 5f2db7acfa5ea1d101f534b67c0566869e940336 Mon Sep 17 00:00:00 2001 From: Chukwuma Nwaugha Date: Thu, 5 Dec 2024 10:09:14 +0000 Subject: [PATCH 6/8] move ui logic for auto_detected_category to own file --- .../components/AutoDetectedCategory.svelte | 43 +++++++++++++++++++ .../components/RenderCategorySelection.svelte | 41 ++---------------- 2 files changed, 46 insertions(+), 38 deletions(-) create mode 100644 app/src/lib/components/AutoDetectedCategory.svelte diff --git a/app/src/lib/components/AutoDetectedCategory.svelte b/app/src/lib/components/AutoDetectedCategory.svelte new file mode 100644 index 0000000..fdd899a --- /dev/null +++ b/app/src/lib/components/AutoDetectedCategory.svelte @@ -0,0 +1,43 @@ + + + + + + + + + diff --git a/app/src/lib/components/RenderCategorySelection.svelte b/app/src/lib/components/RenderCategorySelection.svelte index f5bb8e7..632c768 100644 --- a/app/src/lib/components/RenderCategorySelection.svelte +++ b/app/src/lib/components/RenderCategorySelection.svelte @@ -1,29 +1,12 @@ - - +
- {#if loading} + {#if likelyErrored} + + Failed to generate response. + Likely errored + + {:else if loading} Generating response... @@ -41,3 +54,13 @@ {/if}
+ +{#if likelyErrored} + +{/if} diff --git a/app/src/lib/components/ExampleCard.svelte b/app/src/lib/components/ExampleCard.svelte index cbd7581..74063b1 100644 --- a/app/src/lib/components/ExampleCard.svelte +++ b/app/src/lib/components/ExampleCard.svelte @@ -12,7 +12,7 @@ async function handleClick() { startSession(category); - addChatItem({ id: uuid(), content, role: 'user', loading: false }); + addChatItem({ id: uuid(), content, role: 'user', loading: false, createdAt: Date.now() }); return goto(href, { invalidateAll: true, replaceState: true }); } diff --git a/app/src/lib/stores/sessionContext.svelte.ts b/app/src/lib/stores/sessionContext.svelte.ts index 3f37bed..e5c3764 100644 --- a/app/src/lib/stores/sessionContext.svelte.ts +++ b/app/src/lib/stores/sessionContext.svelte.ts @@ -15,6 +15,7 @@ export type ChatItem = { content: string; role: 'user' | 'assistant'; loading?: boolean; + createdAt?: number; }; export type Session = { diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index 201dd39..a872fc4 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -25,7 +25,7 @@ startSession(category); const content = `${selectContent}\nCategory: ${category} `; - addChatItem({ id: uuid(), content, role: 'user', loading: false }); + addChatItem({ id: uuid(), content, role: 'user', loading: false, createdAt: Date.now() }); const href = `/chat/${sessionId}?category=${category}&chat`; return goto(href, { invalidateAll: true, replaceState: true }); diff --git a/app/src/routes/chat/[sessionId=sessionId]/+page.svelte b/app/src/routes/chat/[sessionId=sessionId]/+page.svelte index 77fbf3b..1cc2d67 100644 --- a/app/src/routes/chat/[sessionId=sessionId]/+page.svelte +++ b/app/src/routes/chat/[sessionId=sessionId]/+page.svelte @@ -48,7 +48,8 @@ id: uuid(), content: searchTerm, role: 'user', - loading: false + loading: false, + createdAt: Date.now() }; addChatItem(chatItem); searchTerm = ''; @@ -57,7 +58,13 @@ } async function chatRequest(uItem: ChatItem) { - const aItem = addChatItem({ id: uuid(), content: '', role: 'assistant', loading: true }); + const aItem = addChatItem({ + id: uuid(), + content: '', + role: 'assistant', + loading: true, + createdAt: Date.now() + }); return fetch(`${env.API_BASE_URL}/chat/${sessionId}`, { method: 'POST', @@ -92,7 +99,12 @@
{#each sessionChats as item (item.id)} {@const finalResponse = isfinalResponse(item)} - + {#if finalResponse} Date: Thu, 5 Dec 2024 11:03:41 +0000 Subject: [PATCH 8/8] allow user to regenerate errored ai responses --- app/src/lib/components/ChatListItem.svelte | 4 +++ app/src/lib/stores/sessionContext.svelte.ts | 11 ++++++ .../chat/[sessionId=sessionId]/+page.svelte | 36 ++++++++++++++----- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/app/src/lib/components/ChatListItem.svelte b/app/src/lib/components/ChatListItem.svelte index deccac4..bc5a3e6 100644 --- a/app/src/lib/components/ChatListItem.svelte +++ b/app/src/lib/components/ChatListItem.svelte @@ -7,12 +7,15 @@ import cs from 'clsx'; import { parse } from 'marked'; import { Button } from './ui/button'; + import { createEventDispatcher } from 'svelte'; export let type: 'user' | 'assistant'; export let content: string; export let loading = false; export let createdAt: number | undefined = undefined; + const dispatch = createEventDispatcher<{ regenerate: void }>(); + $: likelyErrored = loading && (!createdAt || Date.now() - createdAt > TWO_MINUTES_MS); @@ -59,6 +62,7 @@