omnibot3000 1.8.6 → 1.8.7
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/api/server.ts +21 -16
- package/package.json +2 -2
- package/src/App.tsx +2 -2
- package/src/commons/api/api.ts +36 -27
- package/src/commons/api/utils/getData.ts +1 -1
- package/src/commons/api/utils/getStream.ts +1 -1
- package/src/commons/layout/Footer.module.css +1 -0
- package/src/commons/layout/Header.module.css +4 -2
- package/src/commons/layout/Header.tsx +10 -10
- package/src/commons/styles/debug.css +2 -1
- package/src/commons/styles/main.css +4 -4
- package/src/features/chat/Chat.tsx +19 -10
package/api/server.ts
CHANGED
|
@@ -27,35 +27,35 @@ type Package = {
|
|
|
27
27
|
size: number;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
type Provider = "openai" | "mistral";
|
|
31
|
-
|
|
32
|
-
const MODEL: Provider = "openai";
|
|
33
|
-
const MAX_TOKENS = 1000;
|
|
34
|
-
|
|
35
30
|
const DOMAIN = process.env.DOMAIN || "localhost";
|
|
36
31
|
const API_PATH = process.env.API_PATH || "/api";
|
|
37
32
|
const API_PORT = process.env.API_PORT || 3001;
|
|
38
33
|
const BASE_PATH = process.cwd();
|
|
39
34
|
const JSON_PATH = path.join(BASE_PATH, "dist", "packages.json");
|
|
40
35
|
|
|
36
|
+
type Provider = "openai" | "mistral";
|
|
37
|
+
|
|
38
|
+
export const MODEL: Provider = "openai";
|
|
39
|
+
const MAX_TOKENS = 1000;
|
|
40
|
+
|
|
41
41
|
type OpenAIConfig = Omit<ChatCompletionCreateParamsNonStreaming, "messages">;
|
|
42
42
|
type MistralConfig = Omit<ChatCompletionRequest, "messages">;
|
|
43
43
|
|
|
44
|
-
const API_CONFIG = {
|
|
44
|
+
export const API_CONFIG = {
|
|
45
45
|
openai: {
|
|
46
46
|
model: "gpt-4.1-mini",
|
|
47
47
|
//model: "gpt-5-mini",
|
|
48
48
|
temperature: 2.0 /* more creative */,
|
|
49
|
-
top_p: 0.
|
|
50
|
-
frequency_penalty:
|
|
49
|
+
top_p: 0.1 /* use nucleus sampling */,
|
|
50
|
+
frequency_penalty: 2.0 /* avoid repetition */,
|
|
51
51
|
presence_penalty: 2.0 /* encourage new topics */,
|
|
52
52
|
max_completion_tokens: MAX_TOKENS,
|
|
53
53
|
} satisfies OpenAIConfig,
|
|
54
54
|
mistral: {
|
|
55
|
-
//model: "
|
|
55
|
+
//model: "labs-mistral-small-creative",
|
|
56
56
|
model: "mistral-small-latest",
|
|
57
57
|
temperature: 1 /* creativity */,
|
|
58
|
-
topP: 0.
|
|
58
|
+
topP: 0.1 /* nucleus sampling */,
|
|
59
59
|
frequencyPenalty: 1.0 /* avoid repetition */,
|
|
60
60
|
presencePenalty: 1.0 /* encourage new topics */,
|
|
61
61
|
maxTokens: MAX_TOKENS,
|
|
@@ -102,7 +102,6 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
|
102
102
|
req.on("end", async () => {
|
|
103
103
|
try {
|
|
104
104
|
const {messages, stream} = JSON.parse(body);
|
|
105
|
-
|
|
106
105
|
switch (MODEL as Provider) {
|
|
107
106
|
case "openai":
|
|
108
107
|
/* https://openai.com/api/pricing/ */
|
|
@@ -113,7 +112,7 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
|
113
112
|
project: process.env.OPENAI_PROJECT_ID,
|
|
114
113
|
});
|
|
115
114
|
const response = await openai.chat.completions.create({
|
|
116
|
-
...API_CONFIG
|
|
115
|
+
...API_CONFIG[MODEL],
|
|
117
116
|
messages,
|
|
118
117
|
stream,
|
|
119
118
|
});
|
|
@@ -151,7 +150,7 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
|
151
150
|
Connection: "keep-alive",
|
|
152
151
|
});
|
|
153
152
|
const response = await mistral.chat.stream({
|
|
154
|
-
...API_CONFIG
|
|
153
|
+
...API_CONFIG[MODEL],
|
|
155
154
|
messages,
|
|
156
155
|
});
|
|
157
156
|
/* forward chunks to browser as SSE */
|
|
@@ -163,7 +162,7 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
|
163
162
|
res.end();
|
|
164
163
|
} else {
|
|
165
164
|
const response = await mistral.chat.complete({
|
|
166
|
-
...API_CONFIG
|
|
165
|
+
...API_CONFIG[MODEL],
|
|
167
166
|
messages,
|
|
168
167
|
});
|
|
169
168
|
res.writeHead(200, {"Content-Type": "application/json"});
|
|
@@ -196,6 +195,13 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
|
196
195
|
}
|
|
197
196
|
}
|
|
198
197
|
});
|
|
198
|
+
} else if (url.startsWith(`${API_PATH}/config`)) {
|
|
199
|
+
const config = {
|
|
200
|
+
provider: MODEL,
|
|
201
|
+
config: API_CONFIG[MODEL],
|
|
202
|
+
};
|
|
203
|
+
res.writeHead(200, {"Content-Type": "application/json"});
|
|
204
|
+
res.end(JSON.stringify(config));
|
|
199
205
|
} else if (url.startsWith(`${API_PATH}/packages`)) {
|
|
200
206
|
exec("npm list --json --depth=0 --silent", (err, stdout) => {
|
|
201
207
|
if (err) {
|
|
@@ -226,8 +232,7 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
|
226
232
|
}
|
|
227
233
|
});
|
|
228
234
|
|
|
229
|
-
/*
|
|
230
|
-
server.setMaxListeners(0);
|
|
235
|
+
server.setMaxListeners(0); /* remove listener limit */
|
|
231
236
|
server.maxConnections = 100;
|
|
232
237
|
|
|
233
238
|
server.listen(API_PORT, () => {
|
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.8.
|
|
6
|
+
"version": "1.8.7",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "rez",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"npm-run-all": "^4.1.5",
|
|
62
62
|
"prettier": "^3.7.4",
|
|
63
63
|
"typescript": "^5.9.3",
|
|
64
|
-
"typescript-eslint": "^8.50.
|
|
64
|
+
"typescript-eslint": "^8.50.1",
|
|
65
65
|
"vite": "^7.3.0",
|
|
66
66
|
"vite-tsconfig-paths": "^6.0.3"
|
|
67
67
|
}
|
package/src/App.tsx
CHANGED
|
@@ -98,8 +98,8 @@ const Layout = () => {
|
|
|
98
98
|
el.id = "debug-screen-size";
|
|
99
99
|
el.className = "debug-info";
|
|
100
100
|
document.body.appendChild(el);
|
|
101
|
-
el.innerHTML = `viewport: ${vw}
|
|
102
|
-
char: ${format(cw)}
|
|
101
|
+
el.innerHTML = `viewport: ${vw}*${vh} | \
|
|
102
|
+
char: ${format(cw)}*${format(lh)} | \
|
|
103
103
|
w: ${w} | h: ${h}`;
|
|
104
104
|
el.style.display = debug ? "block" : "none";
|
|
105
105
|
}, [w, h]);
|
package/src/commons/api/api.ts
CHANGED
|
@@ -6,35 +6,44 @@ import persona from "@commons/persona.txt?raw";
|
|
|
6
6
|
import {formatText} from "@utils/strings";
|
|
7
7
|
import {getVariableFromCSS} from "@utils/styles";
|
|
8
8
|
|
|
9
|
-
export const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const systemConfig = [
|
|
13
|
-
...formatting,
|
|
14
|
-
`your name is ${NAME} and your version is ${VERSION.join(".")}`,
|
|
15
|
-
...persona.split("\n").map((line) => line.trim()),
|
|
16
|
-
`current date: ${new Date().toLocaleDateString()}`,
|
|
17
|
-
`current time: ${new Date().toLocaleTimeString()}`,
|
|
18
|
-
`current unix EPOCH time: ${Math.floor(Date.now() / 1000)}`,
|
|
19
|
-
`a list of random number: ${Array.from({length: 32}, () =>
|
|
20
|
-
Math.round(Math.random() * 100),
|
|
21
|
-
).join(", ")}`,
|
|
22
|
-
`current user agent: ${navigator.userAgent}`,
|
|
23
|
-
`current color hue: ${getVariableFromCSS("h")}°`,
|
|
24
|
-
`current color saturation: ${getVariableFromCSS("s")}%`,
|
|
25
|
-
`current color lightness: ${getVariableFromCSS("l")}%`,
|
|
26
|
-
`current font base size: ${getVariableFromCSS("BASE-SIZE")}`,
|
|
27
|
-
'user can change the color with the "/color [h|s|l] number" command',
|
|
28
|
-
'user can change the font size with the "/size number" command',
|
|
29
|
-
`the "/size" command without parameter will reset the value to ${size}`,
|
|
30
|
-
'user can change the line height with the "/height number" command',
|
|
31
|
-
`the "/height" command without parameter will reset the value to ${height}`,
|
|
32
|
-
'user can reset the settings with the "/reset" command',
|
|
33
|
-
'user can reload the page with "/reboot" (do no reset, just reload)',
|
|
34
|
-
];
|
|
35
|
-
return {role: "system", content: systemConfig.join(". ")};
|
|
9
|
+
export const getApiConfig = async (): Promise<Record<string, string>> => {
|
|
10
|
+
const response = await fetch("/api/config");
|
|
11
|
+
return response.ok ? await response.json() : {};
|
|
36
12
|
};
|
|
37
13
|
|
|
14
|
+
export const getSystemConfig =
|
|
15
|
+
async (): Promise<ChatCompletionMessageParam> => {
|
|
16
|
+
const size = getVariableFromCSS("base-size");
|
|
17
|
+
const height = getVariableFromCSS("base-height");
|
|
18
|
+
const apiConfig = await getApiConfig();
|
|
19
|
+
const systemConfig = [
|
|
20
|
+
...formatting,
|
|
21
|
+
`your name is ${NAME} and your version is ${VERSION.join(".")}`,
|
|
22
|
+
...persona.split("\n").map((line) => line.trim()),
|
|
23
|
+
`current date: ${new Date().toLocaleDateString()}`,
|
|
24
|
+
`current time: ${new Date().toLocaleTimeString()}`,
|
|
25
|
+
`current unix EPOCH time: ${Math.floor(Date.now() / 1000)}`,
|
|
26
|
+
`a list of random number: ${Array.from({length: 32}, () =>
|
|
27
|
+
Math.round(Math.random() * 100),
|
|
28
|
+
).join(", ")}`,
|
|
29
|
+
`current API provider: ${apiConfig.provider || "unknown"}`,
|
|
30
|
+
`current API config: ${JSON.stringify(apiConfig.config || {})}`,
|
|
31
|
+
`current user agent: ${navigator.userAgent}`,
|
|
32
|
+
`current color hue: ${getVariableFromCSS("h")}°`,
|
|
33
|
+
`current color saturation: ${getVariableFromCSS("s")}%`,
|
|
34
|
+
`current color lightness: ${getVariableFromCSS("l")}%`,
|
|
35
|
+
`current font base size: ${getVariableFromCSS("BASE-SIZE")}`,
|
|
36
|
+
'user can change the color with the "/color [h|s|l] number" command',
|
|
37
|
+
'user can change the font size with the "/size number" command',
|
|
38
|
+
`the "/size" command without parameter will reset the value to ${size}`,
|
|
39
|
+
'user can change the line height with the "/height number" command',
|
|
40
|
+
`the "/height" command without parameter will reset the value to ${height}`,
|
|
41
|
+
'user can reset the settings with the "/reset" command',
|
|
42
|
+
'user can reload the page with "/reboot" (do no reset, just reload)',
|
|
43
|
+
];
|
|
44
|
+
return {role: "system", content: systemConfig.join(". ")};
|
|
45
|
+
};
|
|
46
|
+
|
|
38
47
|
export const formatting = [
|
|
39
48
|
"do not mention, repeat or paraphrase user prompt, just answer it",
|
|
40
49
|
"generate text or markdown only, no HTML please! never HTML",
|
|
@@ -9,7 +9,7 @@ export const getData = async (
|
|
|
9
9
|
context?: ChatCompletionMessageParam[],
|
|
10
10
|
): Promise<ChatCompletion> => {
|
|
11
11
|
const messages: ChatCompletionMessageParam[] = [
|
|
12
|
-
getSystemConfig(),
|
|
12
|
+
await getSystemConfig(),
|
|
13
13
|
{
|
|
14
14
|
role: "system",
|
|
15
15
|
content: system?.map((str) => str.trim()).join(". ") || "",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
flex-grow: 0;
|
|
5
5
|
flex-shrink: 0;
|
|
6
6
|
column-gap: var(--font-width);
|
|
7
|
-
align-items:
|
|
7
|
+
align-items: flex-start;
|
|
8
8
|
align-self: stretch;
|
|
9
9
|
padding-left: var(--font-width);
|
|
10
10
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
.container {
|
|
13
13
|
display: flex;
|
|
14
14
|
flex-direction: row;
|
|
15
|
+
flex-wrap: wrap;
|
|
15
16
|
column-gap: var(--font-width);
|
|
16
17
|
height: fit-content;
|
|
17
18
|
}
|
|
@@ -20,11 +21,11 @@
|
|
|
20
21
|
display: inline-block;
|
|
21
22
|
text-wrap: wrap;
|
|
22
23
|
text-overflow: ellipsis;
|
|
23
|
-
width: fit-content;
|
|
24
24
|
opacity: var(--opacity-primary);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.subtitle {
|
|
28
|
+
flex-shrink: 1;
|
|
28
29
|
min-height: var(--line-height);
|
|
29
30
|
opacity: var(--opacity-secondary);
|
|
30
31
|
}
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
.avatar {
|
|
45
46
|
display: flex;
|
|
46
47
|
flex: row;
|
|
48
|
+
flex-wrap: nowrap;
|
|
47
49
|
column-gap: var(--font-width);
|
|
48
50
|
align-items: flex-start;
|
|
49
51
|
opacity: 0;
|
|
@@ -78,17 +78,17 @@ const Header = (_props: {darkMode: boolean; onThemeToggle: () => void}) => {
|
|
|
78
78
|
<div className={subtitle && styles.subtext}>{subtitle}</div>
|
|
79
79
|
</div>
|
|
80
80
|
</div>
|
|
81
|
-
{subtitle && (
|
|
82
|
-
<>
|
|
83
|
-
<div className={styles.avatar}>
|
|
84
|
-
{AVATAR_1}
|
|
85
|
-
<br />
|
|
86
|
-
{AVATAR_2}
|
|
87
|
-
</div>
|
|
88
|
-
<Button name="?" handler={helpHandler} className={styles.help} />
|
|
89
|
-
</>
|
|
90
|
-
)}
|
|
91
81
|
</div>
|
|
82
|
+
{subtitle && (
|
|
83
|
+
<div className={styles.avatar}>
|
|
84
|
+
<div>
|
|
85
|
+
{AVATAR_1}
|
|
86
|
+
<br />
|
|
87
|
+
{AVATAR_2}
|
|
88
|
+
</div>
|
|
89
|
+
<Button name="?" handler={helpHandler} className={styles.help} />
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
92
|
<Spacer />
|
|
93
93
|
<div className={styles.button}>
|
|
94
94
|
<Button name={BUTTON_CREATE} handler={newChatHandler} />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
:root {
|
|
2
2
|
/* constants */
|
|
3
3
|
--base-size: 15; /* default font size */
|
|
4
|
-
--base-height:
|
|
4
|
+
--base-height: 2; /* default line height */
|
|
5
5
|
/* global variables */
|
|
6
6
|
--font-width: 1rem;
|
|
7
7
|
--font-height: 2rem;
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
/* game of life variables */
|
|
25
25
|
--lifespan: 750; /* lifespan of lifeforms in ms */
|
|
26
26
|
/* colors */
|
|
27
|
-
--h:
|
|
28
|
-
--s:
|
|
29
|
-
--l:
|
|
27
|
+
--h: 150; /* amber:30 | yellow: 90 | green:120 | blue:180 */
|
|
28
|
+
--s: 25; /* saturation */
|
|
29
|
+
--l: 60; /* 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);
|
|
@@ -30,6 +30,7 @@ const Chat = () => {
|
|
|
30
30
|
const [completion, setCompletion] = useState<Completion>();
|
|
31
31
|
const [loading, setLoading] = useState<boolean>(false);
|
|
32
32
|
const [query, setQuery] = useState<string>("");
|
|
33
|
+
const [updateTitle, setUpdateTitle] = useState<boolean>(false);
|
|
33
34
|
|
|
34
35
|
const navigate = useNavigate();
|
|
35
36
|
|
|
@@ -69,17 +70,14 @@ const Chat = () => {
|
|
|
69
70
|
"you must separate each part with a line or empty line",
|
|
70
71
|
],
|
|
71
72
|
prompt,
|
|
72
|
-
|
|
73
|
+
[
|
|
74
|
+
...chatStore.getMessages(id),
|
|
75
|
+
{role: "assistant", content: completion?.message || "nothing"},
|
|
76
|
+
],
|
|
73
77
|
completionCallback,
|
|
74
78
|
);
|
|
75
79
|
};
|
|
76
80
|
|
|
77
|
-
const setTitle = async (id: ChatId) => {
|
|
78
|
-
const title = await getChatTitle(chatStore.getMessages(id));
|
|
79
|
-
chatStore.updateChatTitle(id, title);
|
|
80
|
-
storage.save();
|
|
81
|
-
};
|
|
82
|
-
|
|
83
81
|
/* handle chat id url parameter */
|
|
84
82
|
useEffect(() => {
|
|
85
83
|
if (!chatStore.getChat(id)) {
|
|
@@ -117,18 +115,29 @@ const Chat = () => {
|
|
|
117
115
|
if (!chatId) {
|
|
118
116
|
chatStore.setCompletions();
|
|
119
117
|
chatStore.createChat(completion);
|
|
120
|
-
setTitle(chatStore.getChatId());
|
|
121
118
|
}
|
|
122
119
|
chatStore.addCompletion(completion);
|
|
123
120
|
if (chatId) {
|
|
124
121
|
chatStore.updateCompletions(chatId);
|
|
125
|
-
setTitle(chatId);
|
|
126
122
|
}
|
|
127
123
|
/* reset values once the completion is saved in the store */
|
|
128
124
|
setCompletion(undefined);
|
|
129
125
|
setResponse("");
|
|
126
|
+
setUpdateTitle(true);
|
|
130
127
|
}, [completion]);
|
|
131
128
|
|
|
129
|
+
const setTitle = async (id: ChatId) => {
|
|
130
|
+
const title = await getChatTitle(chatStore.getMessages(id));
|
|
131
|
+
chatStore.updateChatTitle(id, title);
|
|
132
|
+
storage.save();
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (!updateTitle) return;
|
|
137
|
+
setTitle(chatStore.getChatId());
|
|
138
|
+
setUpdateTitle(false);
|
|
139
|
+
}, [updateTitle]);
|
|
140
|
+
|
|
132
141
|
return (
|
|
133
142
|
<section className={styles.root}>
|
|
134
143
|
<Container>
|
|
@@ -139,7 +148,7 @@ const Chat = () => {
|
|
|
139
148
|
<Toolbar completion={completion} />
|
|
140
149
|
</Fragment>
|
|
141
150
|
))}
|
|
142
|
-
{loading &&
|
|
151
|
+
{loading && (
|
|
143
152
|
<Fragment key="chat-completion">
|
|
144
153
|
<Message role="user" content={query} />
|
|
145
154
|
<Message role="assistant" content={response} hasCaret={loading} />
|