Skip to content

Commit

Permalink
Deploy @nestia/chat on the website
Browse files Browse the repository at this point in the history
  • Loading branch information
samchon committed Jan 22, 2025
1 parent ad77566 commit 45edad9
Show file tree
Hide file tree
Showing 24 changed files with 582 additions and 286 deletions.
6 changes: 3 additions & 3 deletions packages/chat/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
name="viewport"
content="width=device-width, height=device-height, initial-scale=1, minimum-scale=1.0, maximum-scale=3.0s"
/>
<title>Nestia A.I. Chatbot</title>
<title>Nestia A.I. Chatbot Uploader</title>
</head>
<body style="width: 100%; height: 100%; margin: 0px; overflow: hidden;">
<div id="root" style="width: 100%; height: 100%"></div>
<script type="module" src="src/main.tsx"></script>
<script type="module" src="./src/main.tsx"></script>
</body>
</html>
</html>;
11 changes: 10 additions & 1 deletion packages/chat/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/chat",
"version": "0.2.4",
"version": "0.3.1",
"type": "module",
"main": "./lib/index.mjs",
"typings": "./lib/index.d.ts",
Expand Down Expand Up @@ -35,12 +35,19 @@
},
"homepage": "https://nestia.io",
"dependencies": {
"@mui/icons-material": "^6.4.1",
"@mui/material": "^6.4.0",
"@nestia/agent": "^0.3.7",
"@samchon/openapi": "^2.4.0",
"html2canvas": "^1.4.1",
"js-file-download": "^0.4.12",
"js-yaml": "^4.1.0",
"react-json-editor-ajrm": "^2.5.14",
"react-markdown": "^9.0.3",
"react-mui-fileuploader": "^0.5.2",
"rehype-raw": "^7.0.0",
"rehype-stringify": "^10.0.1",
"remark-mermaid-plugin": "^1.0.2",
"typia": "^7.6.0"
},
"devDependencies": {
Expand All @@ -56,6 +63,7 @@
"@types/node": "^22.10.5",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/react-json-editor-ajrm": "^2.5.6",
"@vitejs/plugin-react": "^4.3.3",
"eslint": "^9.13.0",
"eslint-plugin-react-hooks": "^5.0.0",
Expand All @@ -78,6 +86,7 @@
"dist",
"lib",
"src",
"!src/main.ts",
"!src/shopping.ts"
]
}
14 changes: 14 additions & 0 deletions packages/chat/shopping/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, height=device-height, initial-scale=1, minimum-scale=1.0, maximum-scale=3.0s"
/>
<title>Nestia A.I. Chatbot (Shopping Mall Demonstration)</title>
</head>
<body style="width: 100%; height: 100%; margin: 0px; overflow: hidden;">
<div id="root" style="width: 100%; height: 100%"></div>
<script type="module" src="../src/shopping.tsx"></script>
</body>
</html>;=
158 changes: 158 additions & 0 deletions packages/chat/src/NestiaChatUploader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import {
Button,
FormControl,
FormControlLabel,
FormLabel,
Radio,
RadioGroup,
TextField,
Typography,
} from "@mui/material";
import { INestiaAgentProvider, NestiaAgent } from "@nestia/agent";
import { IHttpLlmApplication } from "@samchon/openapi";
import OpenAI from "openai";
import React, { useState } from "react";
import JsonInput from "react-json-editor-ajrm";
// @ts-ignore
import locale from "react-json-editor-ajrm/locale/en.js";

import { NestiaChatApplication } from "./NestiaChatApplication";
import { NestiaChatFileUploadMovie } from "./movies/uploader/NestiaChatFileUploadMovie";

