1
0
Fork 0

add client app

This commit is contained in:
Sean Sube 2024-05-04 15:36:55 -05:00
parent 8706badec5
commit bb7cdac04f
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
22 changed files with 4567 additions and 0 deletions

3
.gitignore vendored
View File

@ -2,3 +2,6 @@ adventure/custom_actions.py
worlds/
__pycache__/
.env
venv/
client/node_modules/
client/out/

View File

@ -0,0 +1,39 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"projectFolder": ".",
"mainEntryPointFilePath": "<projectFolder>/out/src/main.d.ts",
"apiReport": {
"enabled": true,
"reportFolder": "<projectFolder>/docs/",
"reportTempFolder": "<projectFolder>/out/tmp/"
},
"docModel": {
"enabled": true,
"apiJsonFilePath": "<projectFolder>/out/api/<unscopedPackageName>.api.json"
},
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "<projectFolder>/out/main.d.ts",
"betaTrimmedFilePath": "<projectFolder>/out/main-beta.d.ts",
"publicTrimmedFilePath": "<projectFolder>/out/main-public.d.ts"
},
"tsdocMetadata": {
},
"messages": {
"compilerMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"extractorMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
}
}
}
}

314
client/.eslintrc.json Normal file
View File

@ -0,0 +1,314 @@
{
"env": {
"es6": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json",
"sourceType": "module"
},
"plugins": [
"eslint-plugin-chai",
"eslint-plugin-chai-expect",
"eslint-plugin-chai-expect-keywords",
"eslint-plugin-import",
"eslint-plugin-mocha",
"eslint-plugin-no-null",
"eslint-plugin-sonarjs",
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/adjacent-overload-signatures": "error",
"@typescript-eslint/array-type": [
"error",
{
"default": "generic"
}
],
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"null": "Use 'undefined' instead of 'null'"
}
}
],
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/explicit-member-accessibility": [
"error",
{
"accessibility": "explicit",
"overrides": {
"constructors": "no-public"
}
}
],
"@typescript-eslint/indent": [
"error",
2,
{
"ObjectExpression": "first",
"FunctionDeclaration": {
"parameters": "first"
},
"FunctionExpression": {
"parameters": "first"
},
"SwitchCase": 1
}
],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/member-ordering": [
"error",
{
"default": [
"public-static-method",
"public-static-field",
"public-instance-field",
"protected-instance-field",
"public-constructor",
"public-instance-method",
"protected-instance-method"
]
}
],
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"no-param-reassign": "error",
"@typescript-eslint/no-parameter-properties": "error",
"@typescript-eslint/no-this-alias": "error",
"@typescript-eslint/no-unnecessary-type-arguments": "error",
"@typescript-eslint/no-use-before-declare": "off",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/restrict-plus-operands": "error",
"@typescript-eslint/semi": [
"error",
"always"
],
"space-in-parens": [
"error",
"never"
],
"@typescript-eslint/strict-boolean-expressions": "error",
"@typescript-eslint/triple-slash-reference": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error",
"@typescript-eslint/unified-signatures": "error",
"arrow-body-style": "error",
"arrow-parens": [
"error",
"always"
],
"camelcase": "error",
"complexity": [
"error",
{
"max": 12
}
],
"constructor-super": "error",
"curly": "error",
"default-case": "error",
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": [
"error",
"always"
],
"guard-for-in": "error",
"id-blacklist": [
"error",
"any",
"String",
"Boolean",
"Undefined"
],
"id-match": "error",
"import/no-default-export": "error",
"import/no-deprecated": "error",
"import/no-extraneous-dependencies": "off",
"import/no-internal-modules": "off",
"import/order": [
"error",
{
"groups": [
[
"builtin",
"external"
],
[
"index",
"parent",
"sibling",
"unknown"
]
]
}
],
"max-classes-per-file": [
"off",
1
],
"max-len": [
"error",
{
"code": 180
}
],
"max-lines": [
"error",
500
],
"new-parens": "error",
"no-bitwise": "off",
"no-caller": "error",
"no-cond-assign": "error",
"no-console": "error",
"no-debugger": "error",
"no-duplicate-case": "error",
"no-duplicate-imports": "error",
"no-empty": "error",
"no-eval": "error",
"no-extra-bind": "error",
"no-fallthrough": "off",
"no-invalid-this": "error",
"no-irregular-whitespace": "error",
"@typescript-eslint/no-magic-numbers": [
"error",
{
"ignore": [
0,
1,
2,
10
],
"ignoreEnums": true
}
],
"no-multiple-empty-lines": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-null/no-null": "error",
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
],
"no-redeclare": "off",
"no-restricted-syntax": [
"error",
"ForInStatement",
"WithStatement",
"MemberExpression[optional=true]",
"IfStatement[alternate.type='IfStatement']",
"UnaryExpression[operator='!']",
"BinaryExpression[operator='==='][right.value=true]"
],
"no-return-await": "error",
"no-sequences": "error",
"no-shadow": "off",
"@typescript-eslint/no-redeclare": [
"error"
],
"@typescript-eslint/no-shadow": [
"error"
],
"no-sparse-arrays": "error",
"no-template-curly-in-string": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef-init": "error",
"no-underscore-dangle": "error",
"no-unsafe-finally": "error",
"no-unused-expressions": "error",
"no-unused-labels": "error",
"no-useless-constructor": "error",
"no-var": "error",
"no-void": "error",
"max-params": [
"error",
4
],
"object-shorthand": "error",
"one-var": [
"error",
"never"
],
"prefer-const": "error",
"prefer-object-spread": "error",
"@typescript-eslint/prefer-readonly": "error",
"quote-props": [
"error",
"consistent-as-needed"
],
"radix": "error",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"asyncArrow": "always",
"named": "never"
}
],
"spaced-comment": [
"error",
"always",
{
"markers": [
"/"
]
}
],
"use-isnan": "error",
"valid-typeof": "off",
"sonarjs/max-switch-cases": "error",
"sonarjs/cognitive-complexity": "error",
"sonarjs/no-all-duplicated-branches": "error",
"sonarjs/no-collapsible-if": "error",
"sonarjs/no-collection-size-mischeck": "error",
"sonarjs/no-duplicate-string": "error",
"sonarjs/no-duplicated-branches": "error",
"sonarjs/no-element-overwrite": "error",
"sonarjs/no-identical-conditions": "error",
"sonarjs/no-identical-expressions": "error",
"sonarjs/no-identical-functions": "error",
"sonarjs/no-inverted-boolean-check": "error",
"sonarjs/no-redundant-boolean": "error",
"sonarjs/no-redundant-jump": "error",
"sonarjs/no-same-line-conditional": "error",
"sonarjs/no-useless-catch": "error",
"sonarjs/prefer-immediate-return": "error"
}
}

