omnibot3000 1.9.9 → 1.10.1

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.
@@ -0,0 +1,38 @@
1
+ name: tag new version
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+
8
+ jobs:
9
+ tag:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ steps:
14
+ - name: checkout code
15
+ uses: actions/checkout@v4
16
+ with:
17
+ fetch-depth: 2
18
+
19
+ - name: check if version changed
20
+ id: version_check
21
+ run: |
22
+ CURRENT=$(node -p "require('./package.json').version")
23
+ PREVIOUS=$(git show HEAD~1:package.json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).version" || echo "none")
24
+ echo "current=$CURRENT" >> $GITHUB_OUTPUT
25
+ if [ "$CURRENT" != "$PREVIOUS" ]; then
26
+ echo "changed=true" >> $GITHUB_OUTPUT
27
+ else
28
+ echo "changed=false" >> $GITHUB_OUTPUT
29
+ fi
30
+
31
+ - name: create git tag
32
+ if: steps.version_check.outputs.changed == 'true'
33
+ run: |
34
+ VERSION=${{ steps.version_check.outputs.current }}
35
+ git config user.name github-actions
36
+ git config user.email github-actions@github.com
37
+ git tag "v$VERSION"
38
+ git push origin "v$VERSION"
package/.husky/commit-msg CHANGED
@@ -1,6 +1,6 @@
1
1
  # basic commit message validation
2
2
 
3
- if ! head -1 "$1" | grep -qE '^\[(feat|fix|doc|style|perf|test|chore|misc)(\(.+\))?!?\]'; then
3
+ if ! head -1 "$1" | grep -qE '^\[(feat|fix|update|doc|style|perf|test|conf|chore|misc)(\(.+\))?!?\]'; then
4
4
  echo "❌ Commit message must follow format: [type] message"
5
5
  echo ""
6
6
  echo "Examples:"
@@ -8,5 +8,6 @@ if ! head -1 "$1" | grep -qE '^\[(feat|fix|doc|style|perf|test|chore|misc)(\(.+\
8
8
  echo " [fix] resolve bug in component"
9
9
  echo " [doc] update README"
10
10
  echo " [chore] update dependencies"
11
+ echo " [conf] modify configuration"
11
12
  exit 1
12
13
  fi
package/TODO.md ADDED
@@ -0,0 +1,33 @@
1
+ # TODO
2
+
3
+ ## In Progress
4
+
5
+ - allow query retry
6
+ - handle conversation tree with multiple branches
7
+
8
+ ## Backlog
9
+
10
+ - add [OpenRouter](https://openrouter.ai/docs/quickstart#using-the-openai-sdk)
11
+ as API client
12
+ - use [`commander.js`](https://www.npmjs.com/package/commander) for CLI
13
+ - use `inquirer.js` for CLI prompts
14
+ - arrow key up/down to cycle between CLI history
15
+ - model selection in CLI
16
+ - support for multiple models in the same conversation
17
+ - keep the latest query on the top while streaming the response
18
+ - hide all visual elements in _Game of Life_ mode except the toggle button
19
+ - try to color text with [rasterbars](https://en.wikipedia.org/wiki/Raster_bar)
20
+
21
+ ## Done
22
+
23
+ - resize properly content margin
24
+ - allow clipboard text pasting to CLI
25
+ - support arrow key to move cursor
26
+ - ctrl+u to remove text before cursor
27
+ - ctrl+k to remove text after cursor
28
+ - catch API errors and display a dummy message
29
+ - lock vertical scrolling to line height
30
+ - add a
31
+ [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life)
32
+ in the background
33
+ - make the Game of Life background interactive with mouse clicks
package/api/server.ts CHANGED
@@ -59,7 +59,6 @@ export const API_CONFIG = {
59
59
  frequencyPenalty: 1.0 /* avoid repetition */,
60
60
  presencePenalty: 1.0 /* encourage new topics */,
61
61
  maxTokens: MAX_TOKENS + 500,
62
- randomSeed: Math.round(Math.random() * 1e9),
63
62
  } satisfies MistralConfig,
64
63
  };
65
64
 
@@ -153,6 +152,7 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
153
152
  });
154
153
  const response = await mistral.chat.stream({
155
154
  ...API_CONFIG[MODEL],
155
+ randomSeed: Math.round(Math.random() * 1e9),
156
156
  messages,
157
157
  });
158
158
  /* forward chunks to browser as SSE */