export const NestiaChatUploader = (props: NestiaChatUploader.IProps) => {
// PARAMETERS
const [host, setHost] = useState("http://localhost:37001");
const [headers, setHeaders] = useState<Record<string, string>>({
Authorization: "YOUR_SERVER_API_KEY",
});
const [model, setModel] = useState("gpt-4o");
const [apiKey, setApiKey] = useState("YOUR-OPENAI-API-KEY");

// RESULT
const [application, setApplication] =
useState<IHttpLlmApplication<"chatgpt"> | null>(null);
const [progress, setProgress] = useState(false);

// HANDLERS
const handleApplication = (
application: IHttpLlmApplication<"chatgpt"> | null,
error: string | null,
) => {
setApplication(application);
if (error !== null) handleError(error);
};
const handleError = (error: string) => {
if (props.onError) props.onError(error);
else alert(error);
};

const open = async () => {
if (application === null) return;
setProgress(true);
try {
const provider: INestiaAgentProvider = {
type: "chatgpt",
api: new OpenAI({
apiKey,
dangerouslyAllowBrowser: true,
}),
model: model as "gpt-4o",
};
const agent: NestiaAgent = new NestiaAgent({
provider,
controllers: [
{
protocol: "http",
name: "main",
application,
connection: {
host,
headers,
},
},
],
});
props.onSuccess(<NestiaChatApplication agent={agent} />);
} catch (error) {
handleError(error instanceof Error ? error.message : "unknown error");
setProgress(false);
}
};

return (
<React.Fragment>
<NestiaChatFileUploadMovie onChange={handleApplication} />
<br />
<FormControl fullWidth style={{ paddingLeft: 15 }}>
<Typography variant="h6">HTTP Connection</Typography>
<br />
<TextField
onChange={(e) => setHost(e.target.value)}
defaultValue={host}
label="Host URL"
variant="outlined"
/>
<br />
<FormLabel> Headers </FormLabel>
<JsonInput
locale={locale}
theme="dark_vscode_tribute"
placeholder={headers}
onChange={setHeaders}
height="100px"
/>
<br />
<br />
<Typography variant="h6">LLM Arguments</Typography>
<br />
<FormLabel> LLM Provider </FormLabel>
<RadioGroup defaultValue={"chatgpt"} style={{ paddingLeft: 15 }}>
<FormControlLabel
value="chatgpt"
control={<Radio />}
label="OpenAI (ChatGPT)"
/>
</RadioGroup>
<FormLabel style={{ paddingTop: 20 }}> OpenAI Model </FormLabel>
<RadioGroup
defaultValue={model}
onChange={(_e, value) => setModel(value)}
style={{ paddingLeft: 15 }}
>
<FormControlLabel value="gpt-4o" control={<Radio />} label="GPT-4o" />
<FormControlLabel
value="gpt-4o-mini"
control={<Radio />}
label="GPT-4o Mini"
/>
</RadioGroup>
<br />
<TextField
onChange={(e) => setApiKey(e.target.value)}
defaultValue={apiKey}
label="OpenAI API Key"
variant="outlined"
/>
</FormControl>
<br />
<br />
<Button
component="a"
fullWidth
variant="contained"
color={"info"}
size="large"
disabled={progress === true || document === null}
onClick={() => open()}
>
{progress ? "Generating..." : "Generate Editor"}
</Button>
</React.Fragment>
);
};
export namespace NestiaChatUploader {
export interface IProps {
onError?: (error: string) => void;
onSuccess: (element: JSX.Element) => void;
}
}
5 changes: 5 additions & 0 deletions packages/chat/src/components/MarkdownViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import Markdown from "react-markdown";
import rehypeRaw from "rehype-raw";
import rehypeStringify from "rehype-stringify";
import remarkMermaidPlugin from "remark-mermaid-plugin";

