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 (
{history()}
); } 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} }