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 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.3 /* use nucleus sampling */,
50
- frequency_penalty: 1.5 /* avoid repetition */,
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: "ministral-14b-latest",
55
+ //model: "labs-mistral-small-creative",
56
56
  model: "mistral-small-latest",
57
57
  temperature: 1 /* creativity */,
58
- topP: 0.3 /* nucleus sampling */,
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.openai,
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.mistral,
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.mistral,
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
- /* Increase max listeners to handle concurrent streaming requests */
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",
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.0",
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}x${vh} | \
102
- char: ${format(cw)}x${format(lh)} | \
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]);
@@ -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 getSystemConfig = (): ChatCompletionMessageParam => {
10
- const size = getVariableFromCSS("base-size");
11
- const height = getVariableFromCSS("base-height");
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(". ") || "",
@@ -42,7 +42,7 @@ const getStream = async (
42
42
  ) => {
43
43
  try {
44
44
  const messages: ChatCompletionMessageParam[] = [
45
- getSystemConfig(),
45
+ await getSystemConfig(),
46
46
  {
47
47
  role: "system",
48
48
  content: system?.map((str) => str.trim()).join(". ") || "",
@@ -3,6 +3,7 @@
3
3
  flex-direction: row;
4
4
  flex-grow: 0;
5
5
  flex-shrink: 0;
6
+ flex-wrap: wrap;
6
7
  column-gap: var(--font-width);
7
8
  align-items: start;
8
9
  align-self: stretch;
@@ -4,7 +4,7 @@
4
4
  flex-grow: 0;
5
5
  flex-shrink: 0;
6
6
  column-gap: var(--font-width);
7
- align-items: center;
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} />
@@ -61,11 +61,12 @@
61
61
 
62
62
  .debug-info {
63
63
  position: absolute;
64
- top: 0.1rem;
64
+ top: 0.15rem;
65
65
  left: 0.3rem;
66
66
  line-height: 1rem;
67
67
  font-size: 1rem;
68
68
  font-weight: 900;
69
69
  color: #f53;
70
+ text-transform: uppercase;
70
71
  z-index: var(--z-index-debug);
71
72
  }
@@ -1,7 +1,7 @@
1
1
  :root {
2
2
  /* constants */
3
3
  --base-size: 15; /* default font size */
4
- --base-height: 1.8; /* default line 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: 30; /* amber:30 | yellow: 90 | green:120 | blue:180 */
28
- --s: 35; /* saturation */
29
- --l: 65; /* lightness */
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
- chatStore.getMessages(id),
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 && response && (
151
+ {loading && (
143
152
  <Fragment key="chat-completion">
144
153
  <Message role="user" content={query} />
145
154
  <Message role="assistant" content={response} hasCaret={loading} />