export const MarkdownViewer = (props: MarkdownViewer.IProps) => {
return (
<Markdown
remarkPlugins={[remarkMermaidPlugin as any]}
rehypePlugins={[rehypeRaw, rehypeStringify]}
components={{
img: ({ ...props }) => (
<img
Expand Down
1 change: 1 addition & 0 deletions packages/chat/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./NestiaChatApplication";
export * from "./NestiaChatUploader";
104 changes: 8 additions & 96 deletions packages/chat/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,11 @@
import { NestiaAgent } from "@nestia/agent";
import {
HttpLlm,
IHttpConnection,
IHttpLlmApplication,
OpenApi,
OpenApiV3,
OpenApiV3_1,
SwaggerV2,
} from "@samchon/openapi";
import ShoppingApi from "@samchon/shopping-api";
import OpenAI from "openai";
import { createRoot } from "react-dom/client";

import { NestiaChatApplication } from "./NestiaChatApplication";
import { NestiaChatUploader } from "./NestiaChatUploader";

interface IQuery {
server?: "local" | "real";
}

const main = async (): Promise<void> => {
const query: IQuery = ((): IQuery => {
const index: number = window.location.href.indexOf("?");
if (index === -1) return {};
const search = new URLSearchParams(window.location.href.substr(index + 1));
return {
server: search.get("server") as "local" | "real" | undefined,
};
})();

// COMPOSE LLM APPLICATION SCHEMA
const swagger:
| SwaggerV2.IDocument
| OpenApiV3.IDocument
| OpenApiV3_1.IDocument = await fetch(
query.server === "local"
? "http://127.0.0.1:37001/editor/swagger.json"
: "https://raw.githubusercontent.com/samchon/shopping-backend/refs/heads/master/packages/api/customer.swagger.json",
).then((r) => r.json());
const document: OpenApi.IDocument = OpenApi.convert(swagger);
const application: IHttpLlmApplication<"chatgpt"> = HttpLlm.application({
model: "chatgpt",
document,
});
application.functions = application.functions.filter((func) =>
func.path.startsWith("/shoppings/customers"),
);

// HANDSHAKE WITH SHOPPING BACKEND
const connection: IHttpConnection = {
host:
query.server === "local"
? "http://127.0.0.1:37001"
: "https://shopping-be.wrtn.ai",
};
await ShoppingApi.functional.shoppings.customers.authenticate.create(
connection,
{
channel_code: "samchon",
external_user: null,
href: "https://127.0.0.1/NodeJS",
referrer: null,
},
);
await ShoppingApi.functional.shoppings.customers.authenticate.activate(
connection,
{
mobile: "821012345678",
name: "John Doe",
},
);

// COMPOSE CHAT AGENT
const agent: NestiaAgent = new NestiaAgent({
provider: {
type: "chatgpt",
api: new OpenAI({
apiKey: import.meta.env.VITE_CHATGPT_API_KEY,
dangerouslyAllowBrowser: true,
}),
model: "gpt-4o-mini",
},
controllers: [
{
protocol: "http",
name: "shopping",
application,
connection,
},
],
config: {
locale: "en-US",
},
});
createRoot(window.document.getElementById("root")!).render(
<NestiaChatApplication agent={agent} />,
);
};
main().catch(console.error);
createRoot(window.document.getElementById("root")!).render(
<NestiaChatUploader
onSuccess={(element) => {
createRoot(window.document.getElementById("root")!).render(element);
}}
/>,
);
11 changes: 9 additions & 2 deletions packages/chat/src/movies/NestiaChatMovie.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import AddAPhotoIcon from "@mui/icons-material/AddAPhoto";
import SendIcon from "@mui/icons-material/Send";
import {
AppBar,
Button,
Expand Down Expand Up @@ -155,8 +157,12 @@ export const NestiaChatMovie = ({ agent }: NestiaChatMovie.IProps) => {
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Nestia A.I. Chatbot
</Typography>
<Button color="inherit" onClick={capture}>
Capture
<Button
color="inherit"
startIcon={<AddAPhotoIcon />}
onClick={capture}
>
Screenshot Capture
</Button>
</Toolbar>
</AppBar>
Expand Down Expand Up @@ -229,6 +235,7 @@ export const NestiaChatMovie = ({ agent }: NestiaChatMovie.IProps) => {
<Button
variant="contained"
style={{ marginLeft: 10 }}
startIcon={<SendIcon />}
disabled={!enabled}
onClick={conversate}
>
Expand Down
Loading

0 comments on commit 45edad9

Please sign in to comment.