5
client/.mocharc.json Normal file
View File

@ -0,0 +1,5 @@
{
"reporter": "@mochajs/multi-reporter",
"ui": ["bdd"]
}

37
client/.npmignore Normal file
View File

@ -0,0 +1,37 @@
.awcache/
.github/
.nyc_output/
config/
deploy/
node_modules/
out/cache/
out/coverage/
out/coverage-*
out/docs/
out/test/
out/typings/
out/*.db
out/*.html
out/*.json
out/test-*
out/tmp/
scripts/
src/
temp/
test/
vendor/
.api-extractor.json
.codeclimate.yml
.dockerignore
.eslintrc.json
.mocharc.json
.reporters.json
Containerfile.*
Makefile
esbuild.js
serve.js
tsconfig.json
yarn-*

5
client/.reporters.json Normal file
View File

@ -0,0 +1,5 @@
{
"mocha-junit-reporter": true,
"spec": true
}

71
client/Makefile Normal file
View File

@ -0,0 +1,71 @@
.PHONY: build bundle ci clean docs docs-local lint package run test
# JS targets
node_modules: deps
ci: deps lint build-shebang test bundle
clean:
rm -rf node_modules/
rm -rf out/
deps:
yarn install
docs:
yarn api-extractor run -c .api-extractor.json
yarn api-documenter markdown -i out/api -o docs/api
docs-local:
yarn api-extractor run -c .api-extractor.json --local
yarn api-documenter markdown -i out/api -o docs/api
build: deps
yarn tsc
# cp -v src/components/main.css out/src/components/
build-shebang: build
sed -i '1s;^;#! /usr/bin/env node\n\n;g' $(shell pwd)/out/src/main.js
chmod ug+x out/src/main.js
bundle: build
node esbuild.js
# copy other files into output to make a complete UI
cp -v src/index.html out/
# cp -v src/config.json out/
# copy everything into the server's default path
# cp -v src/index.html ../api/gui/
# cp -v src/config.json ../api/gui/
# cp -v out/bundle/main.css ../api/gui/bundle/
# cp -v out/bundle/main.js ../api/gui/bundle/
COVER_OPTS := --all \
--exclude ".eslintrc.js" \
--exclude "docs/**" \
--exclude "out/bundle/**" \
--exclude "out/coverage/**" \
--exclude "vendor/**" \
--reporter=text-summary \
--reporter=lcov \
--reporter=cobertura \
--report-dir=out/coverage
MOCHA_OPTS := --async-only \
--check-leaks \
--forbid-only \
--recursive \
--require source-map-support/register \
--require out/test/setup.js \
--sort
lint: deps
yarn eslint src/ test/ --ext .ts,.tsx
test: build
MOCHA_FILE=out/test-results.xml yarn c8 $(COVER_OPTS) mocha $(MOCHA_OPTS) "out/**/Test*.js"
watch: deps
WATCH=TRUE make ci
serve:
node serve.js

