Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

add upload file function, not tested #22

Merged
merged 1 commit into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 59 additions & 12 deletions src/components/chat/Conversation.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from "react";
import ConversationBubble from "./ConversationBubble";
import { Send, StopCircleFill } from 'react-bootstrap-icons';
import { FileImageFill, FileTextFill, Paperclip, Send, StopCircleFill } from 'react-bootstrap-icons';
import useIDB from "../../utils/idb";
import { isModelLoaded, loadModel } from '../../utils/workers/worker'
import { getCompletionFunctions } from "../../utils/workers";
Expand All @@ -11,6 +11,7 @@ export default function Conversation({ uid }) {
const [message, setMessage] = useState('');
const [pending_message, setPendingMessage] = useState('');
const [hide_pending, setHidePending] = useState(true);
const [upload_file, setUploadFile] = useState(null);
const chat_functions = useRef(getCompletionFunctions());
const idb = useIDB();

Expand Down Expand Up @@ -45,7 +46,6 @@ export default function Conversation({ uid }) {
const user_msg = {role: 'user', content: message}
setConversation([...conversation, user_msg])
setMessage('');
setHidePending(false);

function cb(text, isFinished) {
if(!isFinished) {
Expand All @@ -69,12 +69,41 @@ export default function Conversation({ uid }) {
}
}

if(chat_functions.current.type === "Wllama") {
let messages = [];
setHidePending(false);

if(chat_functions.current.platform === "Wllama") {
if(!isModelLoaded()) {
await loadModel();
await loadModel('completion', (progress)=>{
setPendingMessage(
typeof progress === 'number' ?
`**Downloading model, ${progress}% completed**` :
'**Loading model...**'
)
});
setPendingMessage('')
}
messages = [user_msg];
} else {
let user_message = user_msg;
if(upload_file) {
const is_img = upload_file.type.startsWith('image')
const file_obj = {
content: new Uint8Array(await upload_file.arrayBuffer())
}
if(!is_img) file_obj.name = upload_file.name;
user_message[
is_img ? 'image' : 'document'
] = file_obj;
setUploadFile(null);
}
messages = [
...conversation,
user_message
]
}
const result = await chat_functions.current.completions([user_msg], cb);

const result = await chat_functions.current.completions(messages, cb);
if(!result) {
setPendingMessage('');
setHidePending(true);
Expand All @@ -85,6 +114,7 @@ export default function Conversation({ uid }) {

useEffect(()=>{
uid && getConversationByUid();
setUploadFile(null);
// eslint-disable-next-line
}, [uid]);

Expand Down Expand Up @@ -115,14 +145,31 @@ export default function Conversation({ uid }) {
/>
</div>
<form className="send-message-form" onSubmit={sendMessage}>
<input type="text" value={message} onChange={messageOnChange}/>
<div className="send-message-button-container">
{
hide_pending ?
<Send className="button-icon" /> :
<StopCircleFill className="button-icon stop clickable" onClick={chat_functions.current.abort} />
<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='submit' className={`clickable${!hide_pending?" disabled":''}`}/>
<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>
</div>
</form>
</> :
Expand Down
67 changes: 47 additions & 20 deletions src/styles/chat.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@
position: absolute;
bottom: 0;
left: 0;
display: block;
display: flex;
align-items: center;
overflow: hidden;
background-color: var(--normal-bg-color);
}
Expand Down Expand Up @@ -186,41 +187,62 @@
animation-delay: .8s;
}

.chat > .conversation-main > .send-message-form >
input[type="text"] {
.chat > .conversation-main > .send-message-form > .input-container {
width: calc(100% - 20px);
margin-left: 10px;
margin-top: 10px;
position: relative;
height: var(--elem-size);
border: 1px solid lightgray;
position: relative;
margin: auto;
border: 2px solid lightgray;
overflow: hidden;
border-radius: 30px;
}

.chat > .conversation-main > .send-message-form > .input-container >
input[type="text"] {
width: 100%;
height: 100%;
position: relative;
border: none;
padding: 0px var(--elem-size) 0px 10px;
}

.chat > .conversation-main > .send-message-form >
.chat > .conversation-main > .send-message-form > .input-container >
input[type="text"]:focus {
outline: none;
}


.chat > .conversation-main > .send-message-form >
.send-message-button-container {
.chat > .conversation-main > .send-message-form > .input-container >
.button-container {
position: absolute;
width: var(--elem-size);
height: var(--elem-size);
right: 10px;
top: 10px;
right: 0;
top: 0;
display: flex;
z-index: 1;
background-color: rgba(230, 230, 230, 0.6);
}
.chat > .conversation-main > .send-message-form > .input-container >
.button-container.file-upload {
right: unset;
left: 0;
}

.chat > .conversation-main > .send-message-form >
.send-message-button-container:hover > .button-icon:not(.stop) {
.chat > .conversation-main >
.send-message-form > .input-container:has(>.button-container.file-upload) >
input[type="text"] {
padding-left: calc(5px + var(--elem-size));
}


.chat > .conversation-main > .send-message-form > .input-container >
.button-container:hover > .button-icon.animated {
transform: rotate(45deg);
}

.chat > .conversation-main > .send-message-form >
.send-message-button-container > input[type="submit"]{
.chat > .conversation-main > .send-message-form > .input-container >
.button-container > input{
width: 100%;
height: 100%;
position: absolute;
Expand All @@ -229,17 +251,22 @@ input[type="text"]:focus {
z-index: 2;
opacity: 0;
}
.chat > .conversation-main > .send-message-form >
.send-message-button-container > input[type="submit"].disabled {
.chat > .conversation-main > .send-message-form > .input-container >
.button-container > input.disabled {
pointer-events: none;
}

.chat > .conversation-main > .send-message-form >
.send-message-button-container > .button-icon {
.chat > .conversation-main > .send-message-form > .input-container >
.button-container > .button-icon {
--size: 40%;
width: var(--size);
height: var(--size);
margin: auto;
color: rgb(90, 90, 90);
transition-duration: .3s;
}

.chat > .conversation-main > .send-message-form > .input-container >
.button-container > .button-icon.highlight {
color: dodgerblue;
}
8 changes: 6 additions & 2 deletions src/utils/workers/aws-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ let abort_signal = false;
/**
* @typedef ImageMessage
* @property {"png" | "jpeg" | "gif" | "webp"} format Format of image
* @property {Buffer} content Content in bytes
* @property {Uint8Array} content Content in bytes
*/

/**
Expand Down Expand Up @@ -95,7 +95,11 @@ let abort_signal = false;
*/
export async function chatCompletions(messages, cb = null) {
const { aws_model_id } = getPlatformSettings();
if(!aws_model_id || (!bedrock_client && !await initBedrockClient())) return null;
if(!aws_model_id || (!bedrock_client && !await initBedrockClient())) {
console.log('no bedrock')
cb && cb("**Cannot Initialize AWS Bedrock Client**", true)
return null;
}

const system = [];
const normal_messages = [];
Expand Down
6 changes: 3 additions & 3 deletions src/utils/workers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { chatCompletions as AwsCompletions, abortCompletion as AwsAbort } from "
* @typedef CompletionFunctions
* @property {Function} completions
* @property {Function} abort
* @property {"Wllama" | "AWS"} type
* @property {"Wllama" | "AWS"} platform
*/

/**
Expand All @@ -18,9 +18,9 @@ export function getCompletionFunctions() {

switch(platform_settings.enabled_platform ) {
case 'AWS':
return { completions: AwsCompletions, abort: AwsAbort, type: "AWS" }
return { completions: AwsCompletions, abort: AwsAbort, platform: "AWS" }
default:
return { completions: WllamaCompletions, abort: WllamaAbort, type: "Wllama" }
return { completions: WllamaCompletions, abort: WllamaAbort, platform: "Wllama" }
}

}
7 changes: 6 additions & 1 deletion src/utils/workers/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,16 @@ export async function deleteModel(type = 'completion') {
await instance.cacheManager.delete(cacheKey);
}

export async function loadModel(type = 'completion') {
export async function loadModel(type = 'completion', cb = null) {
// check if model already in cache
const { instance, model_src } = engines[type];

try {
// if not downloaded, download first
if(!await isModelDownloaded(type)) {
await downloadModel(type, cb);
}
cb && cb('loading')
await instance.loadModelFromUrl(model_src, {
n_threads: 6,
n_ctx: 4096,
Expand Down