From 4e828b0669706c4bd3e18927d52edfc4e552838e Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Wed, 17 Jan 2024 21:01:30 -0800 Subject: [PATCH 01/10] feat: convo tagging frontend --- src/lib/components/chat/ShareChatModal.svelte | 38 ++++++++ src/lib/components/common/Modal.svelte | 4 +- src/lib/components/layout/Navbar.svelte | 88 ++++++++++--------- 3 files changed, 87 insertions(+), 43 deletions(-) create mode 100644 src/lib/components/chat/ShareChatModal.svelte diff --git a/src/lib/components/chat/ShareChatModal.svelte b/src/lib/components/chat/ShareChatModal.svelte new file mode 100644 index 000000000..f834c421d --- /dev/null +++ b/src/lib/components/chat/ShareChatModal.svelte @@ -0,0 +1,38 @@ + + + +
+ + +
+
or
+ + +
+
+
diff --git a/src/lib/components/common/Modal.svelte b/src/lib/components/common/Modal.svelte index 0fb3f8bd1..b292b910f 100644 --- a/src/lib/components/common/Modal.svelte +++ b/src/lib/components/common/Modal.svelte @@ -8,7 +8,9 @@ let mounted = false; const sizeToWidth = (size) => { - if (size === 'sm') { + if (size === 'xs') { + return 'w-[16rem]'; + } else if (size === 'sm') { return 'w-[30rem]'; } else { return 'w-[40rem]'; diff --git a/src/lib/components/layout/Navbar.svelte b/src/lib/components/layout/Navbar.svelte index bd52bd9b5..e75dafd67 100644 --- a/src/lib/components/layout/Navbar.svelte +++ b/src/lib/components/layout/Navbar.svelte @@ -5,11 +5,14 @@ import { getChatById } from '$lib/apis/chats'; import { chatId, modelfiles } from '$lib/stores'; + import ShareChatModal from '../chat/ShareChatModal.svelte'; export let initNewChat: Function; export let title: string = 'Ollama Web UI'; export let shareEnabled: boolean = false; + let showShareChatModal = false; + const shareChat = async () => { const chat = (await getChatById(localStorage.token, $chatId)).chat; console.log('share', chat); @@ -53,16 +56,17 @@ }; + From 93ec04003a05b146161de0c99ee6f376e918e2e0 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Wed, 17 Jan 2024 21:03:53 -0800 Subject: [PATCH 02/10] fix: share modal styling --- src/lib/components/chat/ShareChatModal.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/ShareChatModal.svelte b/src/lib/components/chat/ShareChatModal.svelte index f834c421d..3e9e30c42 100644 --- a/src/lib/components/chat/ShareChatModal.svelte +++ b/src/lib/components/chat/ShareChatModal.svelte @@ -21,10 +21,10 @@
-
or
+
or
From 287668f84ea295ace9517960029c048b1a98d5a4 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Wed, 17 Jan 2024 22:49:58 -0800 Subject: [PATCH 04/10] feat: tag add/remove frontend --- src/lib/components/layout/Navbar.svelte | 127 +++++++++++++++++++++--- 1 file changed, 112 insertions(+), 15 deletions(-) diff --git a/src/lib/components/layout/Navbar.svelte b/src/lib/components/layout/Navbar.svelte index e75dafd67..521e7abcc 100644 --- a/src/lib/components/layout/Navbar.svelte +++ b/src/lib/components/layout/Navbar.svelte @@ -6,6 +6,7 @@ import { getChatById } from '$lib/apis/chats'; import { chatId, modelfiles } from '$lib/stores'; import ShareChatModal from '../chat/ShareChatModal.svelte'; + import { stringify } from 'postcss'; export let initNewChat: Function; export let title: string = 'Ollama Web UI'; @@ -13,6 +14,24 @@ let showShareChatModal = false; + let tags = [ + // { + // name: 'general' + // }, + // { + // name: 'medicine' + // }, + // { + // name: 'cooking' + // }, + // { + // name: 'education' + // } + ]; + + let tagName = ''; + let showTagInput = false; + const shareChat = async () => { const chat = (await getChatById(localStorage.token, $chatId)).chat; console.log('share', chat); @@ -54,6 +73,20 @@ saveAs(blob, `chat-${chat.title}.txt`); }; + + const addTag = () => { + if (!tags.find((e) => e.name === tagName)) { + tags = [ + ...tags, + { + name: JSON.parse(JSON.stringify(tagName)) + } + ]; + } + + tagName = ''; + showTagInput = false; + }; @@ -93,23 +126,87 @@
-
-
-
Add Tags
-
- + {#each tags as tag} +
+
+ {tag.name} +
+
+ {/each} + +
+ {#if showTagInput} +
+ + + +
+ + + {/if} + +
From 077f1fa34bea06f61545feea4775264b7b6a7792 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 18 Jan 2024 00:58:45 -0800 Subject: [PATCH 05/10] feat: convo tagging backend support --- backend/apps/web/models/tags.py | 180 ++++++++++++++++++++++++++++++ backend/apps/web/routers/chats.py | 65 +++++++++++ 2 files changed, 245 insertions(+) create mode 100644 backend/apps/web/models/tags.py diff --git a/backend/apps/web/models/tags.py b/backend/apps/web/models/tags.py new file mode 100644 index 000000000..eb33b31b1 --- /dev/null +++ b/backend/apps/web/models/tags.py @@ -0,0 +1,180 @@ +from pydantic import BaseModel +from typing import List, Union, Optional +from peewee import * +from playhouse.shortcuts import model_to_dict + +import json +import uuid +import time + +from apps.web.internal.db import DB + +#################### +# Tag DB Schema +#################### + + +class Tag(Model): + name = CharField(unique=True) + user_id = CharField() + data = TextField(null=True) + + class Meta: + database = DB + + +class ChatIdTag(Model): + tag_name = ForeignKeyField(Tag, backref="chat_id_tags") + chat_id = CharField() + user_id = CharField() + timestamp = DateField() + + class Meta: + database = DB + + +class TagModel(BaseModel): + name: str + user_id: str + data: Optional[str] = None + + +class ChatIdTagModel(BaseModel): + tag_name: str + chat_id: str + user_id: str + timestamp: int + + +#################### +# Forms +#################### + + +class ChatIdTagForm(BaseModel): + tag_name: str + chat_id: str + + +class TagChatIdsResponse(BaseModel): + chat_ids: List[str] + + +class ChatTagsResponse(BaseModel): + tags: List[str] + + +class TagTable: + def __init__(self, db): + self.db = db + db.create_tables([Tag, ChatIdTag]) + + def insert_new_tag(self, name: str, user_id: str) -> Optional[TagModel]: + tag = TagModel(**{"user_id": user_id, "name": name}) + try: + result = Tag.create(**tag.model_dump()) + if result: + return tag + else: + return None + except: + return None + + def get_tag_by_name_and_user_id( + self, name: str, user_id: str + ) -> Optional[TagModel]: + try: + tag = Tag.get(Tag.name == name, Tag.user_id == user_id) + return TagModel(**model_to_dict(tag)) + except: + return None + + def add_tag_to_chat( + self, user_id: str, form_data: ChatIdTagForm + ) -> Optional[ChatTagsResponse]: + tag = self.get_tag_by_name_and_user_id(form_data.tag_name, user_id) + if tag == None: + tag = self.insert_new_tag(form_data.tag_name, user_id) + + chatIdTag = ChatIdTagModel(**{"user_id": user_id, "tag_name": tag.name}) + try: + result = ChatIdTag.create(**chatIdTag.model_dump()) + if result: + return chatIdTag + else: + return None + except: + return None + + def get_tags_by_chat_id_and_user_id( + self, chat_id: str, user_id: str + ) -> List[TagModel]: + return [ + TagModel(**model_to_dict(tag)) + for tag in Tag.select().where( + Tag.name + in [ + ChatIdTagModel(**model_to_dict(chat_id_tag)).tag_name + for chat_id_tag in ChatIdTag.select() + .where( + (ChatIdTag.user_id == user_id) & (ChatIdTag.chat_id == chat_id) + ) + .order_by(ChatIdTag.timestamp.desc()) + ] + ) + ] + + def get_chat_ids_by_tag_name_and_user_id( + self, tag_name: str, user_id: str + ) -> Optional[ChatIdTagModel]: + return [ + ChatIdTagModel(**model_to_dict(chat_id_tag)) + for chat_id_tag in ChatIdTag.select() + .where((ChatIdTag.user_id == user_id) & (ChatIdTag.tag_name == tag_name)) + .order_by(ChatIdTag.timestamp.desc()) + ] + + def count_chat_ids_by_tag_name_and_user_id( + self, tag_name: str, user_id: str + ) -> int: + return ( + ChatIdTag.select() + .where((ChatIdTag.tag_name == tag_name) & (ChatIdTag.user_id == user_id)) + .count() + ) + + def delete_tag_by_tag_name_and_chat_id_and_user_id( + self, tag_name: str, chat_id: str, user_id: str + ) -> bool: + try: + query = ChatIdTag.delete().where( + (ChatIdTag.tag_name == tag_name) + & (ChatIdTag.chat_id == chat_id) + & (ChatIdTag.user_id == user_id) + ) + query.execute() # Remove the rows, return number of rows removed. + + tag_count = self.count_chat_ids_by_tag_name_and_user_id(tag_name, user_id) + if tag_count == 0: + # Remove tag item from Tag col as well + query = Tag.delete().where( + (Tag.name == tag_name) & (Tag.user_id == user_id) + ) + query.execute() # Remove the rows, return number of rows removed. + + return True + except: + return False + + def delete_tags_by_chat_id_and_user_id(self, chat_id: str, user_id: str) -> bool: + tags = self.get_tags_by_chat_id_and_user_id(chat_id, user_id) + + for tag in tags: + self.delete_tag_by_tag_name_and_chat_id_and_user_id( + tag.tag_name, chat_id, user_id + ) + + return True + + +Tags = TagTable(DB) diff --git a/backend/apps/web/routers/chats.py b/backend/apps/web/routers/chats.py index e97e14730..a419a4122 100644 --- a/backend/apps/web/routers/chats.py +++ b/backend/apps/web/routers/chats.py @@ -16,6 +16,14 @@ from apps.web.models.chats import ( Chats, ) + +from apps.web.models.tags import ( + TagModel, + ChatIdTagForm, + ChatTagsResponse, + Tags, +) + from utils.utils import ( bearer_scheme, ) @@ -115,6 +123,63 @@ async def delete_chat_by_id(id: str, user=Depends(get_current_user)): return result +############################ +# GetChatTagsById +############################ + + +@router.get("/{id}/tags", response_model=List[TagModel]) +async def get_chat_tags_by_id(id: str, user=Depends(get_current_user)): + tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) + + if tags: + return tags + else: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND + ) + + +############################ +# AddChatTagById +############################ + + +@router.post("/{id}/tags", response_model=Optional[ChatTagsResponse]) +async def add_chat_tag_by_id( + id: str, form_data: ChatIdTagForm, user=Depends(get_current_user) +): + tag = Tags.add_tag_to_chat(user.id, {"tag_name": form_data.tag_name, "chat_id": id}) + + if tag: + return tag + else: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND + ) + + +############################ +# DeleteChatTagById +############################ + + +@router.delete("/{id}/tags", response_model=Optional[bool]) +async def add_chat_tag_by_id( + id: str, form_data: ChatIdTagForm, user=Depends(get_current_user) +): + tag = Tags.delete_tag_by_tag_name_and_chat_id_and_user_id( + form_data.tag_name, id, user.id + ) + + if tag: + return tag + else: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND + ) + + ############################ # DeleteAllChats ############################ From d5ed119687cfb8a27e089dc185ba563372e94189 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 18 Jan 2024 01:04:24 -0800 Subject: [PATCH 06/10] feat: convo tagging api added --- backend/apps/web/routers/chats.py | 25 +++++- src/lib/apis/chats/index.ts | 135 ++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/backend/apps/web/routers/chats.py b/backend/apps/web/routers/chats.py index a419a4122..0c9aa5736 100644 --- a/backend/apps/web/routers/chats.py +++ b/backend/apps/web/routers/chats.py @@ -165,15 +165,32 @@ async def add_chat_tag_by_id( @router.delete("/{id}/tags", response_model=Optional[bool]) -async def add_chat_tag_by_id( +async def delete_chat_tag_by_id( id: str, form_data: ChatIdTagForm, user=Depends(get_current_user) ): - tag = Tags.delete_tag_by_tag_name_and_chat_id_and_user_id( + result = Tags.delete_tag_by_tag_name_and_chat_id_and_user_id( form_data.tag_name, id, user.id ) - if tag: - return tag + if result: + return result + else: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND + ) + + +############################ +# DeleteAllChatTagsById +############################ + + +@router.delete("/{id}/tags/all", response_model=Optional[bool]) +async def delete_all_chat_tags_by_id(id: str, user=Depends(get_current_user)): + result = Tags.delete_tags_by_chat_id_and_user_id(id, user.id) + + if result: + return result else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 0eddf5b4a..b7f01c6e9 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -192,6 +192,141 @@ export const deleteChatById = async (token: string, id: string) => { return res; }; +export const getTagsById = async (token: string, id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/tags`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const addTagById = async (token: string, id: string, tagName: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/tags`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + }, + body: JSON.stringify({ + tag_name: tagName, + chat_id: id + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const deleteTagById = async (token: string, id: string, tagName: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/tags`, { + method: 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + }, + body: JSON.stringify({ + tag_name: tagName, + chat_id: id + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; +export const deleteTagsById = async (token: string, id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/tags/all`, { + method: 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const deleteAllChats = async (token: string) => { let error = null; From 987685dbf9223197d66c77fac82033fb7bfd2528 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 18 Jan 2024 02:10:07 -0800 Subject: [PATCH 07/10] feat: convo tagging full integration --- backend/apps/web/models/tags.py | 55 +++++--- backend/apps/web/routers/chats.py | 22 +++- src/lib/components/layout/Navbar.svelte | 168 +++++++++++------------- src/routes/(app)/+page.svelte | 28 +++- src/routes/(app)/c/[id]/+page.svelte | 31 ++++- 5 files changed, 185 insertions(+), 119 deletions(-) diff --git a/backend/apps/web/models/tags.py b/backend/apps/web/models/tags.py index eb33b31b1..ef21ca08c 100644 --- a/backend/apps/web/models/tags.py +++ b/backend/apps/web/models/tags.py @@ -15,7 +15,8 @@ from apps.web.internal.db import DB class Tag(Model): - name = CharField(unique=True) + id = CharField(unique=True) + name = CharField() user_id = CharField() data = TextField(null=True) @@ -24,7 +25,8 @@ class Tag(Model): class ChatIdTag(Model): - tag_name = ForeignKeyField(Tag, backref="chat_id_tags") + id = CharField(unique=True) + tag_name = CharField() chat_id = CharField() user_id = CharField() timestamp = DateField() @@ -34,12 +36,14 @@ class ChatIdTag(Model): class TagModel(BaseModel): + id: str name: str user_id: str data: Optional[str] = None class ChatIdTagModel(BaseModel): + id: str tag_name: str chat_id: str user_id: str @@ -70,14 +74,15 @@ class TagTable: db.create_tables([Tag, ChatIdTag]) def insert_new_tag(self, name: str, user_id: str) -> Optional[TagModel]: - tag = TagModel(**{"user_id": user_id, "name": name}) + id = str(uuid.uuid4()) + tag = TagModel(**{"id": id, "user_id": user_id, "name": name}) try: result = Tag.create(**tag.model_dump()) if result: return tag else: return None - except: + except Exception as e: return None def get_tag_by_name_and_user_id( @@ -86,17 +91,27 @@ class TagTable: try: tag = Tag.get(Tag.name == name, Tag.user_id == user_id) return TagModel(**model_to_dict(tag)) - except: + except Exception as e: return None def add_tag_to_chat( self, user_id: str, form_data: ChatIdTagForm - ) -> Optional[ChatTagsResponse]: + ) -> Optional[ChatIdTagModel]: tag = self.get_tag_by_name_and_user_id(form_data.tag_name, user_id) if tag == None: tag = self.insert_new_tag(form_data.tag_name, user_id) - chatIdTag = ChatIdTagModel(**{"user_id": user_id, "tag_name": tag.name}) + print(tag) + id = str(uuid.uuid4()) + chatIdTag = ChatIdTagModel( + **{ + "id": id, + "user_id": user_id, + "chat_id": form_data.chat_id, + "tag_name": tag.name, + "timestamp": int(time.time()), + } + ) try: result = ChatIdTag.create(**chatIdTag.model_dump()) if result: @@ -109,19 +124,17 @@ class TagTable: def get_tags_by_chat_id_and_user_id( self, chat_id: str, user_id: str ) -> List[TagModel]: + tag_names = [ + ChatIdTagModel(**model_to_dict(chat_id_tag)).tag_name + for chat_id_tag in ChatIdTag.select() + .where((ChatIdTag.user_id == user_id) & (ChatIdTag.chat_id == chat_id)) + .order_by(ChatIdTag.timestamp.desc()) + ] + + print(tag_names) return [ TagModel(**model_to_dict(tag)) - for tag in Tag.select().where( - Tag.name - in [ - ChatIdTagModel(**model_to_dict(chat_id_tag)).tag_name - for chat_id_tag in ChatIdTag.select() - .where( - (ChatIdTag.user_id == user_id) & (ChatIdTag.chat_id == chat_id) - ) - .order_by(ChatIdTag.timestamp.desc()) - ] - ) + for tag in Tag.select().where(Tag.name.in_(tag_names)) ] def get_chat_ids_by_tag_name_and_user_id( @@ -152,7 +165,8 @@ class TagTable: & (ChatIdTag.chat_id == chat_id) & (ChatIdTag.user_id == user_id) ) - query.execute() # Remove the rows, return number of rows removed. + res = query.execute() # Remove the rows, return number of rows removed. + print(res) tag_count = self.count_chat_ids_by_tag_name_and_user_id(tag_name, user_id) if tag_count == 0: @@ -163,7 +177,8 @@ class TagTable: query.execute() # Remove the rows, return number of rows removed. return True - except: + except Exception as e: + print("delete_tag", e) return False def delete_tags_by_chat_id_and_user_id(self, chat_id: str, user_id: str) -> bool: diff --git a/backend/apps/web/routers/chats.py b/backend/apps/web/routers/chats.py index 0c9aa5736..38685826f 100644 --- a/backend/apps/web/routers/chats.py +++ b/backend/apps/web/routers/chats.py @@ -19,6 +19,7 @@ from apps.web.models.chats import ( from apps.web.models.tags import ( TagModel, + ChatIdTagModel, ChatIdTagForm, ChatTagsResponse, Tags, @@ -132,7 +133,8 @@ async def delete_chat_by_id(id: str, user=Depends(get_current_user)): async def get_chat_tags_by_id(id: str, user=Depends(get_current_user)): tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) - if tags: + if tags != None: + print(tags) return tags else: raise HTTPException( @@ -145,17 +147,25 @@ async def get_chat_tags_by_id(id: str, user=Depends(get_current_user)): ############################ -@router.post("/{id}/tags", response_model=Optional[ChatTagsResponse]) +@router.post("/{id}/tags", response_model=Optional[ChatIdTagModel]) async def add_chat_tag_by_id( id: str, form_data: ChatIdTagForm, user=Depends(get_current_user) ): - tag = Tags.add_tag_to_chat(user.id, {"tag_name": form_data.tag_name, "chat_id": id}) + tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) - if tag: - return tag + if form_data.tag_name not in tags: + tag = Tags.add_tag_to_chat(user.id, form_data) + + if tag: + return tag + else: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.NOT_FOUND, + ) else: raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND + status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT() ) diff --git a/src/lib/components/layout/Navbar.svelte b/src/lib/components/layout/Navbar.svelte index 521e7abcc..5899d5822 100644 --- a/src/lib/components/layout/Navbar.svelte +++ b/src/lib/components/layout/Navbar.svelte @@ -12,22 +12,11 @@ export let title: string = 'Ollama Web UI'; export let shareEnabled: boolean = false; - let showShareChatModal = false; + export let tags = []; + export let addTag: Function; + export let deleteTag: Function; - let tags = [ - // { - // name: 'general' - // }, - // { - // name: 'medicine' - // }, - // { - // name: 'cooking' - // }, - // { - // name: 'education' - // } - ]; + let showShareChatModal = false; let tagName = ''; let showTagInput = false; @@ -74,16 +63,17 @@ saveAs(blob, `chat-${chat.title}.txt`); }; - const addTag = () => { - if (!tags.find((e) => e.name === tagName)) { - tags = [ - ...tags, - { - name: JSON.parse(JSON.stringify(tagName)) - } - ]; - } + const addTagHandler = () => { + // if (!tags.find((e) => e.name === tagName)) { + // tags = [ + // ...tags, + // { + // name: JSON.parse(JSON.stringify(tagName)) + // } + // ]; + // } + addTag(tagName); tagName = ''; showTagInput = false; }; @@ -126,48 +116,19 @@
-
- {#each tags as tag} -
-
- {tag.name} -
- -
- {/each} - -
- {#if showTagInput} -
- - +
+ {tag.name} +
+ {/each} - - {/if} - - -
-
- {#if shareEnabled} + +
+ + + {/if} + + +
+
+ + {#each $tags as tag} + + {/each} +
+ {/if} +
{#each $chats.filter((chat) => { if (search === '') { diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index c7d8f5e62..7880235cf 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -10,6 +10,7 @@ export const theme = writable('dark'); export const chatId = writable(''); export const chats = writable([]); +export const tags = writable([]); export const models = writable([]); export const modelfiles = writable([]); diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 39ae0eeac..c7839d93f 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -20,7 +20,8 @@ models, modelfiles, prompts, - documents + documents, + tags } from '$lib/stores'; import { REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants'; @@ -29,6 +30,7 @@ import { checkVersion } from '$lib/utils'; import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte'; import { getDocs } from '$lib/apis/documents'; + import { getAllChatTags } from '$lib/apis/chats'; let ollamaVersion = ''; let loaded = false; @@ -106,6 +108,7 @@ await modelfiles.set(await getModelfiles(localStorage.token)); await prompts.set(await getPrompts(localStorage.token)); await documents.set(await getDocs(localStorage.token)); + await tags.set(await getAllChatTags(localStorage.token)); modelfiles.subscribe(async () => { // should fetch models diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 29e4f2018..9507579c6 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -6,7 +6,16 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; - import { models, modelfiles, user, settings, chats, chatId, config } from '$lib/stores'; + import { + models, + modelfiles, + user, + settings, + chats, + chatId, + config, + tags as _tags + } from '$lib/stores'; import { copyToClipboard, splitStream } from '$lib/utils'; import { generateChatCompletion, cancelChatCompletion, generateTitle } from '$lib/apis/ollama'; @@ -14,6 +23,7 @@ addTagById, createNewChat, deleteTagById, + getAllChatTags, getChatList, getTagsById, updateChatById @@ -695,6 +705,8 @@ chat = await updateChatById(localStorage.token, $chatId, { tags: tags }); + + _tags.set(await getAllChatTags(localStorage.token)); }; const deleteTag = async (tagName) => { @@ -704,6 +716,8 @@ chat = await updateChatById(localStorage.token, $chatId, { tags: tags }); + + _tags.set(await getAllChatTags(localStorage.token)); }; const setChatTitle = async (_chatId, _title) => { diff --git a/src/routes/(app)/c/[id]/+page.svelte b/src/routes/(app)/c/[id]/+page.svelte index 37f6f39c4..206b73989 100644 --- a/src/routes/(app)/c/[id]/+page.svelte +++ b/src/routes/(app)/c/[id]/+page.svelte @@ -6,7 +6,16 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; - import { models, modelfiles, user, settings, chats, chatId, config } from '$lib/stores'; + import { + models, + modelfiles, + user, + settings, + chats, + chatId, + config, + tags as _tags + } from '$lib/stores'; import { copyToClipboard, splitStream, convertMessagesToHistory } from '$lib/utils'; import { generateChatCompletion, generateTitle } from '$lib/apis/ollama'; @@ -14,6 +23,7 @@ addTagById, createNewChat, deleteTagById, + getAllChatTags, getChatById, getChatList, getTagsById, @@ -709,8 +719,10 @@ tags = await getTags(); chat = await updateChatById(localStorage.token, $chatId, { - tags: tags.map((tag) => tag.name) + tags: tags }); + + _tags.set(await getAllChatTags(localStorage.token)); }; const deleteTag = async (tagName) => { @@ -718,8 +730,10 @@ tags = await getTags(); chat = await updateChatById(localStorage.token, $chatId, { - tags: tags.map((tag) => tag.name) + tags: tags }); + + _tags.set(await getAllChatTags(localStorage.token)); }; onMount(async () => { From 76529acced2449b992f8a6a21d5f4f57287ec1ce Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 18 Jan 2024 02:57:31 -0800 Subject: [PATCH 10/10] doc: feature --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index dfa7c1a5c..3a14b00aa 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ Also check our sibling project, [OllamaHub](https://ollamahub.com/), where you c - 👍👎 **RLHF Annotation**: Empower your messages by rating them with thumbs up and thumbs down, facilitating the creation of datasets for Reinforcement Learning from Human Feedback (RLHF). Utilize your messages to train or fine-tune models, all while ensuring the confidentiality of locally saved data. +- 🏷️ **Conversation Tagging**: Effortlessly categorize and locate specific chats for quick reference and streamlined data collection. + - 📥🗑️ **Download/Delete Models**: Easily download or remove models directly from the web UI. - ⬆️ **GGUF File Model Creation**: Effortlessly create Ollama models by uploading GGUF files directly from the web UI. Streamlined process with options to upload from your machine or download GGUF files from Hugging Face.