Skip to content

Commit

Permalink
create agent related changes
Browse files Browse the repository at this point in the history
  • Loading branch information
b4s36t4 committed Jun 24, 2024
1 parent fb0966c commit ae30f75
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 190 deletions.
36 changes: 36 additions & 0 deletions src/agents/BaseInstance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { ChatCompletionChunk } from "@mlc-ai/web-llm";
import { type SearchResult } from "voy-search";

abstract class ModelInstance {
name: string;
cacheName: string;

constructor(name: string, cacheName: string) {
this.name = name;
this.cacheName = cacheName;
}

getInstance(): ModelInstance {
return this;
}

abstract addIndex(data: string | string[]): void;

abstract search(question: number[], count?: number): SearchResult;

abstract saveToCache(): void;

abstract loadCache(): void;

async ask(
question: string,
context: string,
callback?: (chunk: ChatCompletionChunk) => void
): Promise<void> {
throw new Error("Not Implemented");
}

abstract loadFile(file: File): void;
}

export { ModelInstance };
129 changes: 129 additions & 0 deletions src/agents/ChatPDF.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import type { SearchResult } from "voy-search";
import { ModelInstance } from "./BaseInstance";
import { WorkerPostTypes } from "../const";
import { WebPDFLoader } from "@langchain/community/document_loaders/web/pdf";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import * as pdfjs from "pdfjs-dist";
import type { ChatCompletionChunk, MLCEngine } from "@mlc-ai/web-llm";

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.mjs`;

class ChatPDF extends ModelInstance {
static instance: ModelInstance | null = null;
model: MLCEngine | null = null;
splitter: RecursiveCharacterTextSplitter | null = null;
// loader: WebPDFLoader | null = null
worker: Worker | null = null;

constructor(model: MLCEngine, worker: Worker) {
super("chat_pdf", "chat_pdf");
this.model = model;
this.splitter = new RecursiveCharacterTextSplitter({
chunkSize: 500,
chunkOverlap: 12,
});
this.worker = worker;

this.worker.addEventListener("message", (event) => {
const { type } = event.data;

if (type === WorkerPostTypes.embedDocument) {
console.log("[Embed] Document embed created");
}

return;
});
}

static getInstance(model: MLCEngine, worker: Worker): ModelInstance {
if (this.instance) {
return this.instance;
}
this.instance = new ChatPDF(model, worker);
return this.instance;
}

addIndex(data: string | string[]): void {
// throw new Error("Method not implemented.");
self.postMessage({
type: WorkerPostTypes.embedQuestion,
data,
id: crypto.randomUUID(),
});
}

async ask(
question: string,
context: string,
callback: (chunk: ChatCompletionChunk) => void
) {
const prompt = `
Give the context below answer my questions.
context: ${context}
question: ${question}
`;

const stream = await this.model?.chat.completions.create({
messages: [{ role: "user", content: prompt }],
stream: true,
stream_options: { include_usage: true },
temperature: 0.2,
seed: 10000,
});

if (!stream) {
return;
}

for await (const chunk of stream) {
callback(chunk);
}
}

async loadFile(file: File, callback: (event: any) => void) {
const loader = new WebPDFLoader(file, {
pdfjs: async () => ({
getDocument: pdfjs.getDocument,
version: pdfjs.version,
}),
});

const documents = await loader.load();
const splits = await this.splitter?.splitDocuments(documents);

if (!splits) {
return;
}

splits.forEach((document, i) => {
console.log(
"%c[INDEX]",
"background-color: red;padding:2px;color: white",
"Indexing document",
i
);
this.worker?.postMessage({
data: document.pageContent,
id: crypto.randomUUID(),
type: WorkerPostTypes.embedDocument,
});
});
}

search(question: number[], count?: number): SearchResult {
return window.db.search(question as any, count || 5);
}

saveToCache(): void {
const serailizedJSON = window.db.serialize();
}
loadCache(): void {
throw new Error("Method not implemented.");
}
}

export { ChatPDF };
4 changes: 2 additions & 2 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { NavLink } from "./NavLink.tsx";
const props = {
home: { href: "/", title: "Home" },
history: { href: "/history", title: "History" },
agents: { href: "/agents", title: "Agents" },
models: { href: "/models", title: "Models" },
settings: { href: "/settings", title: "Settings" },
};
Expand All @@ -14,7 +14,7 @@ const props = {
class="flex text-white gap-10 min-w-[400px] min-h-[60px] justify-center bg-slate-900 w-max mx-auto p-4 rounded-lg px-8"
>
<NavLink {...props["home"]} client:only />
<NavLink {...props["history"]} client:only />
<NavLink {...props["agents"]} client:only />
<NavLink {...props["models"]} client:only />
<NavLink {...props["settings"]} client:only />
</ul>
Expand Down
1 change: 0 additions & 1 deletion src/components/RenderMessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ export const RenderMessageList = () => {
useEffect(() => {
chatStore.subscribe((store, prevStore) => {
setMessages(store.messages);
console.log(store.messages.at(-1), "final message");
messageContainerRef.current?.scrollTo({
left: 0,
top: messageContainerRef.current?.scrollHeight ?? 500,
Expand Down
5 changes: 5 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum WorkerPostTypes {
embedDocument = "EMBED_DOCUMENT",
embedQuestion = "EMBED_QUESTION",
initiate = "INITIATE",
}
1 change: 1 addition & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
declare interface Window {
MODEL_LOADED: boolean;
CHAT_PDF: boolean;
db: import("voy-search").Voy;
}
88 changes: 88 additions & 0 deletions src/pages/agents.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
import { Eye, FileText, RefreshCcw } from "lucide-react";
import Layout from "../layouts/Layout.astro";
const agents = [
{
title: "ChatPDF",
description: "Chat with your PDF, ask questions summarize anything.",
id: "chat_pdf",
embedding_json: "chat_pdf.json",
icon: FileText,
},
];
---

<Layout title="Agents">
<main class="mx-auto mt-6 md:w-[60%]">
<h3 class="text-2xl font-semibold underline">Agents</h3>

<div class="mt-4 flex justify-between items-center flex-wrap">
{
agents.map((agent) => {
return (
<div class="w-[250px] border-2 rounded-lg border-blue-200 p-2">
<agent.icon className="my-4" size={40} />
<h2 class="text-lg font-bold">{agent.title}</h2>
<h4 class="text-base text-gray-600">{agent.description}</h4>
<div class="my-2 flex space-x-2">
<span title="View loaded/saved documents">
<Eye />
</span>
<span title="Reload the data">
<RefreshCcw />
</span>
</div>
<button
data-agent-id={agent.id}
class="w-full bg-black text-white mt-4 rounded-md p-1 agentEnable"
>
Enable
</button>
</div>
);
})
}
</div>
</main>
</Layout>

<script>
import { agentStore } from "../store/agent";
import { ModelInstance } from "../agents/BaseInstance";
import { ChatPDF } from "../agents/ChatPDF";
const { selectedAgentId, updateAgent } = agentStore.getState();

const buttons = document.querySelectorAll(
".agentEnable"
) as NodeListOf<HTMLButtonElement>;

buttons.forEach((button) => {
const agentId = button.dataset["agentId"];

agentStore.subscribe(({ selectedAgentId }) => {
if (selectedAgentId === agentId) {
button.innerText = "Disable";
return;
}

button.innerText = "Enable";
});

if (agentId === selectedAgentId) {
button.innerText = "Disable";
}
button.addEventListener("click", function () {
if (!agentId) {
return;
}

if (agentId === agentStore.getState().selectedAgentId) {
updateAgent(null, null);
return;
}

updateAgent(agentId, null);
});
});
</script>
9 changes: 0 additions & 9 deletions src/pages/history.astro

This file was deleted.

Loading

0 comments on commit ae30f75

Please sign in to comment.