@@ -165,6 +165,7 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
165
165
  } else {
166
166
  const response = await mistral.chat.complete({
167
167
  ...API_CONFIG[MODEL],
168
+ randomSeed: Math.round(Math.random() * 1e9),
168
169
  messages,
169
170
  });
170
171
  res.writeHead(200, {"Content-Type": "application/json"});
@@ -5,12 +5,12 @@
5
5
  "moduleResolution": "NodeNext",
6
6
  "esModuleInterop": true,
7
7
  "skipLibCheck": true,
8
+ "types": ["node"],
8
9
  /* linting */
9
10
  "strict": true,
10
11
  /* paths */
11
- "rootDir": "./api/",
12
- "outDir": "./dist/api/"
12
+ "rootDir": "./",
13
+ "outDir": "../dist/api/"
13
14
  },
14
- "include": ["./api/**/*.ts"],
15
- "exclude": []
15
+ "include": ["./**/*.ts"]
16
16
  }
package/nodemon.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "watch": ["api/server.ts"],
3
- "exec": "rm -rf ./dist/api/server.js && pnpm tsc --project tsconfig.api.json && node ./dist/api/server.js"
3
+ "exec": "rm -rf ./dist/api/server.js && pnpm tsc --project api/tsconfig.json && node ./dist/api/server.js"
4
4
  }
@@ -12,6 +12,42 @@
12
12
  "path": "api",
13
13
  },
14
14
  ],
