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.
- package/.github/workflows/tag.yml +38 -0
- package/.husky/commit-msg +2 -1
- package/TODO.md +33 -0
- package/api/server.ts +2 -1
- package/{tsconfig.api.json → api/tsconfig.json} +4 -4
- package/nodemon.json +1 -1
- package/omnibot3000.code-workspace +36 -0
- package/package.json +22 -22
- package/src/commons/api/api.ts +3 -3
- package/src/commons/api/utils/getStream.ts +8 -6
- package/src/commons/constants.ts +5 -2
- package/src/commons/hooks/useCli.tsx +7 -14
- package/src/commons/hooks/useStorage.tsx +2 -2
- package/src/commons/layout/Menu.tsx +1 -1
- package/src/commons/styles/main.css +2 -2
- package/src/commons/ui/Button.tsx +3 -1
- package/src/commons/utils/debug.ts +7 -0
- package/src/commons/utils/strings.ts +1 -1
- package/src/features/chat/Chat.tsx +34 -61
- package/src/features/chat/commons/getQuery.ts +34 -0
- package/src/features/chat/commons/strings.ts +13 -3
- package/src/features/chat/components/Toolbar.tsx +62 -4
- package/src/features/chat/hooks/useChatCompletionStore.tsx +339 -84
- package/src/features/cli/Cli.tsx +7 -5
- package/src/features/history/History.tsx +2 -8
- package/src/features/home/Home.tsx +1 -4
- package/src/features/version/Version.tsx +33 -6
- package/tsconfig.json +1 -1
- package/upgrade.sh +19 -1
- package/vite.config.ts +7 -1
|
@@ -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": "./
|
|
12
|
-
"outDir": "
|
|
12
|
+
"rootDir": "./",
|
|
13
|
+
"outDir": "../dist/api/"
|
|
13
14
|
},
|
|
14
|
-
"include": ["
|
|
15
|
-
"exclude": []
|
|
15
|
+
"include": ["./**/*.ts"]
|
|
16
16
|
}
|
package/nodemon.json
CHANGED
|
@@ -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.
|
|
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
|
|
23
|
-
"dotenv": "^17.
|
|
24
|
-
"openai": "^6.
|
|
25
|
-
"react": "^19.2.
|
|
26
|
-
"react-dom": "^19.2.
|
|
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.
|
|
29
|
-
"zustand": "^5.0.
|
|
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.
|
|
34
|
-
"@types/react": "^19.2.
|
|
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.
|
|
36
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
37
37
|
"classnames": "^2.5.1",
|
|
38
|
-
"eslint": "^10.
|
|
38
|
+
"eslint": "^10.4.0",
|
|
39
39
|
"eslint-plugin-import": "^2.32.0",
|
|
40
|
-
"eslint-plugin-react-hooks": "^7.
|
|
40
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
41
41
|
"eslint-plugin-react-refresh": "^0.5.2",
|
|
42
|
-
"eslint-plugin-simple-import-sort": "^
|
|
43
|
-
"globals": "^17.
|
|
42
|
+
"eslint-plugin-simple-import-sort": "^13.0.0",
|
|
43
|
+
"globals": "^17.6.0",
|
|
44
44
|
"husky": "^9.1.7",
|
|
45
|
-
"knip": "^6.
|
|
45
|
+
"knip": "^6.14.2",
|
|
46
46
|
"nodemon": "^3.1.14",
|
|
47
47
|
"npm-run-all": "^4.1.5",
|
|
48
|
-
"prettier": "^3.8.
|
|
48
|
+
"prettier": "^3.8.3",
|
|
49
49
|
"react-refresh": "^0.18.0",
|
|
50
|
-
"stylelint": "^17.
|
|
50
|
+
"stylelint": "^17.12.0",
|
|
51
51
|
"stylelint-config-standard": "^40.0.0",
|
|
52
|
-
"typescript": "^6.0.
|
|
53
|
-
"typescript-eslint": "^8.
|
|
54
|
-
"vite": "^8.0.
|
|
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.
|
|
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",
|
package/src/commons/api/api.ts
CHANGED
|
@@ -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
|
|
76
|
+
"use only user and assistant messages as context",
|
|
77
77
|
...smallQueryFormatting(28),
|
|
78
78
|
],
|
|
79
79
|
[
|
|
80
|
-
"
|
|
81
|
-
"do not answer to the query, just provide a
|
|
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[] = [
|
package/src/commons/constants.ts
CHANGED
|
@@ -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
|
|
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
|
|
13
|
+
import {log} from "@utils/debug";
|
|
14
|
+
|
|
15
|
+
import {useConfig} from "@hooks/useConfig";
|
|
14
16
|
|
|
15
|
-
import
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
-
.
|
|
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
|
|
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
|
|
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
|
|
43
|
-
created
|
|
44
|
-
model
|
|
45
|
-
query
|
|
39
|
+
const completionCallback: CompletionCallback = (
|
|
40
|
+
id,
|
|
41
|
+
created,
|
|
42
|
+
model,
|
|
43
|
+
query,
|
|
46
44
|
) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
61
|
+
getQuery(
|
|
72
62
|
setLoading,
|
|
73
63
|
setResponse,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
93
|
-
chatStore.
|
|
73
|
+
if (!chatStore.readChat(id)) {
|
|
74
|
+
chatStore.resetChat();
|
|
94
75
|
navigate("/chat");
|
|
95
76
|
} else {
|
|
96
|
-
chatStore.
|
|
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
|
-
|
|
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 (
|
|
139
|
-
const title = await getChatTitle(chatStore.
|
|
140
|
-
chatStore.updateChatTitle(
|
|
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(
|
|
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
|
-
.
|
|
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
|
|
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
|
)}
|