import React, { useState, useRef, useEffect } from 'react';
import './App.css';
import { Ollama } from "langchain/llms/ollama";
import { v4 as uuidv4 } from 'uuid';
import { FluentProvider, teamsDarkTheme, Button } from '@fluentui/react-components';
import {
Body1,
Caption1,
} from "@fluentui/react-components";
import {
makeStyles,
shorthands,
Input,
Label,
useId,
Field,
RadioGroup,
Radio,
Spinner,
Avatar,
Toaster,
useToastController,
Toast,
ToastBody, ToastTitle,
ToastIntent, Card, CardHeader, CardPreview
} from "@fluentui/react-components";
const createUniqueId = (): string => {
return uuidv4()
}
interface RequestResponse {date: Date, query: string, response: string[]}
function App() {
const toasterId = useId("toaster");
const { dispatchToast } = useToastController(toasterId);
const error = (data: string) => dispatchToast(
Error
{data}
,
{ intent: "error" }
);
const ollamaClient = OllamaClient()
const {sendMessage, clearHistory, chatHistory, deleteId} = OllamaApi(ollamaClient)
function onSubmit(e: any) {
e.preventDefault();
const form = e.target;
// if (form.checkValidity() === false) {
// return;
// }
if(sendMessage === undefined) error("!")
sendMessage?.(form.prompt.value as string)
}
function clear() {
clearHistory?.()
}
// @ts-ignore
const deleteRow = (id: string) => deleteId?.(id)
const history = () => {
return Object.keys(chatHistory ?? {})
.map(key => ({key, value: chatHistory?.[key]}))
.sort((a: {key: string, value: RequestResponse | undefined}, b: {key: string, value: RequestResponse | undefined}) => (b.value?.date.getTime() ?? 0) - (a.value?.date.getTime() ?? 0))
.map(({key, value}: {key: string, value: RequestResponse | undefined}) => {
return (<>
{value?.query}
>}
description={{key}}
/>
{value?.response.join("")}
>
)
})
}
const ModelPicker = () =>
Model picker
const Prompt = () =>{
return (
)
}
return (
);
}
export default App;
const OllamaClient = () => {
// setup client
// TODO: Variable model
// TODO: Variable URL
const [state, setState] = useState(undefined);
useEffect(() => {
setState(new Ollama({
baseUrl: "http://localhost:11434",
model: "mistral-openorca"
}))
}, []);
return state
}
const OllamaApi = (client: Ollama | undefined) => {
const [chatHistory, setChatHistory] = useState<{[key: string]: RequestResponse}>({});
if(!client) return {}
const clearHistory = () => setChatHistory({})
const appendChunk = (chunk: string, id: string) => {
setChatHistory(previous => {
if(!previous[id]) return previous
const previousForId = {...previous[id]}
if(previousForId === undefined) throw new Error("State should never be empty when applying chunks")
previousForId.response = [...previousForId.response, chunk]
const newState = {...previous}
newState[id] = previousForId
return newState
})
}
const createRequestState = (query: string) => {
const request: RequestResponse = {
date: new Date(),
query,
response: [] as string[]
}
const id = createUniqueId()
setChatHistory(previous => {
const p = {...previous}
p[id] = request
return p
})
return id
}
const sendMessage = (prompt: string) => {
const id = createRequestState(prompt)
client.stream(prompt).then(rb => {
const reader = rb.getReader()
return new ReadableStream({
start(controller) {
// The following function handles each data chunk
function push() {
// "done" is a Boolean and value a "Uint8Array"
reader.read().then(({ done, value }) => {
// If there is no more data to read
if (done) {
console.log("done", done);
// controller.close();
return;
}
// Get the data and send it to the browser via the controller
controller.enqueue(value);
// Check chunks by logging to the console
appendChunk(value, id)
// console.log(done, value);
push();
});
}
push();
},
});
})
}
const deleteId = (id: string) => setChatHistory(chatHistory => {
const newChatHistroy = {...chatHistory}
delete newChatHistroy[id]
return newChatHistroy
})
return {sendMessage, clearHistory, chatHistory, deleteId}
}