forked from open-webui/open-webui
commit
4e6207f888
13 changed files with 871 additions and 64 deletions
|
@ -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.
|
- 👍👎 **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.
|
- 📥🗑️ **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.
|
- ⬆️ **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.
|
||||||
|
|
|
@ -60,23 +60,23 @@ class ChatTitleIdResponse(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class ChatTable:
|
class ChatTable:
|
||||||
|
|
||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
db.create_tables([Chat])
|
db.create_tables([Chat])
|
||||||
|
|
||||||
def insert_new_chat(self, user_id: str,
|
def insert_new_chat(self, user_id: str, form_data: ChatForm) -> Optional[ChatModel]:
|
||||||
form_data: ChatForm) -> Optional[ChatModel]:
|
|
||||||
id = str(uuid.uuid4())
|
id = str(uuid.uuid4())
|
||||||
chat = ChatModel(
|
chat = ChatModel(
|
||||||
**{
|
**{
|
||||||
"id": id,
|
"id": id,
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"title": form_data.chat["title"] if "title" in
|
"title": form_data.chat["title"]
|
||||||
form_data.chat else "New Chat",
|
if "title" in form_data.chat
|
||||||
|
else "New Chat",
|
||||||
"chat": json.dumps(form_data.chat),
|
"chat": json.dumps(form_data.chat),
|
||||||
"timestamp": int(time.time()),
|
"timestamp": int(time.time()),
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
result = Chat.create(**chat.model_dump())
|
result = Chat.create(**chat.model_dump())
|
||||||
return chat if result else None
|
return chat if result else None
|
||||||
|
@ -109,25 +109,37 @@ class ChatTable:
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_chat_lists_by_user_id(self,
|
def get_chat_lists_by_user_id(
|
||||||
user_id: str,
|
self, user_id: str, skip: int = 0, limit: int = 50
|
||||||
skip: int = 0,
|
) -> List[ChatModel]:
|
||||||
limit: int = 50) -> List[ChatModel]:
|
|
||||||
return [
|
return [
|
||||||
ChatModel(**model_to_dict(chat)) for chat in Chat.select().where(
|
ChatModel(**model_to_dict(chat))
|
||||||
Chat.user_id == user_id).order_by(Chat.timestamp.desc())
|
for chat in Chat.select()
|
||||||
|
.where(Chat.user_id == user_id)
|
||||||
|
.order_by(Chat.timestamp.desc())
|
||||||
# .limit(limit)
|
# .limit(limit)
|
||||||
# .offset(skip)
|
# .offset(skip)
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_all_chats_by_user_id(self, user_id: str) -> List[ChatModel]:
|
def get_chat_lists_by_chat_ids(
|
||||||
|
self, chat_ids: List[str], skip: int = 0, limit: int = 50
|
||||||
|
) -> List[ChatModel]:
|
||||||
return [
|
return [
|
||||||
ChatModel(**model_to_dict(chat)) for chat in Chat.select().where(
|
ChatModel(**model_to_dict(chat))
|
||||||
Chat.user_id == user_id).order_by(Chat.timestamp.desc())
|
for chat in Chat.select()
|
||||||
|
.where(Chat.id.in_(chat_ids))
|
||||||
|
.order_by(Chat.timestamp.desc())
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_chat_by_id_and_user_id(self, id: str,
|
def get_all_chats_by_user_id(self, user_id: str) -> List[ChatModel]:
|
||||||
user_id: str) -> Optional[ChatModel]:
|
return [
|
||||||
|
ChatModel(**model_to_dict(chat))
|
||||||
|
for chat in Chat.select()
|
||||||
|
.where(Chat.user_id == user_id)
|
||||||
|
.order_by(Chat.timestamp.desc())
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]:
|
||||||
try:
|
try:
|
||||||
chat = Chat.get(Chat.id == id, Chat.user_id == user_id)
|
chat = Chat.get(Chat.id == id, Chat.user_id == user_id)
|
||||||
return ChatModel(**model_to_dict(chat))
|
return ChatModel(**model_to_dict(chat))
|
||||||
|
@ -142,8 +154,7 @@ class ChatTable:
|
||||||
|
|
||||||
def delete_chat_by_id_and_user_id(self, id: str, user_id: str) -> bool:
|
def delete_chat_by_id_and_user_id(self, id: str, user_id: str) -> bool:
|
||||||
try:
|
try:
|
||||||
query = Chat.delete().where((Chat.id == id)
|
query = Chat.delete().where((Chat.id == id) & (Chat.user_id == user_id))
|
||||||
& (Chat.user_id == user_id))
|
|
||||||
query.execute() # Remove the rows, return number of rows removed.
|
query.execute() # Remove the rows, return number of rows removed.
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
206
backend/apps/web/models/tags.py
Normal file
206
backend/apps/web/models/tags.py
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
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):
|
||||||
|
id = CharField(unique=True)
|
||||||
|
name = CharField()
|
||||||
|
user_id = CharField()
|
||||||
|
data = TextField(null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = DB
|
||||||
|
|
||||||
|
|
||||||
|
class ChatIdTag(Model):
|
||||||
|
id = CharField(unique=True)
|
||||||
|
tag_name = CharField()
|
||||||
|
chat_id = CharField()
|
||||||
|
user_id = CharField()
|
||||||
|
timestamp = DateField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = DB
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
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]:
|
||||||
|
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 Exception as e:
|
||||||
|
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 Exception as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_tag_to_chat(
|
||||||
|
self, user_id: str, form_data: ChatIdTagForm
|
||||||
|
) -> 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)
|
||||||
|
|
||||||
|
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:
|
||||||
|
return chatIdTag
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_tags_by_user_id(self, 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)
|
||||||
|
.order_by(ChatIdTag.timestamp.desc())
|
||||||
|
]
|
||||||
|
|
||||||
|
return [
|
||||||
|
TagModel(**model_to_dict(tag))
|
||||||
|
for tag in Tag.select().where(Tag.name.in_(tag_names))
|
||||||
|
]
|
||||||
|
|
||||||
|
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())
|
||||||
|
]
|
||||||
|
|
||||||
|
return [
|
||||||
|
TagModel(**model_to_dict(tag))
|
||||||
|
for tag in Tag.select().where(Tag.name.in_(tag_names))
|
||||||
|
]
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
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:
|
||||||
|
# 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 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:
|
||||||
|
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)
|
|
@ -16,6 +16,15 @@ from apps.web.models.chats import (
|
||||||
Chats,
|
Chats,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
from apps.web.models.tags import (
|
||||||
|
TagModel,
|
||||||
|
ChatIdTagModel,
|
||||||
|
ChatIdTagForm,
|
||||||
|
ChatTagsResponse,
|
||||||
|
Tags,
|
||||||
|
)
|
||||||
|
|
||||||
from utils.utils import (
|
from utils.utils import (
|
||||||
bearer_scheme,
|
bearer_scheme,
|
||||||
)
|
)
|
||||||
|
@ -65,6 +74,42 @@ async def create_new_chat(form_data: ChatForm, user=Depends(get_current_user)):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# GetAllTags
|
||||||
|
############################
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tags/all", response_model=List[TagModel])
|
||||||
|
async def get_all_tags(user=Depends(get_current_user)):
|
||||||
|
try:
|
||||||
|
tags = Tags.get_tags_by_user_id(user.id)
|
||||||
|
return tags
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# GetChatsByTags
|
||||||
|
############################
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tags/tag/{tag_name}", response_model=List[ChatTitleIdResponse])
|
||||||
|
async def get_user_chats_by_tag_name(
|
||||||
|
tag_name: str, user=Depends(get_current_user), skip: int = 0, limit: int = 50
|
||||||
|
):
|
||||||
|
chat_ids = [
|
||||||
|
chat_id_tag.chat_id
|
||||||
|
for chat_id_tag in Tags.get_chat_ids_by_tag_name_and_user_id(tag_name, user.id)
|
||||||
|
]
|
||||||
|
|
||||||
|
print(chat_ids)
|
||||||
|
|
||||||
|
return Chats.get_chat_lists_by_chat_ids(chat_ids, skip, limit)
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# GetChatById
|
# GetChatById
|
||||||
############################
|
############################
|
||||||
|
@ -115,6 +160,88 @@ async def delete_chat_by_id(id: str, user=Depends(get_current_user)):
|
||||||
return result
|
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 != None:
|
||||||
|
return tags
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# AddChatTagById
|
||||||
|
############################
|
||||||
|
|
||||||
|
|
||||||
|
@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)
|
||||||
|
):
|
||||||
|
tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id)
|
||||||
|
|
||||||
|
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.DEFAULT()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# DeleteChatTagById
|
||||||
|
############################
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{id}/tags", response_model=Optional[bool])
|
||||||
|
async def delete_chat_tag_by_id(
|
||||||
|
id: str, form_data: ChatIdTagForm, user=Depends(get_current_user)
|
||||||
|
):
|
||||||
|
result = Tags.delete_tag_by_tag_name_and_chat_id_and_user_id(
|
||||||
|
form_data.tag_name, id, user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# DeleteAllChats
|
# DeleteAllChats
|
||||||
############################
|
############################
|
||||||
|
|
|
@ -93,6 +93,68 @@ export const getAllChats = async (token: string) => {
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAllChatTags = async (token: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/tags/all`, {
|
||||||
|
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 getChatListByTagName = async (token: string = '', tagName: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/tags/tag/${tagName}`, {
|
||||||
|
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 getChatById = async (token: string, id: string) => {
|
export const getChatById = async (token: string, id: string) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
@ -192,6 +254,141 @@ export const deleteChatById = async (token: string, id: string) => {
|
||||||
return res;
|
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) => {
|
export const deleteAllChats = async (token: string) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
|
38
src/lib/components/chat/ShareChatModal.svelte
Normal file
38
src/lib/components/chat/ShareChatModal.svelte
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Modal from '../common/Modal.svelte';
|
||||||
|
|
||||||
|
export let downloadChat: Function;
|
||||||
|
export let shareChat: Function;
|
||||||
|
|
||||||
|
export let show = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal bind:show size="xs">
|
||||||
|
<div class="px-4 pt-4 pb-5 w-full flex flex-col justify-center">
|
||||||
|
<button
|
||||||
|
class=" self-center px-8 py-1.5 w-full rounded-full text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white"
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
shareChat();
|
||||||
|
show = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Share to OllamaHub
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="flex justify-center space-x-1 mt-1.5">
|
||||||
|
<div class=" self-center text-gray-400 text-xs font-medium">or</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class=" self-center rounded-full text-xs font-medium text-gray-700 dark:text-gray-500 underline"
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
downloadChat();
|
||||||
|
show = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download as a File
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
|
@ -8,7 +8,9 @@
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
|
|
||||||
const sizeToWidth = (size) => {
|
const sizeToWidth = (size) => {
|
||||||
if (size === 'sm') {
|
if (size === 'xs') {
|
||||||
|
return 'w-[16rem]';
|
||||||
|
} else if (size === 'sm') {
|
||||||
return 'w-[30rem]';
|
return 'w-[30rem]';
|
||||||
} else {
|
} else {
|
||||||
return 'w-[40rem]';
|
return 'w-[40rem]';
|
||||||
|
|
|
@ -5,11 +5,21 @@
|
||||||
|
|
||||||
import { getChatById } from '$lib/apis/chats';
|
import { getChatById } from '$lib/apis/chats';
|
||||||
import { chatId, modelfiles } from '$lib/stores';
|
import { chatId, modelfiles } from '$lib/stores';
|
||||||
|
import ShareChatModal from '../chat/ShareChatModal.svelte';
|
||||||
|
|
||||||
export let initNewChat: Function;
|
export let initNewChat: Function;
|
||||||
export let title: string = 'Ollama Web UI';
|
export let title: string = 'Ollama Web UI';
|
||||||
export let shareEnabled: boolean = false;
|
export let shareEnabled: boolean = false;
|
||||||
|
|
||||||
|
export let tags = [];
|
||||||
|
export let addTag: Function;
|
||||||
|
export let deleteTag: Function;
|
||||||
|
|
||||||
|
let showShareChatModal = false;
|
||||||
|
|
||||||
|
let tagName = '';
|
||||||
|
let showTagInput = false;
|
||||||
|
|
||||||
const shareChat = async () => {
|
const shareChat = async () => {
|
||||||
const chat = (await getChatById(localStorage.token, $chatId)).chat;
|
const chat = (await getChatById(localStorage.token, $chatId)).chat;
|
||||||
console.log('share', chat);
|
console.log('share', chat);
|
||||||
|
@ -51,18 +61,34 @@
|
||||||
|
|
||||||
saveAs(blob, `chat-${chat.title}.txt`);
|
saveAs(blob, `chat-${chat.title}.txt`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addTagHandler = () => {
|
||||||
|
// if (!tags.find((e) => e.name === tagName)) {
|
||||||
|
// tags = [
|
||||||
|
// ...tags,
|
||||||
|
// {
|
||||||
|
// name: JSON.parse(JSON.stringify(tagName))
|
||||||
|
// }
|
||||||
|
// ];
|
||||||
|
// }
|
||||||
|
|
||||||
|
addTag(tagName);
|
||||||
|
tagName = '';
|
||||||
|
showTagInput = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} />
|
||||||
<nav
|
<nav
|
||||||
id="nav"
|
id="nav"
|
||||||
class=" fixed py-2.5 top-0 flex flex-row justify-center bg-white/95 dark:bg-gray-800/90 dark:text-gray-200 backdrop-blur-xl w-screen z-30"
|
class=" fixed py-2.5 top-0 flex flex-row justify-center bg-white/95 dark:bg-gray-800/90 dark:text-gray-200 backdrop-blur-xl w-screen z-30"
|
||||||
>
|
>
|
||||||
<div class=" flex max-w-3xl w-full mx-auto px-3">
|
<div class=" flex max-w-3xl w-full mx-auto px-3">
|
||||||
<div class="flex w-full max-w-full">
|
<div class="flex items-center w-full max-w-full">
|
||||||
<div class="pr-2 self-center">
|
<div class="pr-2 self-start">
|
||||||
<button
|
<button
|
||||||
id="new-chat-button"
|
id="new-chat-button"
|
||||||
class=" cursor-pointer p-1 flex dark:hover:bg-gray-700 rounded-lg transition"
|
class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-lg transition"
|
||||||
on:click={initNewChat}
|
on:click={initNewChat}
|
||||||
>
|
>
|
||||||
<div class=" m-auto self-center">
|
<div class=" m-auto self-center">
|
||||||
|
@ -82,39 +108,78 @@
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class=" flex-1 self-center font-medium text-ellipsis whitespace-nowrap overflow-hidden">
|
<div class=" flex-1 self-center font-medium line-clamp-1">
|
||||||
|
<div>
|
||||||
{title != '' ? title : 'Ollama Web UI'}
|
{title != '' ? title : 'Ollama Web UI'}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pl-2 self-center flex items-center space-x-2">
|
||||||
{#if shareEnabled}
|
{#if shareEnabled}
|
||||||
<div class="pl-2 flex space-x-1.5">
|
<div class="flex flex-row space-x-0.5 line-clamp-1">
|
||||||
|
{#each tags as tag}
|
||||||
|
<div
|
||||||
|
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition border dark:border-gray-600 dark:text-white"
|
||||||
|
>
|
||||||
|
<div class=" text-[0.65rem] font-medium self-center line-clamp-1">
|
||||||
|
{tag.name}
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
class=" cursor-pointer p-2 flex dark:hover:bg-gray-700 rounded-lg transition border dark:border-gray-600"
|
class=" m-auto self-center cursor-pointer"
|
||||||
on:click={async () => {
|
on:click={() => {
|
||||||
downloadChat();
|
deleteTag(tag.name);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class=" m-auto self-center">
|
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
class="w-4 h-4"
|
class="w-3 h-3"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
|
d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z"
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<div class="flex space-x-1 pl-1.5">
|
||||||
|
{#if showTagInput}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
bind:value={tagName}
|
||||||
|
class=" cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[4rem]"
|
||||||
|
placeholder="Add a tag"
|
||||||
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class=" cursor-pointer p-2 flex dark:hover:bg-gray-700 rounded-lg transition border dark:border-gray-600"
|
on:click={() => {
|
||||||
on:click={async () => {
|
addTagHandler();
|
||||||
shareChat();
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-3 h-3"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TODO: Tag Suggestions -->
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button
|
||||||
|
class=" cursor-pointer self-center p-0.5 space-x-1 flex h-fit items-center dark:hover:bg-gray-700 rounded-full transition border dark:border-gray-600 border-dashed"
|
||||||
|
on:click={() => {
|
||||||
|
showTagInput = !showTagInput;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class=" m-auto self-center">
|
<div class=" m-auto self-center">
|
||||||
|
@ -122,19 +187,42 @@
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
class="w-4 h-4"
|
class="w-3 h-3 {showTagInput ? 'rotate-45' : ''} transition-all transform"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
|
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-lg transition border dark:border-gray-600"
|
||||||
|
on:click={async () => {
|
||||||
|
showShareChatModal = !showShareChatModal;
|
||||||
|
|
||||||
|
// console.log(showShareChatModal);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" m-auto self-center">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -6,9 +6,14 @@
|
||||||
|
|
||||||
import { goto, invalidateAll } from '$app/navigation';
|
import { goto, invalidateAll } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { user, chats, settings, showSettings, chatId } from '$lib/stores';
|
import { user, chats, settings, showSettings, chatId, tags } from '$lib/stores';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { deleteChatById, getChatList, updateChatById } from '$lib/apis/chats';
|
import {
|
||||||
|
deleteChatById,
|
||||||
|
getChatList,
|
||||||
|
getChatListByTagName,
|
||||||
|
updateChatById
|
||||||
|
} from '$lib/apis/chats';
|
||||||
|
|
||||||
let show = false;
|
let show = false;
|
||||||
let navElement;
|
let navElement;
|
||||||
|
@ -28,6 +33,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
await chats.set(await getChatList(localStorage.token));
|
await chats.set(await getChatList(localStorage.token));
|
||||||
|
|
||||||
|
tags.subscribe(async (value) => {
|
||||||
|
if (value.length === 0) {
|
||||||
|
await chats.set(await getChatList(localStorage.token));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadChat = async (id) => {
|
const loadChat = async (id) => {
|
||||||
|
@ -281,6 +292,29 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if $tags.length > 0}
|
||||||
|
<div class="px-2.5 mt-0.5 mb-2 flex gap-1 flex-wrap">
|
||||||
|
<button
|
||||||
|
class="px-2.5 text-xs font-medium bg-gray-900 hover:bg-gray-800 transition rounded-full"
|
||||||
|
on:click={async () => {
|
||||||
|
await chats.set(await getChatList(localStorage.token));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
all
|
||||||
|
</button>
|
||||||
|
{#each $tags as tag}
|
||||||
|
<button
|
||||||
|
class="px-2.5 text-xs font-medium bg-gray-900 hover:bg-gray-800 transition rounded-full"
|
||||||
|
on:click={async () => {
|
||||||
|
await chats.set(await getChatListByTagName(localStorage.token, tag.name));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="pl-2.5 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto">
|
<div class="pl-2.5 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto">
|
||||||
{#each $chats.filter((chat) => {
|
{#each $chats.filter((chat) => {
|
||||||
if (search === '') {
|
if (search === '') {
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const theme = writable('dark');
|
||||||
export const chatId = writable('');
|
export const chatId = writable('');
|
||||||
|
|
||||||
export const chats = writable([]);
|
export const chats = writable([]);
|
||||||
|
export const tags = writable([]);
|
||||||
export const models = writable([]);
|
export const models = writable([]);
|
||||||
|
|
||||||
export const modelfiles = writable([]);
|
export const modelfiles = writable([]);
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
models,
|
models,
|
||||||
modelfiles,
|
modelfiles,
|
||||||
prompts,
|
prompts,
|
||||||
documents
|
documents,
|
||||||
|
tags
|
||||||
} from '$lib/stores';
|
} from '$lib/stores';
|
||||||
import { REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
|
import { REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
import { checkVersion } from '$lib/utils';
|
import { checkVersion } from '$lib/utils';
|
||||||
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
|
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
|
||||||
import { getDocs } from '$lib/apis/documents';
|
import { getDocs } from '$lib/apis/documents';
|
||||||
|
import { getAllChatTags } from '$lib/apis/chats';
|
||||||
|
|
||||||
let ollamaVersion = '';
|
let ollamaVersion = '';
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
@ -106,6 +108,7 @@
|
||||||
await modelfiles.set(await getModelfiles(localStorage.token));
|
await modelfiles.set(await getModelfiles(localStorage.token));
|
||||||
await prompts.set(await getPrompts(localStorage.token));
|
await prompts.set(await getPrompts(localStorage.token));
|
||||||
await documents.set(await getDocs(localStorage.token));
|
await documents.set(await getDocs(localStorage.token));
|
||||||
|
await tags.set(await getAllChatTags(localStorage.token));
|
||||||
|
|
||||||
modelfiles.subscribe(async () => {
|
modelfiles.subscribe(async () => {
|
||||||
// should fetch models
|
// should fetch models
|
||||||
|
|
|
@ -6,11 +6,28 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
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 { copyToClipboard, splitStream } from '$lib/utils';
|
||||||
|
|
||||||
import { generateChatCompletion, cancelChatCompletion, generateTitle } from '$lib/apis/ollama';
|
import { generateChatCompletion, cancelChatCompletion, generateTitle } from '$lib/apis/ollama';
|
||||||
import { createNewChat, getChatList, updateChatById } from '$lib/apis/chats';
|
import {
|
||||||
|
addTagById,
|
||||||
|
createNewChat,
|
||||||
|
deleteTagById,
|
||||||
|
getAllChatTags,
|
||||||
|
getChatList,
|
||||||
|
getTagsById,
|
||||||
|
updateChatById
|
||||||
|
} from '$lib/apis/chats';
|
||||||
import { queryVectorDB } from '$lib/apis/rag';
|
import { queryVectorDB } from '$lib/apis/rag';
|
||||||
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
|
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
|
||||||
|
|
||||||
|
@ -47,6 +64,7 @@
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
let chat = null;
|
let chat = null;
|
||||||
|
let tags = [];
|
||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
let prompt = '';
|
let prompt = '';
|
||||||
|
@ -181,6 +199,7 @@
|
||||||
},
|
},
|
||||||
messages: messages,
|
messages: messages,
|
||||||
history: history,
|
history: history,
|
||||||
|
tags: [],
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
});
|
});
|
||||||
await chats.set(await getChatList(localStorage.token));
|
await chats.set(await getChatList(localStorage.token));
|
||||||
|
@ -673,6 +692,34 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTags = async () => {
|
||||||
|
return await getTagsById(localStorage.token, $chatId).catch(async (error) => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addTag = async (tagName) => {
|
||||||
|
const res = await addTagById(localStorage.token, $chatId, tagName);
|
||||||
|
tags = await getTags();
|
||||||
|
|
||||||
|
chat = await updateChatById(localStorage.token, $chatId, {
|
||||||
|
tags: tags
|
||||||
|
});
|
||||||
|
|
||||||
|
_tags.set(await getAllChatTags(localStorage.token));
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteTag = async (tagName) => {
|
||||||
|
const res = await deleteTagById(localStorage.token, $chatId, tagName);
|
||||||
|
tags = await getTags();
|
||||||
|
|
||||||
|
chat = await updateChatById(localStorage.token, $chatId, {
|
||||||
|
tags: tags
|
||||||
|
});
|
||||||
|
|
||||||
|
_tags.set(await getAllChatTags(localStorage.token));
|
||||||
|
};
|
||||||
|
|
||||||
const setChatTitle = async (_chatId, _title) => {
|
const setChatTitle = async (_chatId, _title) => {
|
||||||
if (_chatId === $chatId) {
|
if (_chatId === $chatId) {
|
||||||
title = _title;
|
title = _title;
|
||||||
|
@ -691,7 +738,7 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} />
|
<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} {tags} {addTag} {deleteTag} />
|
||||||
<div class="min-h-screen w-full flex justify-center">
|
<div class="min-h-screen w-full flex justify-center">
|
||||||
<div class=" py-2.5 flex flex-col justify-between w-full">
|
<div class=" py-2.5 flex flex-col justify-between w-full">
|
||||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 mt-10">
|
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 mt-10">
|
||||||
|
|
|
@ -6,11 +6,29 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
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 { copyToClipboard, splitStream, convertMessagesToHistory } from '$lib/utils';
|
||||||
|
|
||||||
import { generateChatCompletion, generateTitle } from '$lib/apis/ollama';
|
import { generateChatCompletion, generateTitle } from '$lib/apis/ollama';
|
||||||
import { createNewChat, getChatById, getChatList, updateChatById } from '$lib/apis/chats';
|
import {
|
||||||
|
addTagById,
|
||||||
|
createNewChat,
|
||||||
|
deleteTagById,
|
||||||
|
getAllChatTags,
|
||||||
|
getChatById,
|
||||||
|
getChatList,
|
||||||
|
getTagsById,
|
||||||
|
updateChatById
|
||||||
|
} from '$lib/apis/chats';
|
||||||
import { queryVectorDB } from '$lib/apis/rag';
|
import { queryVectorDB } from '$lib/apis/rag';
|
||||||
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
|
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
|
||||||
|
|
||||||
|
@ -49,6 +67,7 @@
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
let chat = null;
|
let chat = null;
|
||||||
|
let tags = [];
|
||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
let prompt = '';
|
let prompt = '';
|
||||||
|
@ -97,6 +116,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (chat) {
|
if (chat) {
|
||||||
|
tags = await getTags();
|
||||||
const chatContent = chat.chat;
|
const chatContent = chat.chat;
|
||||||
|
|
||||||
if (chatContent) {
|
if (chatContent) {
|
||||||
|
@ -688,6 +708,34 @@
|
||||||
await chats.set(await getChatList(localStorage.token));
|
await chats.set(await getChatList(localStorage.token));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTags = async () => {
|
||||||
|
return await getTagsById(localStorage.token, $chatId).catch(async (error) => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addTag = async (tagName) => {
|
||||||
|
const res = await addTagById(localStorage.token, $chatId, tagName);
|
||||||
|
tags = await getTags();
|
||||||
|
|
||||||
|
chat = await updateChatById(localStorage.token, $chatId, {
|
||||||
|
tags: tags
|
||||||
|
});
|
||||||
|
|
||||||
|
_tags.set(await getAllChatTags(localStorage.token));
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteTag = async (tagName) => {
|
||||||
|
const res = await deleteTagById(localStorage.token, $chatId, tagName);
|
||||||
|
tags = await getTags();
|
||||||
|
|
||||||
|
chat = await updateChatById(localStorage.token, $chatId, {
|
||||||
|
tags: tags
|
||||||
|
});
|
||||||
|
|
||||||
|
_tags.set(await getAllChatTags(localStorage.token));
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!($settings.saveChatHistory ?? true)) {
|
if (!($settings.saveChatHistory ?? true)) {
|
||||||
await goto('/');
|
await goto('/');
|
||||||
|
@ -713,6 +761,9 @@
|
||||||
|
|
||||||
goto('/');
|
goto('/');
|
||||||
}}
|
}}
|
||||||
|
{tags}
|
||||||
|
{addTag}
|
||||||
|
{deleteTag}
|
||||||
/>
|
/>
|
||||||
<div class="min-h-screen w-full flex justify-center">
|
<div class="min-h-screen w-full flex justify-center">
|
||||||
<div class=" py-2.5 flex flex-col justify-between w-full">
|
<div class=" py-2.5 flex flex-col justify-between w-full">
|
||||||
|
|
Loading…
Reference in a new issue