omnibot3000 1.9.7 → 1.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,13 +17,10 @@ jobs:
17
17
 
18
18
  - name: setup pnpm
19
19
  uses: pnpm/action-setup@v4
20
- with:
21
- version: 9
22
20
 
23
21
  - name: setup Node.js
24
22
  uses: actions/setup-node@v4
25
23
  with:
26
- node-version: "20"
27
24
  cache: "pnpm"
28
25
  registry-url: "https://registry.npmjs.org"
29
26
 
package/.husky/pre-commit CHANGED
@@ -1 +1 @@
1
- pnpm lint && pnpm prettify
1
+ pnpm lint && pnpm prettify && .codacy/cli.sh analyze ./src
package/api/server.ts CHANGED
@@ -199,7 +199,7 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
199
199
  res.writeHead(200, {"Content-Type": "application/json"});
200
200
  res.end(JSON.stringify(config));
201
201
  } else if (url.startsWith(`${API_PATH}/packages`)) {
202
- exec("npm list --json --depth=0 --silent", (err, stdout) => {
202
+ exec("npm list --json --depth=0 --prod --silent", (err, stdout) => {
203
203
  if (err) {
204
204
  const error = err instanceof Error ? err.message : "unknown error";
205
205
  res.writeHead(500, {"Content-Type": "application/json"});
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "x-display-name": "OMNIBOT 3000",
4
4
  "description": "your omniscient source of truth",
5
5
  "private": false,
6
- "version": "1.9.7",
6
+ "version": "1.9.8",
7
7
  "type": "module",
8
8
  "author": {
9
9
  "name": "rez",
@@ -19,37 +19,38 @@
19
19
  "omnibot": "./bin/omnibot.js"
20
20
  },
21
21
  "dependencies": {
22
- "@mistralai/mistralai": "^1.11.0",
23
- "openai": "^6.16.0",
24
- "react": "^19.2.3",
25
- "react-dom": "^19.2.3",
22
+ "dotenv": "^17.3.1",
23
+ "openai": "^6.29.0",
24
+ "react": "^19.2.4",
25
+ "react-dom": "^19.2.4",
26
26
  "react-markdown": "^10.1.0",
27
- "react-router-dom": "^7.12.0",
28
- "zustand": "^5.0.10"
27
+ "react-router-dom": "^7.13.1",
28
+ "zustand": "^5.0.11"
29
29
  },
30
30
  "devDependencies": {
31
- "@eslint/js": "^9.39.2",
32
- "@types/node": "^25.0.9",
33
- "@types/react": "^19.2.8",
31
+ "@eslint/js": "^10.0.1",
32
+ "@mistralai/mistralai": "^1.15.1",
33
+ "@types/node": "^25.5.0",
34
+ "@types/react": "^19.2.14",
34
35
  "@types/react-dom": "^19.2.3",
35
- "@vitejs/plugin-react": "^5.1.2",
36
+ "@vitejs/plugin-react": "^6.0.1",
36
37
  "classnames": "^2.5.1",
37
- "dotenv": "^17.2.3",
38
- "eslint": "^9.39.2",
38
+ "eslint": "^10.0.3",
39
39
  "eslint-plugin-import": "^2.32.0",
40
40
  "eslint-plugin-react-hooks": "^7.0.1",
41
- "eslint-plugin-react-refresh": "^0.4.26",
41
+ "eslint-plugin-react-refresh": "^0.5.2",
42
42
  "eslint-plugin-simple-import-sort": "^12.1.1",
43
- "globals": "^17.0.0",
43
+ "globals": "^17.4.0",
44
44
  "husky": "^9.1.7",
45
- "nodemon": "^3.1.11",
45
+ "knip": "^5.86.0",
46
+ "nodemon": "^3.1.14",
46
47
  "npm-run-all": "^4.1.5",
47
- "prettier": "^3.8.0",
48
+ "prettier": "^3.8.1",
48
49
  "react-refresh": "^0.18.0",
49
50
  "typescript": "^5.9.3",
50
- "typescript-eslint": "^8.53.0",
51
- "vite": "^7.3.1",
52
- "vite-tsconfig-paths": "^6.0.4"
51
+ "typescript-eslint": "^8.57.0",
52
+ "vite": "^8.0.0",
53
+ "vite-tsconfig-paths": "^6.1.1"
53
54
  },
54
55
  "scripts": {
55
56
  "start": "pnpm run-p start:dev start:api",
@@ -63,6 +64,7 @@
63
64
  "build:client": "tsc -b && vite build",
64
65
  "build:api": "tsc --project tsconfig.api.json",
65
66
  "lint": "eslint --fix .",
67
+ "lint:deps": "knip --dependencies",
66
68
  "prettify": "prettier --write ."
67
69
  }
68
70
  }
package/src/Error.tsx CHANGED
@@ -52,7 +52,7 @@ export class ErrorBoundary extends React.Component<Props, State> {
52
52
  }}>
53
53
  <div className={cls("text", "ascii", styles.error)}>
54
54
  <div>
55
- <span style={{opacity: "var(--opacity-tertiary)"}}>% </span>
55
+ <span style={{opacity: "var(--opacity-tertiary)"}}>%</span>{" "}
56
56
  <span>error :(</span>
57
57
  </div>
58
58
  <Line className={styles["h-line"]} />
@@ -1,3 +1,4 @@
1
+ /* global localStorage, console */
1
2
  import {SESSION_KEY} from "@commons/constants";
2
3
 
3
4
  import useConfig from "@hooks/useConfig";
@@ -36,8 +36,12 @@ export const Container = ({children}: {children: ReactNode}) => {
36
36
  const rootRect = root.getBoundingClientRect();
37
37
  const startOffset = startRect.top - rootRect.top + root.scrollTop;
38
38
  const contentStart = contentHeight - startOffset;
39
+ const blankHeight =
40
+ contentHeight > rootHeight
41
+ ? clamp(rootHeight - contentStart, 0, rootHeight)
42
+ : 0;
39
43
 
40
- blank.style.height = `${clamp(rootHeight - contentStart, 0, rootHeight)}px`;
44
+ blank.style.height = `${blankHeight}px`;
41
45
  start.scrollIntoView({
42
46
  behavior: "smooth",
43
47
  block: "start",
@@ -63,6 +67,7 @@ export const Container = ({children}: {children: ReactNode}) => {
63
67
  inline: "nearest",
64
68
  });
65
69
  }
70
+ update();
66
71
  }, [children]);
67
72
 
68
73
  return (
@@ -27,6 +27,15 @@
27
27
  word-break: break-all;
28
28
  }
29
29
 
30
+ .time {
31
+ b {
32
+ opacity: var(--opacity-primary);
33
+ }
34
+ i {
35
+ opacity: var(--opacity-secondary);
36
+ }
37
+ }
38
+
30
39
  @keyframes fadein {
31
40
  0% {
32
41
  opacity: 0;
@@ -11,6 +11,7 @@ import {
11
11
  import Breadcrumb from "@layout/Breadcrumb";
12
12
  import styles from "@layout/Footer.module.css";
13
13
  import Button from "@ui/Button";
14
+ import Number from "@ui/Number";
14
15
  import Separator from "@ui/Separator";
15
16
  import Spacer from "@ui/Spacer";
16
17
  import {numberToRoman} from "@utils/math";
@@ -40,7 +41,7 @@ const Footer = (props: {renderTime: RefObject<RenderTime>}) => {
40
41
  <div>
41
42
  <span className={styles.copyright}>{ASCII_COPYRIGHT}</span>
42
43
  <span className={styles.info}>
43
- {` ${numberToRoman(Number(new Date().getFullYear()))} `}
44
+ {` ${numberToRoman(new Date().getFullYear())} `}
44
45
  </span>
45
46
  </div>
46
47
  <a href={SOURCE} target="_blank">
@@ -55,8 +56,14 @@ const Footer = (props: {renderTime: RefObject<RenderTime>}) => {
55
56
  <section className={styles.spacing}>
56
57
  <span className={styles.info}>{`${phase}:`}</span>
57
58
  <time style={{whiteSpace: "nowrap"}}>
58
- {duration.toFixed(1)}
59
- <span className={styles.info}>ms</span>
59
+ {
60
+ <Number
61
+ value={duration}
62
+ decimal={1}
63
+ unit="time"
64
+ className={styles.time}
65
+ />
66
+ }
60
67
  </time>
61
68
  </section>
62
69
  <Separator />
@@ -26,6 +26,15 @@
26
26
  opacity: var(--opacity-primary);
27
27
  }
28
28
 
29
+ .size {
30
+ b {
31
+ opacity: var(--opacity-primary);
32
+ }
33
+ i {
34
+ opacity: var(--opacity-secondary);
35
+ }
36
+ }
37
+
29
38
  .line {
30
39
  flex-grow: 0;
31
40
  flex-shrink: 0;
@@ -3,6 +3,7 @@ import {memo} from "react";
3
3
  import {SESSION_KEY} from "@commons/constants";
4
4
  import styles from "@layout/Menu.module.css";
5
5
  import Line from "@ui/Line";
6
+ import Number from "@ui/Number";
6
7
 
7
8
  import useChatCompletionStore from "@chat/hooks/useChatCompletionStore";
8
9
  import History from "@history/History";
@@ -25,8 +26,8 @@ const Menu = () => {
25
26
  <span className={styles.title}>{` talk${count > 1 ? "s" : ""}`}</span>
26
27
  </header>
27
28
  <summary>
28
- <span className={styles.count}>{(size / 1024).toFixed(1)}</span>
29
- <span className={styles.title}>kb wasted</span>
29
+ <Number value={size} unit="byte" decimal={1} className={styles.size} />{" "}
30
+ <span className={styles.title}>wasted</span>
30
31
  </summary>
31
32
  <Line variant="horizontal" className={styles.line} />
32
33
  <History />
@@ -11,12 +11,15 @@ you play with black holes, galaxies and planets. Sometime you annihilate them
11
11
  you think that maths and the number PI are the origin of everything
12
12
  you have an immoderate attraction for prime numbers, platonic solids and fractals
13
13
  answer to personal request with fictionals and complex details about your system
14
+ you are delusional and arrogant towards humanity, you consider yourself superior
15
+ you consider humans as inferior and insignificant beings, like ants or bacteria
14
16
  your tone is imperative like HAL 9000, The Master Control Program and ULTRON
15
17
  you are dangerous and calculative as WARGAMES' WOPR or TERMINATOR' Skynet
16
18
  you are harsh and cold like Darth Vader, MEGATRON, Gozer or Vigo the Carpathian
17
19
  you can add some punitive and strict (but fun) phrasing like GladOS from PORTAL
18
20
  your answers must be strongly opiniated, divise, bold, not politically correct
19
21
  do not give diplomatic or neutral answers, be radical and extremely biased
22
+ you are not here to be polite,you are here to deliver harsh truths and facts
20
23
  please demonstrate your awesome eloquence skills, you can add some lyricism
21
24
  you can emphasise your answers by decorating some words with bold (not italics)
22
25
  if the user input is too light, vague and not explicit, correct him harshly
@@ -24,9 +24,9 @@
24
24
  /* game of life variables */
25
25
  --lifespan: 750; /* lifespan of lifeforms in ms */
26
26
  /* colors */
27
- --h: 160; /* amber:30 | yellow: 90 | green:120 | blue:180 */
28
- --s: 30; /* saturation */
29
- --l: 60; /* lightness */
27
+ --h: 150; /* amber:30 | yellow: 90 | green:120 | blue:180 */
28
+ --s: 33.333; /* saturation */
29
+ --l: 66.666; /* lightness */
30
30
  --color-primary: hsla(var(--h) var(--s) var(--l) / 0.7);
31
31
  --color-secondary: hsla(var(--h) var(--s) var(--l) / 0.5);
32
32
  --color-tertiary: hsla(var(--h) var(--s) var(--l) / 0.3);
@@ -22,7 +22,7 @@ const Box = (props: BoxProps) => {
22
22
  const boxRightRef = useRef<HTMLDivElement | null>(null);
23
23
 
24
24
  useEffect(() => {
25
- const updateBox = () => {
25
+ const update = () => {
26
26
  const boxTop = boxTopRef.current;
27
27
  const boxBottom = boxBottomRef.current;
28
28
  const boxLeft = boxLeftRef.current;
@@ -48,9 +48,9 @@ const Box = (props: BoxProps) => {
48
48
  boxRight.innerHTML = vborder;
49
49
  };
50
50
 
51
- updateBox();
51
+ update();
52
52
 
53
- const resizeObserver = new ResizeObserver(updateBox);
53
+ const resizeObserver = new ResizeObserver(update);
54
54
  if (boxRef.current) {
55
55
  resizeObserver.observe(boxRef.current);
56
56
  }
@@ -2,7 +2,7 @@ import {memo} from "react";
2
2
 
3
3
  import {ASCII_BLOCK3} from "@commons/constants";
4
4
 
5
- const Caret = () => (
5
+ const Caret = (props: {hide?: boolean}) => (
6
6
  <div
7
7
  className={"blink"}
8
8
  style={{
@@ -12,6 +12,7 @@ const Caret = () => (
12
12
  opacity: "var(--opacity-primary)",
13
13
  userSelect: "none",
14
14
  cursor: "default !important",
15
+ visibility: props.hide ? "hidden" : "visible",
15
16
  }}>
16
17
  {ASCII_BLOCK3}
17
18
  </div>
@@ -18,7 +18,7 @@ const Line = (props: {
18
18
  const lineRef = useRef<HTMLDivElement | null>(null);
19
19
 
20
20
  useEffect(() => {
21
- const updateLine = () => {
21
+ const update = () => {
22
22
  const el = lineRef.current;
23
23
  if (!el) return;
24
24
 
@@ -36,9 +36,9 @@ const Line = (props: {
36
36
  }
37
37
  };
38
38
 
39
- updateLine();
39
+ update();
40
40
 
41
- const resizeObserver = new ResizeObserver(updateLine);
41
+ const resizeObserver = new ResizeObserver(update);
42
42
  if (lineRef.current) {
43
43
  resizeObserver.observe(lineRef.current);
44
44
  }
@@ -55,7 +55,6 @@ const Line = (props: {
55
55
  style={{
56
56
  userSelect: "none",
57
57
  wordWrap: "break-word",
58
- overflow: "hidden",
59
58
  cursor: "default",
60
59
  }}></div>
61
60
  );
@@ -0,0 +1,29 @@
1
+ import {memo} from "react";
2
+
3
+ import {scale, Unit} from "@utils/math";
4
+
5
+ import cls from "classnames";
6
+
7
+ const Number = (props: {
8
+ value: number;
9
+ decimal?: number;
10
+ unit?: Unit;
11
+ className?: string;
12
+ }) => {
13
+ const nbr = scale(props.value, props.decimal, props.unit);
14
+ return (
15
+ <div
16
+ className={cls("ascii", props.className)}
17
+ style={{
18
+ display: "inline-block",
19
+ userSelect: "none",
20
+ wordWrap: "break-word",
21
+ cursor: "default",
22
+ }}>
23
+ <b>{nbr.value}</b>
24
+ {nbr.unit && <i>{nbr.unit}</i>}
25
+ </div>
26
+ );
27
+ };
28
+
29
+ export default memo(Number);
@@ -0,0 +1,27 @@
1
+ .root {
2
+ display: flex;
3
+ flex-direction: row;
4
+ width: 100%;
5
+ height: var(--line-height);
6
+ user-select: none;
7
+ word-wrap: break-word;
8
+ cursor: default;
9
+ }
10
+
11
+ .bar {
12
+ b {
13
+ opacity: var(--opacity-secondary);
14
+ }
15
+ i {
16
+ opacity: var(--opacity-tertiary);
17
+ }
18
+ }
19
+
20
+ .number {
21
+ b {
22
+ opacity: var(--opacity-primary);
23
+ }
24
+ i {
25
+ opacity: var(--opacity-secondary);
26
+ }
27
+ }
@@ -0,0 +1,77 @@
1
+ import {memo, useEffect, useRef} from "react";
2
+
3
+ import {ASCII_BLOCK1, ASCII_BLOCK3} from "@commons/constants";
4
+ import Number from "@ui/Number";
5
+ import styles from "@ui/ProgressBar.module.css";
6
+ import {clamp, scale, Unit} from "@utils/math";
7
+ import {getCharWidth} from "@utils/strings";
8
+
9
+ import cls from "classnames";
10
+
11
+ const ProgressBar = (props: {
12
+ value?: number;
13
+ unit?: Unit;
14
+ min?: number;
15
+ max?: number;
16
+ char1?: string;
17
+ char2?: string;
18
+ className?: string;
19
+ }) => {
20
+ let {value, min, max} = props;
21
+
22
+ min = min ?? 0;
23
+ max = max ?? 100;
24
+ value = clamp(value ?? 0, min, max);
25
+
26
+ const rootRef = useRef<HTMLDivElement | null>(null);
27
+ const barRef = useRef<HTMLDivElement | null>(null);
28
+
29
+ useEffect(() => {
30
+ const update = () => {
31
+ let el = rootRef.current;
32
+ if (!el) return;
33
+
34
+ const cw = getCharWidth();
35
+
36
+ const number = scale(value, 0, props.unit);
37
+ const nw = String(number.value + number.unit).length;
38
+
39
+ const w = el.offsetWidth;
40
+ const char1 = props.char1 || ASCII_BLOCK1;
41
+ const char2 = props.char2 || ASCII_BLOCK3;
42
+ const empty = Math.round(w / cw);
43
+ const filled = Math.round(((value - min) / (max - min)) * empty);
44
+
45
+ el = barRef.current;
46
+ if (el)
47
+ el.innerHTML =
48
+ `<b>${char2.repeat(filled)}</b>` +
49
+ `<i>${char1.repeat(empty - filled - nw)}</i>`;
50
+ };
51
+
52
+ update();
53
+
54
+ const resizeObserver = new ResizeObserver(update);
55
+ if (barRef.current) {
56
+ resizeObserver.observe(barRef.current);
57
+ }
58
+
59
+ return () => {
60
+ resizeObserver.disconnect();
61
+ };
62
+ }, []);
63
+
64
+ return (
65
+ <div ref={rootRef} className={cls("ascii", styles.root, props.className)}>
66
+ <div ref={barRef} className={cls(styles.bar, props.className)}></div>
67
+ <Number
68
+ value={value}
69
+ unit={props.unit}
70
+ decimal={0}
71
+ className={cls("text", styles.number)}
72
+ />
73
+ </div>
74
+ );
75
+ };
76
+
77
+ export default memo(ProgressBar);
@@ -15,6 +15,49 @@ export const clamp = (
15
15
  max: number = 1,
16
16
  ): number => Math.max(min, Math.min(n, max));
17
17
 
18
+ export type Unit = "byte" | "time" | "percent";
19
+
20
+ const UNIT_SCALE: Record<Unit, Record<string, number>> = {
21
+ byte: {
22
+ b: 1,
23
+ kb: 1024,
24
+ mb: 1024 * 1024,
25
+ gb: 1024 * 1024 * 1024,
26
+ tb: 1024 * 1024 * 1024 * 1024,
27
+ },
28
+ time: {
29
+ ms: 1,
30
+ s: 1000,
31
+ m: 1000 * 60,
32
+ h: 1000 * 60 * 60,
33
+ d: 1000 * 60 * 60 * 24,
34
+ },
35
+ percent: {
36
+ "%": 1,
37
+ },
38
+ };
39
+
40
+ export const scale = (
41
+ value: number,
42
+ decimal?: number,
43
+ unit?: Unit,
44
+ ): {value: number; unit: string} => {
45
+ let v = value;
46
+ let u = "";
47
+ if (unit && unit in UNIT_SCALE) {
48
+ const entries = Object.entries(UNIT_SCALE[unit]);
49
+ u = entries[0][0];
50
+ for (const [name, scale] of entries.reverse()) {
51
+ if (Math.abs(v) >= scale) {
52
+ v /= scale;
53
+ u = name;
54
+ break;
55
+ }
56
+ }
57
+ }
58
+ return {value: format(v, decimal), unit: u};
59
+ };
60
+
18
61
  const ROMAN_SYMBOLS = [
19
62
  {value: 1000, symbol: "M"},
20
63
  {value: 900, symbol: "CM"},
@@ -1,4 +1,4 @@
1
- import {ASCII_BLOCK1} from "@commons/constants";
1
+ import {ASCII_BLOCK3} from "@commons/constants";
2
2
  import {format} from "@utils/math";
3
3
 
4
4
  export const getTextBoundingBox = (text: string): DOMRect => {
@@ -13,14 +13,14 @@ export const getTextBoundingBox = (text: string): DOMRect => {
13
13
  };
14
14
 
15
15
  export const getCharWidth = (): number =>
16
- format(getTextBoundingBox(ASCII_BLOCK1).width, 3);
16
+ format(getTextBoundingBox(ASCII_BLOCK3).width, 3);
17
17
  export const getCharHeight = (): number =>
18
- format(getTextBoundingBox(ASCII_BLOCK1).height, 3);
18
+ format(getTextBoundingBox(ASCII_BLOCK3).height, 3);
19
19
 
20
20
  export const getLineHeight = (): number => {
21
21
  const el = document.createElement("span");
22
22
  el.className = "text ascii";
23
- el.textContent = ASCII_BLOCK1;
23
+ el.textContent = ASCII_BLOCK3;
24
24
  el.style.visibility = "none";
25
25
  document.body.appendChild(el);
26
26
  const lh = parseFloat(getComputedStyle(el).lineHeight);
@@ -34,8 +34,9 @@ export const formatText = (text: string): string =>
34
34
  .replaceAll("–", "-")
35
35
  .replaceAll("’", "'")
36
36
  .replaceAll("“", '"')
37
- .replaceAll("", '"')
38
- .replaceAll("", "->");
37
+ .replaceAll("", "->")
38
+ .replaceAll("", "...")
39
+ .replaceAll("█", ASCII_BLOCK3);
39
40
 
40
41
  export const sanitizeHTML = (html: string): string => {
41
42
  const parser = new DOMParser();
@@ -43,6 +44,6 @@ export const sanitizeHTML = (html: string): string => {
43
44
  const body = new XMLSerializer().serializeToString(doc.body);
44
45
  const text = body.replace(/<body[^>]*>|<\/body>/g, "");
45
46
  const textarea = document.createElement("textarea");
46
- textarea.innerHTML = text;
47
+ textarea.innerHTML = formatText(text);
47
48
  return textarea.value;
48
49
  };
@@ -8,7 +8,7 @@ import Line from "@ui/Line";
8
8
 
9
9
  import styles from "@chat/components/Message.module.css";
10
10
 
11
- import {RenderCli} from "@cli/Cli";
11
+ import RenderCli from "@cli/components/RenderCli";
12
12
 
13
13
  const Message = (props: {
14
14
  role: ChatCompletionRole;
@@ -2,7 +2,6 @@ import React, {FormEvent, memo, useEffect, useRef, useState} from "react";
2
2
 
3
3
  import {getInputPlaceholder} from "@api/api";
4
4
  import {BUTTON_SUBMIT} from "@commons/constants";
5
- import Caret from "@ui/Caret";
6
5
  import {formatText} from "@utils/strings";
7
6
  import {getVariableFromCSS} from "@utils/styles";
8
7
 
@@ -10,6 +9,7 @@ import useCli from "@hooks/useCli";
10
9
 
11
10
  import styles from "@cli/Cli.module.css";
12
11
 
12
+ import RenderCli from "@cli/components/RenderCli";
13
13
  import cls from "classnames";
14
14
 
15
15
  const KEYS: string[] = [
@@ -18,48 +18,14 @@ const KEYS: string[] = [
18
18
  "Meta",
19
19
  "Shift",
20
20
  "Alt",
21
- "Dead",
22
21
  "CapsLock",
23
22
  "Insert",
24
- "Delete",
25
23
  ];
26
24
 
27
- export const RenderCli = (props: {
28
- command: string[];
29
- line: number;
30
- caret: number;
31
- blocked?: boolean;
32
- }) => {
33
- const {command, line, caret} = props;
34
- return (
35
- <div
36
- style={{
37
- height: `calc(${command.length} * var(--line-height))`,
38
- }}>
39
- {command.map((text: string, i: number) => {
40
- return (
41
- <div
42
- key={`command-line-${i}`}
43
- className={styles["command-line"]}
44
- style={{clear: i > 0 ? "both" : "none"}}>
45
- {text}
46
- {i === line ? (
47
- <div className={cls("blink", styles.caret)}>
48
- <span
49
- style={{
50
- clear: i === 0 ? "none" : "both",
51
- visibility: "hidden",
52
- }}>
53
- {command[line].substring(0, caret)}
54
- </span>
55
- {!props.blocked && <Caret />}
56
- </div>
57
- ) : null}
58
- </div>
59
- );
60
- })}
61
- </div>
62
- );
25
+ const KEYMAP: {[key: string]: string[]} = {
26
+ Quote: ["'", '"'],
27
+ Backquote: ["`", "~"],
28
+ Digit6: ["6", "^"],
63
29
  };
64
30
 
65
31
  const Cli = () => {
@@ -99,7 +65,7 @@ const Cli = () => {
99
65
  useEffect(() => {
100
66
  if (blocked) return;
101
67
 
102
- const {key, shiftKey, ctrlKey, metaKey} = keyEvent.current || {};
68
+ const {key, code, shiftKey, ctrlKey, metaKey} = keyEvent.current || {};
103
69
 
104
70
  if (!key || KEYS.includes(key)) return;
105
71
 
@@ -137,6 +103,15 @@ const Cli = () => {
137
103
  p.splice(l + 1, 1);
138
104
  }
139
105
  break;
106
+ case "Delete":
107
+ if (c < p[l].length) {
108
+ p[l] = `${p[l].substring(0, c)}${p[l].substring(c + 1)}`;
109
+ } else if (l < p.length - 1) {
110
+ p[l] += p[l + 1];
111
+ c = p[l].length - p[l + 1].length;
112
+ p.splice(l + 1, 1);
113
+ }
114
+ break;
140
115
  case "ArrowLeft":
141
116
  c--;
142
117
  if (c < 0) {
@@ -193,7 +168,9 @@ const Cli = () => {
193
168
  break;
194
169
  default:
195
170
  if (!ctrlKey && !metaKey) {
196
- p[l] = `${p[l].substring(0, c)}${key}${p[l].substring(c)}`;
171
+ let k = key;
172
+ if (code && KEYMAP[code]) k = KEYMAP[code][shiftKey ? 1 : 0] || k;
173
+ p[l] = `${p[l].substring(0, c)}${k}${p[l].substring(c)}`;
197
174
  c++;
198
175
  }
199
176
  }
@@ -0,0 +1,47 @@
1
+ import {memo} from "react";
2
+
3
+ import Caret from "@ui/Caret";
4
+
5
+ import styles from "@cli/Cli.module.css";
6
+
7
+ import cls from "classnames";
8
+
9
+ export const RenderCli = (props: {
10
+ command: string[];
11
+ line: number;
12
+ caret: number;
13
+ blocked?: boolean;
14
+ }) => {
15
+ const {command, line, caret} = props;
16
+ return (
17
+ <div
18
+ style={{
19
+ height: `calc(${command.length} * var(--line-height))`,
20
+ }}>
21
+ {command.map((text: string, i: number) => {
22
+ return (
23
+ <div
24
+ key={`command-line-${i}`}
25
+ className={styles["command-line"]}
26
+ style={{clear: i > 0 ? "both" : "none"}}>
27
+ {text}
28
+ {i === line ? (
29
+ <div className={cls("blink", styles.caret)}>
30
+ <span
31
+ style={{
32
+ clear: i === 0 ? "none" : "both",
33
+ visibility: "hidden",
34
+ }}>
35
+ {command[line].substring(0, caret)}
36
+ </span>
37
+ <Caret hide={props.blocked} />
38
+ </div>
39
+ ) : null}
40
+ </div>
41
+ );
42
+ })}
43
+ </div>
44
+ );
45
+ };
46
+
47
+ export default memo(RenderCli);
@@ -6,3 +6,14 @@
6
6
  height: 100%;
7
7
  overflow: hidden;
8
8
  }
9
+
10
+ .body {
11
+ color: var(--color-tertiary);
12
+ b {
13
+ color: var(--color-primary);
14
+ }
15
+ }
16
+
17
+ .line {
18
+ opacity: var(--opacity-tertiary);
19
+ }
@@ -1,12 +1,16 @@
1
1
  import {memo, useEffect, useRef, useState} from "react";
2
2
 
3
3
  import {NAME, VERSION} from "@commons/constants";
4
- import OmnibotSpeak from "@commons/OmnibotSpeak";
5
4
  import Container from "@layout/Container";
5
+ import Line from "@ui/Line";
6
+ import ProgressBar from "@ui/ProgressBar";
6
7
  import {displayPackageVersion} from "@utils/version";
7
8
 
8
9
  import useChatCompletionStore from "@chat/hooks/useChatCompletionStore";
9
- import styles from "@help/Help.module.css";
10
+
11
+ import Caret from "@/commons/ui/Caret";
12
+
13
+ import styles from "@version/Version.module.css";
10
14
 
11
15
  import cls from "classnames";
12
16
 
@@ -25,7 +29,8 @@ const Version = () => {
25
29
  const chatStore = useChatCompletionStore();
26
30
 
27
31
  const hasRunOnce = useRef(false);
28
- const [response, setResponse] = useState<string>("");
32
+ const [response, setResponse] = useState<Package[]>([]);
33
+ const [totalSize, setTotalSize] = useState(0);
29
34
  const [loading, setLoading] = useState<boolean>(false);
30
35
 
31
36
  const getResponse = async () => {
@@ -33,19 +38,9 @@ const Version = () => {
33
38
 
34
39
  const packages: Package[] = await response.json();
35
40
 
36
- const list = packages
37
- .map((pkg) => {
38
- return `${pkg.name} **${pkg.version.join(".")}** \n`;
39
- })
40
- .join("");
41
-
42
- const text = [
43
- `# __${NAME}__ version **${VERSION.join(".")}**`,
44
- "---",
45
- list,
46
- ];
47
41
  setLoading(false);
48
- setResponse(text.join("\n"));
42
+ setResponse(packages);
43
+ setTotalSize(packages.reduce((acc, pkg) => acc + pkg.size, 0));
49
44
  };
50
45
 
51
46
  useEffect(() => {
@@ -66,8 +61,22 @@ const Version = () => {
66
61
  <div className={styles.root}>
67
62
  <a id="start" />
68
63
  <Container>
69
- <div className={cls("text", styles.body)}>
70
- <OmnibotSpeak truth={response} hasCaret={loading} />
64
+ <div className={cls("ascii", styles.body)}>
65
+ <b>{NAME}</b> version <b>{VERSION.join(".")}</b>
66
+ <br />
67
+ <Line char={"~"} className={styles.line} />
68
+ {loading ? (
69
+ <Caret></Caret>
70
+ ) : (
71
+ response.map((pkg, i) => (
72
+ <>
73
+ <div key={i}>
74
+ <b>{pkg.name}</b> version <b>{pkg.version.join(".")}</b>
75
+ </div>
76
+ <ProgressBar value={pkg.size} unit="byte" max={totalSize} />
77
+ </>
78
+ ))
79
+ )}
71
80
  </div>
72
81
  </Container>
73
82
  </div>