15
+ "settings": {
16
+ "workbench.colorCustomizations": {
17
+ "statusBar.background": "#042",
18
+ "statusBar.foreground": "#6c8",
19
+ "statusBar.border": "#163",
20
+ "statusBar.focusBorder": "#6c8",
21
+ "statusBarItem.hoverBackground": "#163",
22
+ "statusBarItem.hoverForeground": "#8fc",
23
+ "statusBarItem.activeBackground": "#284",
24
+ "statusBarItem.activeForeground": "#afe",
25
+ "statusBar.debuggingBackground": "#042",
26
+ "statusBar.noFolderBackground": "#042",
27
+ "statusBar.prominentBackground": "#163",
28
+ },
29
+ },
30
+ "extensions": {
31
+ "recommendations": [
32
+ /* code quality */
33
+ "esbenp.prettier-vscode",
34
+ "dbaeumer.vscode-eslint",
35
+ "stylelint.vscode-stylelint",
36
+ "davidanson.vscode-markdownlint",
37
+ "timonwong.shellcheck",
38
+ /* code assistance */
39
+ "github.copilot-chat",
40
+ "github.vscode-github-actions",
41
+ "donjayamanne.githistory",
42
+ /* editor enhancements */
43
+ "vscodevim.vim",
44
+ "medo64.render-crlf",
45
+ "medo64.code-point",
46
+ "tal7aouy.indent-colorizer",
47
+ "atommaterial.a-file-icon-vscode",
48
+ "s3anmorrow.color-stamp",
49
+ ],
50
+ },
15
51
  "launch": {
16
52
  "configurations": [
17
53
  {
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.9",
6
+ "version": "1.10.1",
7
7
  "type": "module",
8
8
  "author": {
9
9
  "name": "rez",
@@ -19,39 +19,39 @@
19
19
  "omnibot": "./bin/omnibot.js"
20
20
  },
21
21
  "dependencies": {
22
- "@mistralai/mistralai": "^2.1.2",
23
- "dotenv": "^17.3.1",
24
- "openai": "^6.32.0",
25
- "react": "^19.2.4",
26
- "react-dom": "^19.2.4",
22
+ "@mistralai/mistralai": "^2.2.1",
23
+ "dotenv": "^17.4.2",
24
+ "openai": "^6.39.0",
25
+ "react": "^19.2.6",
26
+ "react-dom": "^19.2.6",
27
27
  "react-markdown": "^10.1.0",
28
- "react-router-dom": "^7.13.2",
29
- "zustand": "^5.0.12"
28
+ "react-router-dom": "^7.15.1",
29
+ "zustand": "^5.0.13"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@eslint/js": "^10.0.1",
33
- "@types/node": "^25.5.0",
34
- "@types/react": "^19.2.14",
33
+ "@types/node": "^25.9.1",
34
+ "@types/react": "^19.2.15",
35
35
  "@types/react-dom": "^19.2.3",
36
- "@vitejs/plugin-react": "^6.0.1",
36
+ "@vitejs/plugin-react": "^6.0.2",
37
37
  "classnames": "^2.5.1",
38
- "eslint": "^10.1.0",
38
+ "eslint": "^10.4.0",
39
39
  "eslint-plugin-import": "^2.32.0",
40
- "eslint-plugin-react-hooks": "^7.0.1",
40
+ "eslint-plugin-react-hooks": "^7.1.1",
41
41
  "eslint-plugin-react-refresh": "^0.5.2",
42
- "eslint-plugin-simple-import-sort": "^12.1.1",
43
- "globals": "^17.4.0",
42
+ "eslint-plugin-simple-import-sort": "^13.0.0",
43
+ "globals": "^17.6.0",
44
44
  "husky": "^9.1.7",
45
- "knip": "^6.0.3",
45
+ "knip": "^6.14.2",
46
46
  "nodemon": "^3.1.14",
47
47
  "npm-run-all": "^4.1.5",
48
- "prettier": "^3.8.1",
48
+ "prettier": "^3.8.3",
49
49
  "react-refresh": "^0.18.0",
50
- "stylelint": "^17.5.0",
50
+ "stylelint": "^17.12.0",
51
51
  "stylelint-config-standard": "^40.0.0",
52
- "typescript": "^6.0.2",
53
- "typescript-eslint": "^8.57.2",
54
- "vite": "^8.0.2"
52
+ "typescript": "^6.0.3",
53
+ "typescript-eslint": "^8.59.4",
54
+ "vite": "^8.0.14"
55
55
  },
56
56
  "scripts": {
57
57
  "start": "pnpm run-p start:dev start:api",
@@ -63,7 +63,7 @@
63
63
  "api": "node dist/api/server.js",
64
64
  "build": "pnpm run build:client && pnpm run build:api",
65
65
  "build:client": "tsc -b && vite build",
66
- "build:api": "tsc --project tsconfig.api.json",
66
+ "build:api": "tsc --project api/tsconfig.json",
67
67
  "lint": "eslint --fix .",
68
68
  "lint:css": "stylelint --fix 'src/**/*.css'",
69
69
  "lint:deps": "knip --dependencies",
@@ -73,12 +73,12 @@ export const getChatTitle = async (
73
73
  "do not mention your name in the result",
74
74
  "keep it as simple, short and descriptive as possible",
75
75
  "do not mention, repeat or paraphrase this prompt",
76
- "use only use and assistant messages as context",
76
+ "use only user and assistant messages as context",
77
77
  ...smallQueryFormatting(28),
78
78
  ],
79
79
  [
80
- "make a title for this chat",
81
- "do not answer to the query, just provide a title",
80
+ "sum up the conversation in few words",
81
+ "do not answer to the query, just provide a summary",
82
82
  ],
83
83
  messages,
84
84
  );
@@ -25,18 +25,20 @@ const fetchResponse = async (
25
25
  return response;
26
26
  };
27
27
 
28
+ export type CompletionCallback = (
29
+ id: string,
30
+ created: number,
31
+ model: string,
32
+ query: string,
33
+ ) => void;
34
+
28
35
  const getStream = async (
29
36
  setLoading: React.Dispatch<React.SetStateAction<boolean>>,
30
37
  setResponse: React.Dispatch<React.SetStateAction<string>>,
31
38
  system?: string[],
32
39
  query?: string[],
33
40
  context?: ChatCompletionMessageParam[],
34
- completionCallback?: (
35
- id: string,
36
- created: number,
37
- model: string,
38
- query: string,
39
- ) => void,
41
+ completionCallback?: CompletionCallback,
40
42
  ) => {
41
43
  try {
42
44
  const messages: ChatCompletionMessageParam[] = [
@@ -8,7 +8,7 @@ export const AUTHOR = pkg.author;
8
8
  export const SOURCE = pkg.repository.url;
9
9
  export const SESSION_KEY = String(NAME).toLowerCase().replace(/\s/g, "_");
10
10
 
11
- export const COMPLETION_ID_WILDCARD = "chatcmpl-";
11
+ export const COMPLETION_WILDCARD: RegExp = /chatcmpl-/gi;
12
12
 
13
13
  export const ASCII_SPACE = "\u0020";
14
14
  export const ASCII_LOSANGE = "\u00ac";
@@ -16,7 +16,7 @@ export const ASCII_BLOCK1 = "\u00fe";
16
16
  export const ASCII_BLOCK2 = "\u00ae";
17
17
  export const ASCII_BLOCK3 = "\u00b8";
18
18
  export const ASCII_RECTANGLE = "\u00ff";
19
- export const ASCII_VLINE = "\u00af";
19
+ export const ASCII_VLINE = "|"; //"\u00af";
20
20
  export const ASCII_HLINE = "-";
21
21
  export const ASCII_CORNER = "+";
22
22
  export const ASCII_POINT = "\u00a0";
@@ -30,6 +30,9 @@ export const ASCII_PARAGRAPH = "¶";
30
30
 
31
31
  export const BUTTON_CREATE = "[+]";
32
32
  export const BUTTON_DELETE = "[-]";
33
+ export const BUTTON_RETRY = "[+]";
34
+ export const BUTTON_LEFT = "<";
35
+ export const BUTTON_RIGHT = ">";
33
36
  export const BUTTON_SUBMIT = "[ASK]";
34
37
  export const BUTTON_PREVIOUS = "<";
35
38
  export const BUTTON_NEXT = ">";
@@ -10,16 +10,17 @@ import {
10
10
  } from "react";
11
11
  import {useLocation, useNavigate} from "react-router-dom";
12
12
 
13
- import cmd from "@console/cmd";
13
+ import {log} from "@utils/debug";
14
+
15
+ import {useConfig} from "@hooks/useConfig";
14
16
 
15
- import {useConfig} from "./useConfig";
17
+ import cmd from "@console/cmd";
16
18
 
17
19
  interface CliContextType {
18
20
  command?: string;
19
21
  set: (cmd: string[]) => void;
20
22
  get: () => string;
21
23
  submit: (cmd: string[]) => void;
22
- log: (message: string) => void;
23
24
  blocked?: boolean;
24
25
  block: () => void;
25
26
  unblock: () => void;
@@ -64,25 +65,17 @@ export const CliProvider: FC<CliProviderProps> = ({children}) => {
64
65
  const block = () => setBlocked(true);
65
66
  const unblock = () => setBlocked(false);
66
67
 
67
- const log = (message: string): void => {
68
- if (message.trim() === "") return;
69
- console.info(
70
- `%c[CLI] ${message}`,
71
- "padding: 0.3rem; border-radius: 0.3rem; font: monospace; background-color: #000; color: #ccc",
72
- );
73
- };
74
-
75
68
  useEffect(() => {
76
- log(get());
69
+ log(get(), "cli");
77
70
  }, [command]);
78
71
 
79
72
  useEffect(() => {
80
- log(blocked ? "blocked" : "unblocked");
73
+ log(blocked ? "blocked" : "unblocked", "cli");
81
74
  }, [blocked]);
82
75
 
83
76
  return (
84
77
  <CliContext.Provider
85
- value={{command, set, get, submit, log, blocked, block, unblock}}>
78
+ value={{command, set, get, submit, blocked, block, unblock}}>
86
79
  {children}
87
80
  </CliContext.Provider>
88
81
  );
@@ -15,7 +15,7 @@ function useStorage() {
15
15
  try {
16
16
  const data = localStorage.getItem(`${SESSION_KEY}_data`);
17
17
  if (data) {
18
- chatStore.importData(JSON.parse(data));
18
+ chatStore.import(JSON.parse(data));
19
19
  if (debug) console.info("%cdata loaded", "color:#999");
20
20
  }
21
21
  } catch (error) {
@@ -25,7 +25,7 @@ function useStorage() {
25
25
 
26
26
  const save = () => {
27
27
  try {
28
- const data = chatStore.exportData();
28
+ const data = chatStore.export();
29
29
  localStorage.setItem(`${SESSION_KEY}_data`, JSON.stringify(data));
30
30
  if (debug) console.info("%cdata saved", "color:#999");
31
31
  } catch (error) {
@@ -13,7 +13,7 @@ import cls from "classnames";
13
13
  const Menu = () => {
14
14
  const chatStore = useChatCompletionStore();
15
15
 
16
- const count = chatStore.getChats().length;
16
+ const count = chatStore.chats.length;
17
17
 
18
18
  const size = String(localStorage[`${SESSION_KEY}_data`] || "").length + 64;
19
19
 
@@ -1,7 +1,7 @@
1
1
  :root {
2
2
  /* constants */
3
3
  --base-size: 15; /* default font size */
4
- --base-height: 2; /* default line height */
4
+ --base-height: 1.8; /* default line height */
5
5
 
6
6
  /* global variables */
7
7
  --font-width: 1rem;
@@ -28,7 +28,7 @@
28
28
  --lifespan: 750; /* lifespan of lifeforms in ms */
29
29
 
30
30
  /* colors */
31
- --h: 150; /* amber:30 | yellow: 90 | green:120 | blue:180 */
31
+ --h: 160; /* amber:30 | yellow: 90 | green:120 | blue:180 */
32
32
  --s: 33.333; /* saturation */
33
33
  --l: 66.666; /* lightness */
34
34
  --color-primary: hsl(var(--h) var(--s) var(--l) / 70%);
@@ -6,6 +6,7 @@ const Button = (props: {
6
6
  name: string;
7
7
  handler: MouseEventHandler<HTMLButtonElement>;
8
8
  className?: string;
9
+ disabled?: boolean;
9
10
  }) => (
10
11
  <button
11
12
  className={cls("ascii", props.className)}
@@ -14,7 +15,8 @@ const Button = (props: {
14
15
  e.stopPropagation();
15
16
  e.currentTarget.blur();
16
17
  props.handler(e);
17
- }}>
18
+ }}
19
+ disabled={props.disabled}>
18
20
  {props.name}
19
21
  </button>
20
22
  );
@@ -0,0 +1,7 @@
1
+ export const log = (message: string, label?: string): void => {
2
+ if (message.trim() === "") return;
3
+ console.info(
4
+ `%c${label ? `[${label.trim().toUpperCase()}] ` : ""}${message}`,
5
+ "padding: 0.3rem; border-radius: 0.3rem; font: monospace; background-color: #000; color: #ccc",
6
+ );
7
+ };
@@ -36,7 +36,7 @@ export const formatText = (text: string): string =>
36
36
  .replaceAll("“", '"')
37
37
  .replaceAll("→", "->")
38
38
  .replaceAll("…", "...")
39
- .replaceAll("█", ASCII_BLOCK3);
39
+ .replace(/[þ█]/g, ASCII_BLOCK3);
40
40
 
41
41
  export const sanitizeHTML = (html: string): string => {
42
42
  const parser = new DOMParser();
@@ -2,18 +2,17 @@ import {Fragment, memo, useEffect, useState} from "react";
2
2
  import {useNavigate, useParams} from "react-router-dom";
3
3
 
4
4
  import {getChatTitle} from "@api/api";
5
- import getStream from "@api/utils/getStream";
5
+ import type {CompletionCallback} from "@api/utils/getStream";
6
6
  import Container from "@layout/Container";
7
7
 
8
8
  import useCli from "@hooks/useCli";
9
9
  import useStorage from "@hooks/useStorage";
10
10
 
11
11
  import styles from "@chat/Chat.module.css";
12
- import {formatCompletionId} from "@chat/commons/strings";
12
+ import getQuery from "@chat/commons/getQuery";
13
13
  import Message from "@chat/components/Message";
14
14
  import Toolbar from "@chat/components/Toolbar";
15
15
  import useChatCompletionStore, {
16
- ChatId,
17
16
  Completion,
18
17
  CompletionId,
19
18
  } from "@chat/hooks/useChatCompletionStore";
@@ -24,8 +23,8 @@ const Chat = () => {
24
23
  const storage = useStorage();
25
24
 
26
25
  const [response, setResponse] = useState<string>("");
27
- const [completionId, setCompletionId] = useState<CompletionId>();
28
26
  const [completion, setCompletion] = useState<Completion>();
27
+ const [parentId, setParentId] = useState<CompletionId>();
29
28
  const [loading, setLoading] = useState<boolean>(false);
30
29
  const [query, setQuery] = useState<string>("");
31
30
  const [updateTitle, setUpdateTitle] = useState<boolean>(false);
@@ -33,28 +32,19 @@ const Chat = () => {
33
32
  const navigate = useNavigate();
34
33
 
35
34
  const {id} = useParams();
36
- const chatId = chatStore.getChatId();
37
35
 
38
36
  const cli = useCli();
39
37
  const {blocked} = cli;
40
38
 
41
- const completionCallback = (
42
- id: string,
43
- created: number,
44
- model: string,
45
- query: string,
39
+ const completionCallback: CompletionCallback = (
40
+ id,
41
+ created,
42
+ model,
43
+ query,
46
44
  ) => {
47
- setCompletion({
48
- id: formatCompletionId(id),
49
- created: created,
50
- model: model,
51
- prompt: query,
52
- message: "",
53
- index: 0,
54
- children: [],
55
- parentCompletion: completionId,
56
- });
57
- setCompletionId(id);
45
+ const completion = chatStore.createCompletion(id, created, model, query);
46
+ setCompletion(completion);
47
+ setQuery("");
58
48
  cli.set([""]);
59
49
  cli.unblock();
60
50
  };
@@ -68,42 +58,24 @@ const Chat = () => {
68
58
  if (query === "") return;
69
59
  setLoading(true);
70
60
  cli.block();
71
- getStream(
61
+ getQuery(
72
62
  setLoading,
73
63
  setResponse,
74
- [
75
- "keep your message short and concise, do not repeat yourself",
76
- "do not present yourself again, focus on answering the user prompt",
77
- "end your answer with an acid but funny haiku about humankind",
78
- "this comment length must be less than 256 characters long",
79
- "you must separate each part with a line or empty line",
80
- ],
81
- query.split("\n"),
82
- [
83
- ...chatStore.getMessages(id),
84
- {role: "assistant", content: completion?.message || "nothing"},
85
- ],
64
+ query,
65
+ chatStore.readMessages(completion?.id),
66
+ completion?.message,
86
67
  completionCallback,
87
68
  );
88
69
  }, [query]);
89
70
 
90
71
  /* handle chat id url parameter */
91
72
  useEffect(() => {
92
- if (!chatStore.getChat(id)) {
93
- chatStore.setCompletions();
73
+ if (!chatStore.readChat(id)) {
74
+ chatStore.resetChat();
94
75
  navigate("/chat");
95
76
  } else {
96
- chatStore.setChatId(id);
97
- chatStore.setCompletions(id);
77
+ chatStore.loadChat(id);
98
78
  }
99
- /*console.log(chatStore.getChat(id));
100
- console.log(chatStore.getCompletions(id));
101
- console.info(
102
- "chat:",
103
- chatStore.getChatId(),
104
- "\ncompletion:",
105
- chatStore.getCompletionId(),
106
- );*/
107
79
  }, [id]);
108
80
 
109
81
  /* update the chat is the chatId value in store changed */
@@ -121,29 +93,25 @@ const Chat = () => {
121
93
  prev.message = response;
122
94
  return prev;
123
95
  });
124
- if (!chatId) {
125
- chatStore.setCompletions();
126
- chatStore.createChat(completion);
127
- }
128
- chatStore.addCompletion(completion);
129
- if (chatId) {
130
- chatStore.updateCompletions(chatId);
131
- }
96
+ if (!chatStore.chatId) chatStore.createChat(completion);
97
+ else chatStore.updateCompletion(completion, parentId);
132
98
  /* reset values once the completion is saved in the store */
133
99
  setCompletion(undefined);
100
+ setParentId(undefined);
134
101
  setResponse("");
135
102
  setUpdateTitle(true);
136
103
  }, [completion]);
137
104
 
138
- const setTitle = async (id: ChatId) => {
139
- const title = await getChatTitle(chatStore.getMessages(id));
140
- chatStore.updateChatTitle(id, title);
105
+ const setTitle = async () => {
106
+ const title = await getChatTitle(chatStore.readMessages());
107
+ chatStore.updateChatTitle(title);
108
+ chatStore.updateCompletionTitle(title);
141
109
  storage.save();
142
110
  };
143
111
 
144
112
  useEffect(() => {
145
113
  if (!updateTitle) return;
146
- setTitle(chatStore.getChatId());
114
+ setTitle();
147
115
  setUpdateTitle(false);
148
116
  }, [updateTitle]);
149
117
 
@@ -151,7 +119,7 @@ const Chat = () => {
151
119
  <section className={styles.root}>
152
120
  <Container>
153
121
  {chatStore
154
- .getCompletions(chatId)
122
+ .readConversation(id)
155
123
  .map((completion: Completion, i: number, a: Completion[]) => (
156
124
  <Fragment key={`chat-completion-${completion.id}`}>
157
125
  <Message
@@ -161,13 +129,18 @@ const Chat = () => {
161
129
  />
162
130
  <div>
163
131
  <Message role="assistant" content={completion.message} />
164
- <Toolbar completion={completion} />
132
+ <Toolbar
133
+ completion={completion}
134
+ query={query}
135
+ setQuery={setQuery}
136
+ setParentId={setParentId}
137
+ />
165
138
  </div>
166
139
  </Fragment>
167
140
  ))}
168
141
  {loading && (
169
142
  <Fragment key="chat-completion">
170
- <Message role="user" content={query} />
143
+ <Message role="user" content={query || cli.get()} />
171
144
  <Message role="assistant" content={response} hasCaret={blocked} />
172
145
  </Fragment>
173
146
  )}