omnibot3000 1.8.2
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/.prettierrc +18 -0
- package/README.md +9 -0
- package/api/server.ts +153 -0
- package/eslint.config.js +103 -0
- package/index.html +22 -0
- package/netlify.toml +9 -0
- package/nodemon.json +4 -0
- package/omnibot3000.code-workspace +55 -0
- package/package.json +58 -0
- package/pnpm-workspace.yaml +2 -0
- package/public/fonts/vt220.woff2 +0 -0
- package/src/App.module.css +128 -0
- package/src/App.tsx +193 -0
- package/src/Error.tsx +80 -0
- package/src/commons/OmnibotSpeak.module.css +43 -0
- package/src/commons/OmnibotSpeak.tsx +31 -0
- package/src/commons/api/api.ts +150 -0
- package/src/commons/constants.ts +34 -0
- package/src/commons/favicon.ts +69 -0
- package/src/commons/hooks/useConfig.tsx +50 -0
- package/src/commons/hooks/useKeyPress.tsx +76 -0
- package/src/commons/hooks/useStorage.tsx +38 -0
- package/src/commons/layout/Background.module.css +47 -0
- package/src/commons/layout/Background.tsx +138 -0
- package/src/commons/layout/Breadcrumb.module.css +28 -0
- package/src/commons/layout/Breadcrumb.tsx +54 -0
- package/src/commons/layout/Container.module.css +51 -0
- package/src/commons/layout/Container.tsx +60 -0
- package/src/commons/layout/Footer.module.css +36 -0
- package/src/commons/layout/Footer.tsx +74 -0
- package/src/commons/layout/Header.module.css +73 -0
- package/src/commons/layout/Header.tsx +102 -0
- package/src/commons/layout/Menu.module.css +36 -0
- package/src/commons/layout/Menu.tsx +37 -0
- package/src/commons/persona.txt +38 -0
- package/src/commons/styles/debug.css +71 -0
- package/src/commons/styles/main.css +221 -0
- package/src/commons/styles/vt220.css +69 -0
- package/src/commons/ui/Button.tsx +22 -0
- package/src/commons/ui/Caret.tsx +20 -0
- package/src/commons/ui/Line.tsx +64 -0
- package/src/commons/ui/ScrollSnap.tsx +51 -0
- package/src/commons/ui/Separator.tsx +19 -0
- package/src/commons/ui/Spacer.tsx +12 -0
- package/src/commons/utils/canvas.ts +20 -0
- package/src/commons/utils/color.ts +4 -0
- package/src/commons/utils/math.ts +43 -0
- package/src/commons/utils/strings.ts +47 -0
- package/src/commons/utils/styles.ts +11 -0
- package/src/commons/utils/system.ts +6 -0
- package/src/commons/utils/version.ts +24 -0
- package/src/features/chat/Chat.module.css +8 -0
- package/src/features/chat/Chat.tsx +188 -0
- package/src/features/chat/commons/strings.ts +6 -0
- package/src/features/chat/components/Message.module.css +28 -0
- package/src/features/chat/components/Message.tsx +45 -0
- package/src/features/chat/components/Toolbar.module.css +19 -0
- package/src/features/chat/components/Toolbar.tsx +44 -0
- package/src/features/chat/hooks/useChatCompletionStore.tsx +160 -0
- package/src/features/cli/Cli.module.css +75 -0
- package/src/features/cli/Cli.tsx +303 -0
- package/src/features/console/cmd.ts +93 -0
- package/src/features/console/config.ts +106 -0
- package/src/features/help/Help.module.css +8 -0
- package/src/features/help/Help.tsx +78 -0
- package/src/features/history/History.module.css +77 -0
- package/src/features/history/History.tsx +92 -0
- package/src/features/home/Home.module.css +26 -0
- package/src/features/home/Home.tsx +101 -0
- package/src/features/life/Life.module.css +8 -0
- package/src/features/life/Life.tsx +16 -0
- package/src/features/life/generation.ts +103 -0
- package/src/features/life/lifeforms.ts +138 -0
- package/src/features/life/types.ts +5 -0
- package/src/features/version/Version.module.css +8 -0
- package/src/features/version/Version.tsx +70 -0
- package/src/global.d.ts +10 -0
- package/src/main.tsx +32 -0
- package/src/vite-env.d.ts +16 -0
- package/tsconfig.api.json +16 -0
- package/tsconfig.json +47 -0
- package/upgrade.sh +22 -0
- package/vite.config.ts +51 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React, {memo, useEffect, useRef} from "react";
|
|
2
|
+
|
|
3
|
+
import {ASCII_DOT} from "@commons/constants";
|
|
4
|
+
import {getLineHeight} from "@utils/strings";
|
|
5
|
+
|
|
6
|
+
const ScrollSnap = (props: {
|
|
7
|
+
content: React.RefObject<HTMLDivElement | null>;
|
|
8
|
+
className?: string;
|
|
9
|
+
}) => {
|
|
10
|
+
const lh = getLineHeight();
|
|
11
|
+
|
|
12
|
+
const snapRef = useRef<HTMLUListElement>(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const ul = snapRef.current;
|
|
16
|
+
const content = props.content.current as HTMLDivElement | null;
|
|
17
|
+
if (!content || !ul) return;
|
|
18
|
+
|
|
19
|
+
const update = () => {
|
|
20
|
+
ul.innerHTML = "";
|
|
21
|
+
const h = content.clientHeight;
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < Math.round(h / lh); i++) {
|
|
24
|
+
const li = document.createElement("li");
|
|
25
|
+
li.innerHTML = ASCII_DOT;
|
|
26
|
+
ul.appendChild(li);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
update();
|
|
31
|
+
|
|
32
|
+
const resizeObserver = new ResizeObserver(update);
|
|
33
|
+
resizeObserver.observe(content);
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
resizeObserver.disconnect();
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<ul
|
|
42
|
+
ref={snapRef}
|
|
43
|
+
className={props.className}
|
|
44
|
+
style={{
|
|
45
|
+
userSelect: "none",
|
|
46
|
+
cursor: "default",
|
|
47
|
+
}}></ul>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default memo(ScrollSnap);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {memo} from "react";
|
|
2
|
+
|
|
3
|
+
import {ASCII_VLINE} from "@commons/constants";
|
|
4
|
+
|
|
5
|
+
const Separator = (props: {char?: string}) => (
|
|
6
|
+
<div
|
|
7
|
+
style={{
|
|
8
|
+
flexShrink: 0,
|
|
9
|
+
flexGrow: 0,
|
|
10
|
+
alignSelf: "start",
|
|
11
|
+
justifySelf: "center",
|
|
12
|
+
opacity: "var(--opacity-secondary)",
|
|
13
|
+
textTransform: "none",
|
|
14
|
+
userSelect: "none",
|
|
15
|
+
cursor: "default",
|
|
16
|
+
}}>{` ${String(props.char || "").trim() || ASCII_VLINE} `}</div>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export default memo(Separator);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {HALF_PI, PI, TWO_PI} from "@utils/math";
|
|
2
|
+
|
|
3
|
+
export const squircle = (
|
|
4
|
+
ctx: CanvasRenderingContext2D,
|
|
5
|
+
x: number,
|
|
6
|
+
y: number,
|
|
7
|
+
w: number,
|
|
8
|
+
h: number,
|
|
9
|
+
r: number,
|
|
10
|
+
) => {
|
|
11
|
+
ctx.beginPath();
|
|
12
|
+
ctx.arc(x + r, y + r, r, PI, PI + HALF_PI);
|
|
13
|
+
ctx.lineTo(x + w - r, y);
|
|
14
|
+
ctx.arc(x + w - r, y + r, r, PI + HALF_PI, TWO_PI);
|
|
15
|
+
ctx.lineTo(x + w, y + h - r);
|
|
16
|
+
ctx.arc(x + w - r, y + h - r, r, 0, HALF_PI);
|
|
17
|
+
ctx.lineTo(x + r, y + h);
|
|
18
|
+
ctx.arc(x + r, y + h - r, r, HALF_PI, PI);
|
|
19
|
+
ctx.closePath();
|
|
20
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const PI = Math.PI; /* 180° in radians */
|
|
2
|
+
export const HALF_PI = Math.PI / 2; /* 90° in radians */
|
|
3
|
+
export const TWO_PI = Math.PI * 2; /* 360° in radians */
|
|
4
|
+
export const PHI = (Math.sqrt(5) + 1) / 2; /* golden ratio */
|
|
5
|
+
export const EULER = Math.E;
|
|
6
|
+
|
|
7
|
+
export type vec2 = [number, number];
|
|
8
|
+
|
|
9
|
+
export const format = (n: number = 0, d: number = 1): number =>
|
|
10
|
+
parseFloat(n.toFixed(d));
|
|
11
|
+
|
|
12
|
+
export const clamp = (
|
|
13
|
+
n: number = 0,
|
|
14
|
+
min: number = 0,
|
|
15
|
+
max: number = 1,
|
|
16
|
+
): number => Math.max(min, Math.min(n, max));
|
|
17
|
+
|
|
18
|
+
const ROMAN_SYMBOLS = [
|
|
19
|
+
{value: 1000, symbol: "M"},
|
|
20
|
+
{value: 900, symbol: "CM"},
|
|
21
|
+
{value: 500, symbol: "D"},
|
|
22
|
+
{value: 400, symbol: "CD"},
|
|
23
|
+
{value: 100, symbol: "C"},
|
|
24
|
+
{value: 90, symbol: "XC"},
|
|
25
|
+
{value: 50, symbol: "L"},
|
|
26
|
+
{value: 40, symbol: "XL"},
|
|
27
|
+
{value: 10, symbol: "X"},
|
|
28
|
+
{value: 9, symbol: "IX"},
|
|
29
|
+
{value: 5, symbol: "V"},
|
|
30
|
+
{value: 4, symbol: "IV"},
|
|
31
|
+
{value: 1, symbol: "I"},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const numberToRoman = (n: number = 0): string => {
|
|
35
|
+
let string = "";
|
|
36
|
+
for (const {value, symbol} of ROMAN_SYMBOLS) {
|
|
37
|
+
while (n >= value && n !== 0) {
|
|
38
|
+
string += symbol;
|
|
39
|
+
n -= value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return string;
|
|
43
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {ASCII_BLOCK1} from "@commons/constants";
|
|
2
|
+
|
|
3
|
+
export const getTextBoundingBox = (text: string): DOMRect => {
|
|
4
|
+
const el = document.createElement("span");
|
|
5
|
+
el.className = "text ascii";
|
|
6
|
+
el.textContent = text;
|
|
7
|
+
el.style.visibility = "none";
|
|
8
|
+
document.body.appendChild(el);
|
|
9
|
+
const box = el.getBoundingClientRect();
|
|
10
|
+
document.body.removeChild(el);
|
|
11
|
+
return box;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getCharWidth = (): number =>
|
|
15
|
+
getTextBoundingBox(ASCII_BLOCK1).width;
|
|
16
|
+
export const getCharHeight = (): number =>
|
|
17
|
+
getTextBoundingBox(ASCII_BLOCK1).height;
|
|
18
|
+
|
|
19
|
+
export const getLineHeight = (): number => {
|
|
20
|
+
const el = document.createElement("span");
|
|
21
|
+
el.className = "text ascii";
|
|
22
|
+
el.textContent = ASCII_BLOCK1;
|
|
23
|
+
el.style.visibility = "none";
|
|
24
|
+
document.body.appendChild(el);
|
|
25
|
+
const lh = parseFloat(getComputedStyle(el).lineHeight);
|
|
26
|
+
document.body.removeChild(el);
|
|
27
|
+
return lh;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const formatText = (text: string): string =>
|
|
31
|
+
text
|
|
32
|
+
.replaceAll("—", "-")
|
|
33
|
+
.replaceAll("–", "-")
|
|
34
|
+
.replaceAll("’", "'")
|
|
35
|
+
.replaceAll("“", '"')
|
|
36
|
+
.replaceAll("”", '"')
|
|
37
|
+
.replaceAll("→", "->");
|
|
38
|
+
|
|
39
|
+
export const sanitizeHTML = (html: string): string => {
|
|
40
|
+
const parser = new DOMParser();
|
|
41
|
+
const doc = parser.parseFromString(html, "text/html");
|
|
42
|
+
const body = new XMLSerializer().serializeToString(doc.body);
|
|
43
|
+
const text = body.replace(/<body[^>]*>|<\/body>/g, "");
|
|
44
|
+
const textarea = document.createElement("textarea");
|
|
45
|
+
textarea.innerHTML = text;
|
|
46
|
+
return textarea.value;
|
|
47
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const getVariableFromCSS = (variable: string): string =>
|
|
2
|
+
getComputedStyle(document.documentElement)
|
|
3
|
+
.getPropertyValue(`--${variable}`)
|
|
4
|
+
.trim();
|
|
5
|
+
|
|
6
|
+
export const setVariableToCSS = (variable: string, value: string | null) => {
|
|
7
|
+
document.documentElement.style.setProperty(
|
|
8
|
+
`--${variable}`,
|
|
9
|
+
value ? String(value).trim() : null,
|
|
10
|
+
);
|
|
11
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {NAME, VERSION} from "@commons/constants";
|
|
2
|
+
import {getColorFromCSS} from "@utils/color";
|
|
3
|
+
|
|
4
|
+
export const PACKAGE_VERSION = `${NAME} version ${VERSION.join(".")}`;
|
|
5
|
+
|
|
6
|
+
export const displayPackageVersion = (): void => {
|
|
7
|
+
const CHAR = "░";
|
|
8
|
+
const len = PACKAGE_VERSION.length;
|
|
9
|
+
|
|
10
|
+
const lines = [
|
|
11
|
+
String(CHAR).repeat(len + 6),
|
|
12
|
+
`${CHAR}${String(" ").repeat(len + 4)}${CHAR}`,
|
|
13
|
+
`${CHAR} ${PACKAGE_VERSION} ${CHAR}`,
|
|
14
|
+
`${CHAR}${String(" ").repeat(len + 4)}${CHAR}`,
|
|
15
|
+
String(CHAR).repeat(len + 6),
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
console.info(
|
|
19
|
+
`%c${lines.join("\n")}`,
|
|
20
|
+
`font:15px monospace;color:${getColorFromCSS("primary")};background:${getColorFromCSS("background")}`,
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default PACKAGE_VERSION;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {Fragment, memo, useEffect, useState} from "react";
|
|
2
|
+
import {useNavigate, useParams} from "react-router-dom";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ChatCompletionChunk,
|
|
6
|
+
ChatCompletionMessageParam,
|
|
7
|
+
} from "openai/resources/index.mjs";
|
|
8
|
+
import {Stream} from "openai/streaming.mjs";
|
|
9
|
+
|
|
10
|
+
import getData, {getChatTitle, getSystemConfig} from "@api/api";
|
|
11
|
+
import Container from "@layout/Container";
|
|
12
|
+
|
|
13
|
+
import useStorage from "@hooks/useStorage";
|
|
14
|
+
|
|
15
|
+
import styles from "@chat/Chat.module.css";
|
|
16
|
+
import {formatCompletionId} from "@chat/commons/strings";
|
|
17
|
+
import Message from "@chat/components/Message";
|
|
18
|
+
import Toolbar from "@chat/components/Toolbar";
|
|
19
|
+
import useChatCompletionStore, {
|
|
20
|
+
ChatId,
|
|
21
|
+
Completion,
|
|
22
|
+
CompletionId,
|
|
23
|
+
} from "@chat/hooks/useChatCompletionStore";
|
|
24
|
+
|
|
25
|
+
import Cli from "@cli/Cli";
|
|
26
|
+
|
|
27
|
+
const Chat = () => {
|
|
28
|
+
const chatStore = useChatCompletionStore();
|
|
29
|
+
|
|
30
|
+
const storage = useStorage();
|
|
31
|
+
|
|
32
|
+
const [prompt, setPrompt] = useState<string[]>([""]);
|
|
33
|
+
const [response, setResponse] = useState<string>("");
|
|
34
|
+
const [completionId, setCompletionId] = useState<CompletionId>();
|
|
35
|
+
const [completion, setCompletion] = useState<Completion>();
|
|
36
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
37
|
+
const [query, setQuery] = useState<string>("");
|
|
38
|
+
|
|
39
|
+
const navigate = useNavigate();
|
|
40
|
+
|
|
41
|
+
const {id} = useParams();
|
|
42
|
+
const chatId = chatStore.getChatId();
|
|
43
|
+
|
|
44
|
+
const setTitle = async (id: ChatId) => {
|
|
45
|
+
const title = await getChatTitle(chatStore.getMessages(id));
|
|
46
|
+
chatStore.updateChatTitle(id, title);
|
|
47
|
+
storage.save();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/* handle chat id url parameter */
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!chatStore.getChat(id)) {
|
|
53
|
+
chatStore.setCompletions();
|
|
54
|
+
navigate("/chat");
|
|
55
|
+
} else {
|
|
56
|
+
chatStore.setChatId(id);
|
|
57
|
+
chatStore.setCompletions(id);
|
|
58
|
+
}
|
|
59
|
+
/*console.log(chatStore.getChat(id));
|
|
60
|
+
console.log(chatStore.getCompletions(id));
|
|
61
|
+
console.info(
|
|
62
|
+
"chat:",
|
|
63
|
+
chatStore.getChatId(),
|
|
64
|
+
"\ncompletion:",
|
|
65
|
+
chatStore.getCompletionId(),
|
|
66
|
+
);*/
|
|
67
|
+
}, [id]);
|
|
68
|
+
|
|
69
|
+
/* update the chat is the chatId value in store changed */
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const unsubscribe = useChatCompletionStore.subscribe((state) => {
|
|
72
|
+
navigate(`/chat${state.chatId ? `/${state.chatId}` : ""}`);
|
|
73
|
+
});
|
|
74
|
+
return () => unsubscribe();
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const getCompletion = async (query: string) => {
|
|
78
|
+
setQuery(query); /* save the query before reset */
|
|
79
|
+
|
|
80
|
+
if (String(query).replace("\n", "").trim() === "") return;
|
|
81
|
+
|
|
82
|
+
setLoading(true);
|
|
83
|
+
|
|
84
|
+
const messages: ChatCompletionMessageParam[] = [getSystemConfig()];
|
|
85
|
+
|
|
86
|
+
messages.push({
|
|
87
|
+
role: "developer",
|
|
88
|
+
content: `\
|
|
89
|
+
end all messages with a short, acid and fun commment about humankind weakness. \
|
|
90
|
+
keep your message short, do not write more than 256 characters as comment. \
|
|
91
|
+
you must separate each part of your answer with an empty line.`,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/* add chat history to the messages array to give context */
|
|
95
|
+
if (chatId) {
|
|
96
|
+
messages.push(...chatStore.getMessages(chatId));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* append current query */
|
|
100
|
+
messages.push({role: "user", content: query});
|
|
101
|
+
|
|
102
|
+
const stream = (await getData(messages)) as Stream<ChatCompletionChunk>;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
for await (const chunk of stream) {
|
|
106
|
+
const choice = chunk.choices?.[0] || {};
|
|
107
|
+
const finish_reason = choice.finish_reason;
|
|
108
|
+
const text = choice.delta?.content || "";
|
|
109
|
+
|
|
110
|
+
if (finish_reason) {
|
|
111
|
+
setLoading(false);
|
|
112
|
+
if (finish_reason === "length") {
|
|
113
|
+
setResponse((prev) => `${prev}\n\n[max tokens length reached]\n`);
|
|
114
|
+
}
|
|
115
|
+
setCompletion({
|
|
116
|
+
id: formatCompletionId(chunk.id),
|
|
117
|
+
created: chunk.created,
|
|
118
|
+
model: chunk.model,
|
|
119
|
+
prompt: query,
|
|
120
|
+
message: "",
|
|
121
|
+
index: 0,
|
|
122
|
+
children: [],
|
|
123
|
+
parentCompletion: completionId,
|
|
124
|
+
});
|
|
125
|
+
setCompletionId(chunk.id);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (text) {
|
|
129
|
+
setResponse((prev) => `${prev}${text}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("Error reading stream:", error);
|
|
134
|
+
setLoading(false);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (completion) {
|
|
140
|
+
setCompletion((prev) => {
|
|
141
|
+
if (!prev) return;
|
|
142
|
+
prev.message = response;
|
|
143
|
+
return prev;
|
|
144
|
+
});
|
|
145
|
+
if (!chatId) {
|
|
146
|
+
chatStore.setCompletions();
|
|
147
|
+
chatStore.createChat(completion);
|
|
148
|
+
setTitle(chatStore.getChatId());
|
|
149
|
+
}
|
|
150
|
+
chatStore.addCompletion(completion);
|
|
151
|
+
if (chatId) {
|
|
152
|
+
chatStore.updateCompletions(chatId);
|
|
153
|
+
setTitle(chatId);
|
|
154
|
+
}
|
|
155
|
+
/* reset values once the completion is saved in the store */
|
|
156
|
+
setCompletion(undefined);
|
|
157
|
+
setResponse("");
|
|
158
|
+
}
|
|
159
|
+
}, [completion]);
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div className={styles.root}>
|
|
163
|
+
<Container>
|
|
164
|
+
{chatStore.getCompletions(chatId).map((completion: Completion) => (
|
|
165
|
+
<Fragment key={`chat-completion-${completion.id}`}>
|
|
166
|
+
<Message role="user" content={completion.prompt} />
|
|
167
|
+
<Message role="assistant" content={completion.message} />
|
|
168
|
+
<Toolbar completion={completion} />
|
|
169
|
+
</Fragment>
|
|
170
|
+
))}
|
|
171
|
+
{loading && response && (
|
|
172
|
+
<Fragment key="chat-completion">
|
|
173
|
+
<Message role="user" content={query} />
|
|
174
|
+
<Message role="assistant" content={response} hasCaret={loading} />
|
|
175
|
+
</Fragment>
|
|
176
|
+
)}
|
|
177
|
+
</Container>
|
|
178
|
+
<Cli
|
|
179
|
+
loading={loading}
|
|
180
|
+
prompt={prompt}
|
|
181
|
+
setPrompt={setPrompt}
|
|
182
|
+
submitHandler={getCompletion}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export default memo(Chat);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
width: 100%;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.user {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: row;
|
|
8
|
+
column-gap: var(--font-width);
|
|
9
|
+
margin-bottom: var(--line-height);
|
|
10
|
+
margin-top: var(--line-height);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.user-pill {
|
|
14
|
+
opacity: var(--opacity-tertiary);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.bot {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: row;
|
|
20
|
+
column-gap: var(--font-width);
|
|
21
|
+
align-items: stretch;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.bot-line {
|
|
25
|
+
flex-grow: 0;
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
opacity: var(--opacity-tertiary);
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {memo} from "react";
|
|
2
|
+
|
|
3
|
+
import type {ChatCompletionRole} from "openai/resources/index.mjs";
|
|
4
|
+
|
|
5
|
+
import {ASCII_BLOCK1} from "@commons/constants";
|
|
6
|
+
import OmnibotSpeak from "@commons/OmnibotSpeak";
|
|
7
|
+
import Line from "@ui/Line";
|
|
8
|
+
|
|
9
|
+
import styles from "@chat/components/Message.module.css";
|
|
10
|
+
|
|
11
|
+
import {RenderCli} from "@cli/Cli";
|
|
12
|
+
|
|
13
|
+
const Message = (props: {
|
|
14
|
+
role: ChatCompletionRole;
|
|
15
|
+
content: string;
|
|
16
|
+
hasCaret?: boolean;
|
|
17
|
+
}) => {
|
|
18
|
+
const {role, content, hasCaret} = props;
|
|
19
|
+
|
|
20
|
+
const isUser = Boolean(role === "user");
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className={styles.root}>
|
|
24
|
+
{isUser ? (
|
|
25
|
+
<div className={styles.user}>
|
|
26
|
+
<div className={styles["user-pill"]}>{">"}</div>
|
|
27
|
+
<div>
|
|
28
|
+
<RenderCli command={content.split("\n")} line={-1} caret={0} />
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
) : (
|
|
32
|
+
<div className={styles.bot}>
|
|
33
|
+
<Line
|
|
34
|
+
variant="vertical"
|
|
35
|
+
char={ASCII_BLOCK1}
|
|
36
|
+
className={styles["bot-line"]}
|
|
37
|
+
/>
|
|
38
|
+
<OmnibotSpeak truth={content} hasCaret={hasCaret} />
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default memo(Message);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
margin-top: var(--line-height);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.corner {
|
|
8
|
+
opacity: var(--opacity-tertiary);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.line {
|
|
12
|
+
width: 100%;
|
|
13
|
+
opacity: var(--opacity-tertiary);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.toolbar {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: row;
|
|
19
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {memo} from "react";
|
|
2
|
+
|
|
3
|
+
import {BUTTON_DELETE} from "@commons/constants";
|
|
4
|
+
import Button from "@ui/Button";
|
|
5
|
+
import Line from "@ui/Line";
|
|
6
|
+
|
|
7
|
+
import useStorage from "@hooks/useStorage";
|
|
8
|
+
|
|
9
|
+
import styles from "@chat/components/Toolbar.module.css";
|
|
10
|
+
import useChatCompletionStore, {
|
|
11
|
+
Completion,
|
|
12
|
+
CompletionId,
|
|
13
|
+
} from "@chat/hooks/useChatCompletionStore";
|
|
14
|
+
|
|
15
|
+
import cls from "classnames";
|
|
16
|
+
|
|
17
|
+
const Toolbar = (props: {completion: Completion}) => {
|
|
18
|
+
const chatStore = useChatCompletionStore();
|
|
19
|
+
const storage = useStorage();
|
|
20
|
+
|
|
21
|
+
const deleteCompletion = (id: CompletionId) => {
|
|
22
|
+
chatStore.deleteCompletion(id);
|
|
23
|
+
storage.save();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const {completion} = props;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={styles.root}>
|
|
30
|
+
<div className={styles.corner}>+</div>
|
|
31
|
+
<Line variant="horizontal" className={styles["line"]} />
|
|
32
|
+
<div className={cls("text", styles.toolbar)}>
|
|
33
|
+
<Button
|
|
34
|
+
name={BUTTON_DELETE}
|
|
35
|
+
handler={() => {
|
|
36
|
+
deleteCompletion(completion.id);
|
|
37
|
+
}}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default memo(Toolbar);
|