75
client/esbuild.js Normal file
View File

@ -0,0 +1,75 @@
import { build, context } from 'esbuild';
import { join } from 'path';
import alias from 'esbuild-plugin-alias';
import { copy } from 'esbuild-plugin-copy';
function envTrue(key) {
const val = (process.env[key] || '').toLowerCase();
return val === '1' || val === 't' || val === 'true' || val === 'y' || val === 'yes';
}
const debug = envTrue('DEBUG');
const watch = envTrue('WATCH');
const root = process.cwd();
const plugins = [];
if (debug) {
plugins.push(alias({
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}));
}
const config = {
bundle: true,
define: {
global: 'window',
},
entryPoints: [
join(root, 'out/src/main.js'),
],
keepNames: true,
outdir: 'out/bundle/',
platform: 'browser',
plugins,
sourcemap: true,
};
if (watch) {
const copyArray = (files) => files.map(file =>
copy({
resolveFrom: 'cwd',
assets: {
from: [file],
to: ['out/'],
},
})
);
const ctx = await context({
...config,
entryPoints: [
join(root, 'src/main.tsx'),
],
plugins: [
...plugins,
...copyArray(['src/index.html']),
],
banner: {
js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`,
},
});
await ctx.watch();
const { host, port } = await ctx.serve({
host: '0.0.0.0',
port: 8000,
servedir: 'out/',
});
console.log(`Serving on http://${host}:${port}`);
} else {
build(config).catch(() => process.exit(1));
}

64
client/package.json Normal file
View File

@ -0,0 +1,64 @@
{
"name": "@apextoaster/onnx-web",
"version": "0.12.0",
"description": "onnx web gui",
"type": "module",
"main": "out/src/main.js",
"author": "ssube",
"license": "MIT",
"dependencies": {
"@apextoaster/js-utils": "^0.5.0",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.16",
"@mui/lab": "^5.0.0-alpha.170",
"@mui/material": "^5.15.16",
"@mui/x-tree-view": "^7.3.1",
"@types/lodash": "^4.14.192",
"@types/node": "^20.11.0",
"browser-bunyan": "^1.8.0",
"i18next": "^22.4.14",
"i18next-browser-languagedetector": "^7.0.1",
"lodash": "^4.17.21",
"noicejs": "^5.0.0-3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^12.2.0",
"react-use": "^17.4.3",
"react-use-websocket": "^4.8.1",
"tslib": "^2.6.2"
},
"devDependencies": {
"@mochajs/multi-reporter": "^1.1.0",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^10.0.1",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.0.10",
"@types/sinon-chai": "^3.2.9",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"c8": "^7.13.0",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"esbuild": "^0.17.17",
"esbuild-plugin-alias": "^0.2.1",
"esbuild-plugin-copy": "^2.1.0",
"eslint": "^8.38.0",
"eslint-plugin-chai": "^0.0.1",
"eslint-plugin-chai-expect": "^3.0.0",
"eslint-plugin-chai-expect-keywords": "^2.1.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-mocha": "^10.1.0",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-sonarjs": "^0.19.0",
"mocha": "^10.2.0",
"mocha-junit-reporter": "^2.2.0",
"sinon": "^15.0.4",
"sinon-chai": "^3.7.0",
"source-map-support": "^0.5.21",
"typescript": "^4.9.5"
},
"resolutions": {
"@types/react": "18.3.1"
}
}

47
client/serve.js Normal file
View File

@ -0,0 +1,47 @@
import { mustDefault } from '@apextoaster/js-utils';
import { readFile } from 'fs';
import { createServer } from 'http';
import { join } from 'path';
const { env } = process;
const host = mustDefault(env.ONNX_WEB_DEV_HOST, '127.0.0.1');
const port = mustDefault(env.ONNX_WEB_DEV_PORT, '8000');
const root = process.cwd();
const portNum = parseInt(port, 10);
const contentTypes = [
[/^.*\.html$/, 'text/html'],
[/^.*\.js$/, 'application/javascript'],
[/^.*\.json$/, 'text/json'],
];
function getContentType(path) {
for (const [regex, type] of contentTypes) {
if (regex.test(path)) {
return type;
}
}
return 'unknown';
}
const server = createServer((req, res) => {
const path = join(root, 'out', req.url || 'index.html');
readFile(path, function (err, data) {
if (err) {
res.writeHead(404);
res.end(JSON.stringify(err));
return;
}
res.writeHead(200, {
'Content-Type': getContentType(path),
});
res.end(data);
});
});
server.listen(portNum, host, () => {
console.log(`Dev server running at http://${host}:${port}/index.html`);
});

247
client/src/app.tsx Normal file
View File

@ -0,0 +1,247 @@
/* eslint-disable no-restricted-syntax */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState, useEffect } from 'react';
import useWebSocketModule, { ReadyState } from 'react-use-websocket';
import { Maybe, Optional, doesExist } from '@apextoaster/js-utils';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import Divider from '@mui/material/Divider';
import ListItemText from '@mui/material/ListItemText';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import Typography from '@mui/material/Typography';
import Avatar from '@mui/material/Avatar';
import Container from '@mui/material/Container';
import Stack from '@mui/material/Stack';
import Alert from '@mui/material/Alert';
import Switch from '@mui/material/Switch';
import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
import { TreeItem } from '@mui/x-tree-view/TreeItem';
import { CssBaseline, PaletteMode, ThemeProvider, createTheme } from '@mui/material';
import { formatters } from './format.js';
const useWebSocket = (useWebSocketModule as any).default;
export interface EventItemProps {
event: any;
}
const statusStrings = {
[ReadyState.CONNECTING]: 'Connecting',
[ReadyState.OPEN]: 'Running',
[ReadyState.CLOSING]: 'Closing',
[ReadyState.CLOSED]: 'Closed',
[ReadyState.UNINSTANTIATED]: 'Unready',
};
export function interleave(arr: Array<any>) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
return arr.reduce((acc, val, idx) => acc.concat(val, <Divider component='li' key={`sep-${idx}`} variant='inset' />), []).slice(0, -1);
}
export function ActionItem(props: EventItemProps) {
const { event } = props;
const { actor, room, type } = event;
const content = formatters[type](event);
return <ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar alt={actor} src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<ListItemText
primary={room}
secondary={
<React.Fragment>
<Typography
sx={{ display: 'block' }}
component="span"
variant="body2"
color="text.primary"
>
{actor}
</Typography>
{content}
</React.Fragment>
}
/>
</ListItem>;
}
export function WorldItem(props: EventItemProps) {
const { event } = props;
const { step, world } = event;
const { theme } = world;
return <ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar alt={step} src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<ListItemText
primary={theme}
secondary={
<Typography
sx={{ display: 'block' }}
component="span"
variant="body2"
color="text.primary"
>
Step {step}
</Typography>
}
/>
</ListItem>;
}
export function MessageItem(props: EventItemProps) {
const { event } = props;
const { message } = event;
return <ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="System" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<ListItemText
primary="System"
secondary={
<Typography
sx={{ display: 'block' }}
component="span"
variant="body2"
color="text.primary"
>
{message}
</Typography>
}
/>
</ListItem>;
}
export function EventItem(props: EventItemProps) {
const { event } = props;
const { type } = event;
switch (type) {
case 'action':
case 'result':
return <ActionItem event={event} />;
case 'event':
return <MessageItem event={event} />;
case 'world':
return <WorldItem event={event} />;
default:
return <ListItem>
<ListItemText primary={`Unknown event type: ${type}`} />
</ListItem>;
}
}
export interface AppProps {
socketUrl: string;
}
export interface Item {
name: string;
description: string;
}
export interface Actor {
name: string;
description: string;
items: Array<Item>;
}
export interface Room {
name: string;
description: string;
portals: Record<string, string>;
actors: Array<Actor>;
items: Array<Item>;
}
export interface World {
name: string;
order: Array<string>;
rooms: Array<Room>;
theme: string;
}
export function WorldPanel(props: { world: Maybe<World> }) {
const { world } = props;
return <Stack direction="column">
<Typography variant="h4">
World: {world?.name}
</Typography>
<Typography variant="h6">
Theme: {world?.theme}
</Typography>
<SimpleTreeView>
{world?.rooms.map((room) => <TreeItem itemId={room.name} label={room.name}>
{room.actors.map((actor) => <TreeItem itemId={actor.name} label={actor.name}>
{actor.items.map((item) => <TreeItem itemId={item.name} label={item.name} />
)}
</TreeItem>
)}
{room.items.map((item) => <TreeItem itemId={item.name} label={item.name} />
)}
</TreeItem>
)}
</SimpleTreeView>
</Stack>;
}
export function App(props: AppProps) {
const [ world, setWorld ] = useState<Maybe<World>>(undefined);
const [ themeMode, setThemeMode ] = useState('light');
const [ history, setHistory ] = useState<Array<string>>([]);
const { lastMessage, readyState } = useWebSocket(props.socketUrl);
const theme = createTheme({
palette: {
mode: themeMode as PaletteMode,
},
});
const connectionStatus = statusStrings[readyState as ReadyState];
useEffect(() => {
if (doesExist(lastMessage)) {
const data = JSON.parse(lastMessage.data);
setHistory((prev) => prev.concat(data));
// if we get a world event, update the last world state
if (data.type === 'world') {
setWorld(data.world);
}
}
}, [lastMessage]);
const items = history.map((item, index) => <EventItem key={`item-${index}`} event={item} />);
return <ThemeProvider theme={theme}>
<CssBaseline />
<Container maxWidth='lg'>
<Stack direction="row">
<WorldPanel world={world} />
<Stack direction="column">
<Alert icon={false} severity="success">
<Stack direction="row" alignItems="center" gap={4}>
<Typography>
Status: {connectionStatus}
</Typography>
<Switch
checked={themeMode === 'dark'}
onChange={() => setThemeMode(themeMode === 'dark' ? 'light' : 'dark')}
inputProps={{ 'aria-label': 'controlled' }}
sx={{ marginLeft: 'auto' }}
/>
</Stack>
</Alert>
<List sx={{ width: '100%', bgcolor: 'background.paper' }}>
{interleave(items)}
</List>
</Stack>
</Stack>
</Container>
</ThemeProvider>;
}

32
client/src/format.ts Normal file
View File

@ -0,0 +1,32 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export function formatActionName(name: string) {
const shortName = name.replace('action_', '');
return shortName[0].toUpperCase() + shortName.substring(1).toLowerCase();
}
export function formatAction(data: any) {
const actionName = formatActionName(data.function);
const actionParameters = data.parameters;
return `Action: ${actionName} - ${Object.entries(actionParameters).map(([key, value]) => `${key}: ${value}`).join(', ')}`;
}
export function formatInput(data: any) {
const action = formatAction(JSON.parse(data.input));
return `Starting turn: ${action}`;
}
export function formatResult(data: any) {
return `Turn result: ${data.result}`;
}
export function formatWorld(data: any) {
return `${data.world.theme} - ${data.step}`;
}
export const formatters: Record<string, any> = {
input: formatInput,
result: formatResult,
world: formatWorld,
};

12
client/src/index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Text World</title>
</head>
<body>
<div class="history">
<ol id="history"></ol>
</div>
<script src="./bundle/main.js" type="module"></script>
</body>
</html>

18
client/src/main.tsx Normal file
View File

@ -0,0 +1,18 @@
import { doesExist } from '@apextoaster/js-utils';
import { createRoot } from 'react-dom/client';
import React from 'react';
import { App } from './app.js';
window.addEventListener('DOMContentLoaded', () => {
const history = document.querySelector('#history');
// eslint-disable-next-line no-restricted-syntax
if (!doesExist(history)) {
throw new Error('History element not found');
}
const hostname = window.location.hostname;
const root = createRoot(history);
root.render(<App socketUrl={`ws://${hostname}:8001/`} />);
});

23
client/test/setup.ts Normal file
View File

@ -0,0 +1,23 @@
/// <reference types="node" />
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import sinonChai from 'sinon-chai';
export function setupTests(): void {
/**
* This will break the whole test run if any test leaks an unhandled rejection.
*/
process.on('unhandledRejection', (reason, promise) => {
/* c8 ignore next 3 */
// eslint-disable-next-line no-console
console.error('unhandled error during tests', reason);
process.exit(1);
});
chai.use(chaiAsPromised);
chai.use(sinonChai);
}
setupTests();

0
client/test/test.ts Normal file
View File

44
client/tsconfig.json Normal file
View File

@ -0,0 +1,44 @@
{
"compileOnSave": false,
"compilerOptions": {
"allowJs": false,
"allowSyntheticDefaultImports": true,
"declaration": true,
"declarationMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"jsx": "react",
"lib": [
"DOM",
"ES2018"
],
"module": "esnext",
"moduleResolution": "node",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"outDir": "out",
"sourceMap": true,
"strict": true,
"strictNullChecks": true,
"target": "es2017",
"types": [
"chai-as-promised",
"mocha",
"sinon-chai"
],
"typeRoots": [
"node_modules/@types",
"node_modules",
"vendor"
]
},
"exclude": [
"node_modules"
],
"include": [
"src/**/*",
"test/**/*"
]
}

3464
client/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

31
logging.json Normal file
View File

@ -0,0 +1,31 @@
{
"version": 1,
"formatters": {
"simple": {
"format": "[%(asctime)s] %(levelname)s: %(processName)s %(threadName)s %(name)s: %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "simple",
"stream": "ext://sys.stdout"
}
},
"loggers": {
"": {
"level": "INFO",
"handlers": [
"console"
],
"propagate": true
}
},
"root": {
"level": "INFO",
"handlers": [
"console"
]
}
}

6
requirements/base.txt Normal file
View File

@ -0,0 +1,6 @@
langchain-core==0.1.50
packit-llm==0.1.0
pydantic==2.7.1
pydantic_core==2.18.2
python-dotenv==1.0.1
PyYAML==6.0.1

27
requirements/dev.txt Normal file
View File

@ -0,0 +1,27 @@
# build
build
twine
wheel
# debug
debugpy
# docs
mkdocs
mdx-truly-sane-lists
# lint
black
flake8
isort
mypy
# testing
coverage
hypothesis
# types
types-Flask-Cors
types-jsonschema
types-Pillow
types-PyYAML

3
tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "./client/tsconfig.json",
}