Skip to content

Commit

Permalink
Merge pull request #35 from SkywardAI/improves
Browse files Browse the repository at this point in the history
add delete / rename conversation & bug fix
  • Loading branch information
cbh778899 authored Sep 17, 2024
2 parents 25fa0ab + 9dfe2ca commit 345c309
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 73 deletions.
120 changes: 69 additions & 51 deletions src/components/chat/Conversation.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { useEffect, useRef, useState } from "react";
import ConversationBubble from "./ConversationBubble";
import { FileImageFill, FileTextFill, Paperclip, Send, StopCircleFill } from 'react-bootstrap-icons';
import { CheckCircle, FileImageFill, FileTextFill, Paperclip, Send, StopCircleFill, XCircle } from 'react-bootstrap-icons';
import useIDB from "../../utils/idb";
import { isModelLoaded, loadModel } from '../../utils/workers/worker'
import { getCompletionFunctions } from "../../utils/workers";
import { setClient as setAwsClient } from "../../utils/workers/aws-worker";
import { setClient as setOpenaiClient } from "../../utils/workers/openai-worker";

export default function Conversation({ uid, client, updateClient }) {
export default function Conversation({ uid, title, updateTitle, client, updateClient }) {

const [conversation, setConversation] = useState([]);
const [message, setMessage] = useState('');
const [pending_message, setPendingMessage] = useState('');
const [hide_pending, setHidePending] = useState(true);
const [upload_file, setUploadFile] = useState(null);
const [edit_title, toggleEditTitle] = useState(false);
const [edited_title, setEditedTitle] = useState(title);
const chat_functions = useRef(getCompletionFunctions());
const idb = useIDB();

Expand Down Expand Up @@ -53,7 +55,6 @@ export default function Conversation({ uid, client, updateClient }) {
if(!isFinished) {
setPendingMessage(text);
} else {
setPendingMessage('');
setConversation([
...conversation, user_msg,
{ role: 'assistant', content: text }
Expand All @@ -67,7 +68,6 @@ export default function Conversation({ uid, client, updateClient }) {
idb.updateOne(
'chat-history', {updatedAt: Date.now()}, [{uid}]
)
setHidePending(true);
}
}

Expand Down Expand Up @@ -106,13 +106,16 @@ export default function Conversation({ uid, client, updateClient }) {
]
}

const result = await chat_functions.current.completions(messages, cb);
if(!result) {
setPendingMessage('');
setHidePending(true);
} else {
console.log(result)
await chat_functions.current.completions(messages, cb);
setPendingMessage('');
setHidePending(true);
}

function submitUpdateTitle() {
if(edited_title && edited_title !== title) {
updateTitle(edited_title);
}
toggleEditTitle(false);
}

useEffect(()=>{
Expand All @@ -129,7 +132,11 @@ export default function Conversation({ uid, client, updateClient }) {
}, [conversation, pending_message])

useEffect(()=>{
if(!chat_functions.current) return;
setEditedTitle(title);
}, [title])

useEffect(()=>{
if(!chat_functions.current || !uid) return;

const platform = chat_functions.current.platform
if(platform) {
Expand All @@ -145,55 +152,66 @@ export default function Conversation({ uid, client, updateClient }) {
})()
}
// eslint-disable-next-line
}, [client])
}, [uid])

return (
<div className="conversation-main">
{
uid ?
<>
<div className="bubbles" ref={bubblesRef}>
{ conversation.map(({role, content}, idx) => {
return (
<ConversationBubble
key={`conversation-history-${uid}-${idx}`}
role={role} content={content}
/>
)
}) }
<ConversationBubble
role={'assistant'} content={pending_message}
hidden={hide_pending}
/>
</div>
<form className="send-message-form" onSubmit={sendMessage}>
<div className="input-container">
{
chat_functions.current && chat_functions.current.platform !== 'Wllama' &&
<div className="button-container file-upload">
{
upload_file ?
upload_file.type.startsWith("image") ?
<FileImageFill className="button-icon highlight" /> : <FileTextFill className="button-icon highlight" />:
<Paperclip className="button-icon" />
}
<input
type="file" className="clickable"
title={upload_file ? `Append file ${upload_file.name}` : "Select file to append"}
onChange={evt=>setUploadFile(evt.target.files.length ? evt.target.files[0] : null)} />
</div>
}
<input type="text" value={message} onChange={messageOnChange}/>
<div className="button-container">
{
hide_pending ?
<Send className="button-icon animated" /> :
<StopCircleFill className="button-icon clickable" onClick={chat_functions.current.abort} />
<div className="title-bar">
{
edit_title ?
<form onSubmit={evt=>{evt.preventDefault(); submitUpdateTitle()}}>
<input className="edit-title" value={edited_title} onChange={evt=>setEditedTitle(evt.target.value)} />
<CheckCircle className="btn clickable" onClick={submitUpdateTitle} />
<XCircle className="btn clickable" onClick={()=>{setEditedTitle(title); toggleEditTitle(false)}} />
</form>:
<div className="text" onClick={()=>toggleEditTitle(true)}>{ title }</div>
}
</div>
<div className="bubbles" ref={bubblesRef}>
{ conversation.map(({role, content}, idx) => {
return (
<ConversationBubble
key={`conversation-history-${uid}-${idx}`}
role={role} content={content}
/>
)
}) }
<ConversationBubble
role={'assistant'} content={pending_message}
hidden={hide_pending} special={true}
/>
</div>
<form className="send-message-form" onSubmit={sendMessage}>
<div className="input-container">
{
chat_functions.current && chat_functions.current.platform !== 'Wllama' &&
<div className="button-container file-upload">
{
upload_file ?
upload_file.type.startsWith("image") ?
<FileImageFill className="button-icon highlight" /> : <FileTextFill className="button-icon highlight" />:
<Paperclip className="button-icon" />
}
<input type='submit' className={`clickable${!hide_pending?" disabled":''}`}/>
<input
type="file" className="clickable"
title={upload_file ? `Append file ${upload_file.name}` : "Select file to append"}
onChange={evt=>setUploadFile(evt.target.files.length ? evt.target.files[0] : null)} />
</div>
}
<input type="text" value={message} onChange={messageOnChange}/>
<div className="button-container">
{
hide_pending ?
<Send className="button-icon animated" /> :
<StopCircleFill className="button-icon clickable" onClick={chat_functions.current.abort} />
}
<input type='submit' className={`clickable${!hide_pending?" disabled":''}`}/>
</div>
</form>
</div>
</form>
</> :
<div className="no-conversation">Please select a conversation or start a new one.</div>
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/chat/ConversationBubble.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { CircleFill } from "react-bootstrap-icons"
import Markdown from "react-markdown"

export default function ConversationBubble({role, content, hidden}) {
export default function ConversationBubble({role, content, hidden, special}) {
return (
<div className={`bubble ${role}${hidden?' hidden':""}`}>
{
content ?
<Markdown>{ content }</Markdown> :
content || !special?
<Markdown>{ content || "**[ EMPTY MESSAGE ]**" }</Markdown> :
<>
<CircleFill className="dot-animation" />
<CircleFill className="dot-animation" />
Expand Down
12 changes: 11 additions & 1 deletion src/components/chat/Ticket.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
export default function Ticket({ title, info, selectChat, is_selected }) {
import { XLg } from "react-bootstrap-icons";

export default function Ticket({ title, info, selectChat, is_selected, deleteHistory }) {

return (
<div
className={`ticket clickable${is_selected ? " selected":""}`}
onClick={()=>selectChat(info)}
>
{ title }
<XLg
className="delete-icon clickable"
onClick={evt=>{
evt.stopPropagation();
deleteHistory({ uid: info.uid, title })
}}
/>
</div>
)
}
9 changes: 5 additions & 4 deletions src/components/chat/Tickets.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import Ticket from "./Ticket";
import useIDB from "../../utils/idb";
import { genRandomID } from "../../utils/tools";

export default function Tickets({selectChat, current_chat, history, setHistory}) {
export default function Tickets({selectChat, current_chat, history, setHistory, deleteHistory}) {

const idb = useIDB();

async function syncHistory() {
const history = await idb.getAll('chat-history')
history.sort((a, b)=>b.updatedAt - a.updatedAt)
setHistory(history)
setHistory(history.map(e=>{return {...e, client: null}}))
}

async function startNewConversation() {
Expand All @@ -27,7 +27,7 @@ export default function Tickets({selectChat, current_chat, history, setHistory})
const new_conv_info = await idb.getByID('chat-history', conv_id);
new_conv_info &&
setHistory([
new_conv_info,
{...new_conv_info, client: null},
...history
])
selectChat(new_conv_info)
Expand All @@ -50,9 +50,10 @@ export default function Tickets({selectChat, current_chat, history, setHistory})
const { title, uid } = elem;
return (
<Ticket
key={`ticket-${title}-${uid}`}
key={`ticket-${uid}`}
title={title} info={elem}
selectChat={selectChat}
deleteHistory={deleteHistory}
is_selected={current_chat.uid && uid === current_chat.uid}
/>
)
Expand Down
71 changes: 67 additions & 4 deletions src/components/chat/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import Tickets from "./Tickets";
import Conversation from "./Conversation";
import useIDB from "../../utils/idb";
Expand All @@ -8,6 +8,9 @@ export default function Chat() {
const [chat, selectChat] = useState({});
const [history, setHistory] = useState([]);
const idb = useIDB();
const dialogRef = useRef(null);
const [showConfirm, toggleConfirm] = useState(false);
const [conv_to_delete, requestDelete] = useState(null);

function updateChatClient(client) {
selectChat({
Expand All @@ -19,14 +22,74 @@ export default function Chat() {
history_cp.findIndex(e=>e.uid === chat.uid)
].client = client;
setHistory(history_cp);
}

function resetRequestDelete() {
requestDelete(null);
toggleConfirm(false);
}

async function deleteHistory() {
if(!conv_to_delete) return;

const {uid} = conv_to_delete;
await idb.deleteOne("chat-history", [{uid}]);
await idb.deleteAll("messages", [{'history-uid': uid}]);
setHistory(history.filter(e=>e.uid !== uid));
uid === chat.uid && selectChat({});
resetRequestDelete();
}

async function updateTitle(title) {
await idb.updateOne("chat-history", {title}, [{uid: chat.uid}])

selectChat({
...chat, title: title
})

idb.updateOne('chat-history', {client}, [{uid:chat.uid}])
let history_cp = [...history];
history_cp[
history_cp.findIndex(e=>e.uid === chat.uid)
].title = title;
setHistory(history_cp);
}

useEffect(()=>{
if(dialogRef.current) {
if(showConfirm) dialogRef.current.showModal();
else dialogRef.current.close();
}
}, [showConfirm])

useEffect(()=>{
conv_to_delete && toggleConfirm(true);
}, [conv_to_delete])

return (
<div className="chat">
<Tickets selectChat={selectChat} setHistory={setHistory} history={history} current_chat={chat} />
<Conversation uid={chat.uid} client={chat.client} updateClient={updateChatClient} />
<Tickets
selectChat={selectChat} current_chat={chat}
setHistory={setHistory} history={history}
deleteHistory={requestDelete}
/>
<Conversation
uid={chat.uid}
title={chat.title} updateTitle={updateTitle}
client={chat.client} updateClient={updateChatClient}
/>
<dialog ref={dialogRef}>
<div>
Delete <strong>{conv_to_delete && conv_to_delete.title}</strong>?
</div>
<div
className="button clickable"
onClick={deleteHistory}
>Yes, Delete</div>
<div
className="button clickable"
onClick={resetRequestDelete}
>No, Go Back</div>
</dialog>
</div>
)
}
Loading

0 comments on commit 345c309

Please sign in to comment.