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.
Files changed (83) hide show
  1. package/.prettierrc +18 -0
  2. package/README.md +9 -0
  3. package/api/server.ts +153 -0
  4. package/eslint.config.js +103 -0
  5. package/index.html +22 -0
  6. package/netlify.toml +9 -0
  7. package/nodemon.json +4 -0
  8. package/omnibot3000.code-workspace +55 -0
  9. package/package.json +58 -0
  10. package/pnpm-workspace.yaml +2 -0
  11. package/public/fonts/vt220.woff2 +0 -0
  12. package/src/App.module.css +128 -0
  13. package/src/App.tsx +193 -0
  14. package/src/Error.tsx +80 -0
  15. package/src/commons/OmnibotSpeak.module.css +43 -0
  16. package/src/commons/OmnibotSpeak.tsx +31 -0
  17. package/src/commons/api/api.ts +150 -0
  18. package/src/commons/constants.ts +34 -0
  19. package/src/commons/favicon.ts +69 -0
  20. package/src/commons/hooks/useConfig.tsx +50 -0
  21. package/src/commons/hooks/useKeyPress.tsx +76 -0
  22. package/src/commons/hooks/useStorage.tsx +38 -0
  23. package/src/commons/layout/Background.module.css +47 -0
  24. package/src/commons/layout/Background.tsx +138 -0
  25. package/src/commons/layout/Breadcrumb.module.css +28 -0
  26. package/src/commons/layout/Breadcrumb.tsx +54 -0
  27. package/src/commons/layout/Container.module.css +51 -0
  28. package/src/commons/layout/Container.tsx +60 -0
  29. package/src/commons/layout/Footer.module.css +36 -0
  30. package/src/commons/layout/Footer.tsx +74 -0
  31. package/src/commons/layout/Header.module.css +73 -0
  32. package/src/commons/layout/Header.tsx +102 -0
  33. package/src/commons/layout/Menu.module.css +36 -0
  34. package/src/commons/layout/Menu.tsx +37 -0
  35. package/src/commons/persona.txt +38 -0
  36. package/src/commons/styles/debug.css +71 -0
  37. package/src/commons/styles/main.css +221 -0
  38. package/src/commons/styles/vt220.css +69 -0
  39. package/src/commons/ui/Button.tsx +22 -0
  40. package/src/commons/ui/Caret.tsx +20 -0
  41. package/src/commons/ui/Line.tsx +64 -0
  42. package/src/commons/ui/ScrollSnap.tsx +51 -0
  43. package/src/commons/ui/Separator.tsx +19 -0
  44. package/src/commons/ui/Spacer.tsx +12 -0
  45. package/src/commons/utils/canvas.ts +20 -0
  46. package/src/commons/utils/color.ts +4 -0
  47. package/src/commons/utils/math.ts +43 -0
  48. package/src/commons/utils/strings.ts +47 -0
  49. package/src/commons/utils/styles.ts +11 -0
  50. package/src/commons/utils/system.ts +6 -0
  51. package/src/commons/utils/version.ts +24 -0
  52. package/src/features/chat/Chat.module.css +8 -0
  53. package/src/features/chat/Chat.tsx +188 -0
  54. package/src/features/chat/commons/strings.ts +6 -0
  55. package/src/features/chat/components/Message.module.css +28 -0
  56. package/src/features/chat/components/Message.tsx +45 -0
  57. package/src/features/chat/components/Toolbar.module.css +19 -0
  58. package/src/features/chat/components/Toolbar.tsx +44 -0
  59. package/src/features/chat/hooks/useChatCompletionStore.tsx +160 -0
  60. package/src/features/cli/Cli.module.css +75 -0
  61. package/src/features/cli/Cli.tsx +303 -0
  62. package/src/features/console/cmd.ts +93 -0
  63. package/src/features/console/config.ts +106 -0
  64. package/src/features/help/Help.module.css +8 -0
  65. package/src/features/help/Help.tsx +78 -0
  66. package/src/features/history/History.module.css +77 -0
  67. package/src/features/history/History.tsx +92 -0
  68. package/src/features/home/Home.module.css +26 -0
  69. package/src/features/home/Home.tsx +101 -0
  70. package/src/features/life/Life.module.css +8 -0
  71. package/src/features/life/Life.tsx +16 -0
  72. package/src/features/life/generation.ts +103 -0
  73. package/src/features/life/lifeforms.ts +138 -0
  74. package/src/features/life/types.ts +5 -0
  75. package/src/features/version/Version.module.css +8 -0
  76. package/src/features/version/Version.tsx +70 -0
  77. package/src/global.d.ts +10 -0
  78. package/src/main.tsx +32 -0
  79. package/src/vite-env.d.ts +16 -0
  80. package/tsconfig.api.json +16 -0
  81. package/tsconfig.json +47 -0
  82. package/upgrade.sh +22 -0
  83. package/vite.config.ts +51 -0
@@ -0,0 +1,160 @@
1
+ import {create} from "zustand";
2
+
3
+ import {ChatCompletionMessageParam} from "openai/resources/index.mjs";
4
+
5
+ import {formatCompletionId} from "@chat/commons/strings";
6
+
7
+ export type ChatId = string | undefined;
8
+
9
+ export interface Chat {
10
+ id: ChatId;
11
+ title: string;
12
+ created: EpochTimeStamp;
13
+ index: number;
14
+ completions: Completion[];
15
+ }
16
+
17
+ export type CompletionId = string | undefined;
18
+
19
+ export interface Completion {
20
+ id: CompletionId;
21
+ created: EpochTimeStamp;
22
+ model: string;
23
+ prompt: string;
24
+ message: string;
25
+ index: number;
26
+ children: Completion[];
27
+ parentCompletion: CompletionId | undefined;
28
+ }
29
+
30
+ export interface Data {
31
+ chats: Chat[];
32
+ }
33
+
34
+ export interface ChatCompletionStoreState {
35
+ chatId: ChatId;
36
+ setChatId: (id?: ChatId) => void;
37
+ getChatId: () => ChatId;
38
+ chats: Chat[];
39
+ createChat: (completion: Completion) => void;
40
+ getChats: () => Chat[];
41
+ getChat: (id?: ChatId) => Chat | undefined;
42
+ updateChatTitle: (id: ChatId, title: string) => void;
43
+ deleteChat: (id?: ChatId) => void;
44
+ resetChat: () => void;
45
+ completionId: CompletionId;
46
+ setCompletionId: (id?: CompletionId) => void;
47
+ getCompletionId: () => CompletionId;
48
+ completions: Completion[];
49
+ getCompletions: (id: ChatId) => Completion[];
50
+ setCompletions: (id?: ChatId) => void;
51
+ getCompletion: (id: CompletionId) => Completion | undefined;
52
+ addCompletion: (completion: Completion) => void;
53
+ updateCompletions: (id: ChatId) => void;
54
+ deleteCompletion: (id: CompletionId) => void;
55
+ messages: ChatCompletionMessageParam[];
56
+ getMessages: (id: ChatId) => ChatCompletionMessageParam[];
57
+ exportData: () => Data;
58
+ importData: (data: Data) => void;
59
+ }
60
+
61
+ const useChatCompletionStore = create<ChatCompletionStoreState>()(
62
+ (set, get) => ({
63
+ chatId: undefined,
64
+ setChatId: (id?: ChatId) => set({chatId: id}),
65
+ getChatId: () => get().chatId,
66
+ chats: [],
67
+ createChat: (completion: Completion) => {
68
+ const id = formatCompletionId(completion.id);
69
+ set((state) => ({
70
+ chatId: id,
71
+ completionId: id,
72
+ chats: [
73
+ ...state.chats,
74
+ {
75
+ id,
76
+ title: "",
77
+ created: completion.created, //new Date().getTime(),
78
+ index: 0,
79
+ completions: [completion],
80
+ },
81
+ ],
82
+ }));
83
+ },
84
+ getChats: () => get().chats,
85
+ getChat: (id?: ChatId) =>
86
+ get().chats.find((chat: Chat) => Boolean(id === chat.id)),
87
+ updateChatTitle: (id: ChatId, title: string) => {
88
+ const updatedChats: Chat[] = get().chats.map(
89
+ (chat: Chat): Chat => (chat.id === id ? {...chat, title} : chat),
90
+ );
91
+ set({chats: updatedChats});
92
+ },
93
+ deleteChat: (id?: ChatId) => {
94
+ const index = get().chats.findIndex((chat: Chat) => id === chat.id);
95
+ if (index !== -1) {
96
+ const updatedChats = [...get().chats];
97
+ updatedChats.splice(index, 1);
98
+ set({chats: updatedChats});
99
+ }
100
+ },
101
+ resetChat: () => {
102
+ set({
103
+ chatId: undefined,
104
+ completionId: undefined,
105
+ completions: [],
106
+ messages: [],
107
+ });
108
+ },
109
+ completionId: undefined,
110
+ setCompletionId: (id?: CompletionId) => set({completionId: id}),
111
+ getCompletionId: () => get().completionId,
112
+ completions: [],
113
+ getCompletions: (id: ChatId) => get().getChat(id)?.completions || [],
114
+ setCompletions: (id?: ChatId) =>
115
+ set({
116
+ completions: id ? [...get().getCompletions(id)] : [],
117
+ }),
118
+ getCompletion: (id: CompletionId) =>
119
+ get().completions.find((completion: Completion) => completion.id === id),
120
+ addCompletion: (completion: Completion) =>
121
+ set((state) => ({
122
+ completions: [...state.completions, completion],
123
+ })),
124
+ updateCompletions: (id: ChatId) => {
125
+ const updatedChats: Chat[] = get().chats.map(
126
+ (chat: Chat): Chat =>
127
+ chat.id === id ? {...chat, completions: get().completions} : chat,
128
+ );
129
+ set({chats: updatedChats});
130
+ },
131
+ deleteCompletion: (id: CompletionId) => {
132
+ const completion = get().getCompletion(id);
133
+ if (!completion) return;
134
+ const parent = get().getCompletion(completion.parentCompletion || "");
135
+ if (!parent) return;
136
+ parent.children.splice(completion.index, 1);
137
+ },
138
+ messages: [],
139
+ getMessages: (id: ChatId) =>
140
+ get()
141
+ .getCompletions(id)
142
+ .map((completion: Completion) => {
143
+ return [
144
+ {role: "user", content: completion.prompt},
145
+ {role: "assistant", content: completion.message},
146
+ ];
147
+ })
148
+ .flat(Infinity) as ChatCompletionMessageParam[],
149
+ exportData: (): Data => {
150
+ return {
151
+ chats: get().chats,
152
+ };
153
+ },
154
+ importData: (data: Data) => {
155
+ set({chats: [...data.chats]});
156
+ },
157
+ }),
158
+ );
159
+
160
+ export default useChatCompletionStore;
@@ -0,0 +1,75 @@
1
+ .form {
2
+ display: flex;
3
+ flex-direction: row;
4
+ flex-grow: 0;
5
+ flex-shrink: 1;
6
+ column-gap: var(--font-width);
7
+ align-items: end;
8
+ margin-left: var(--font-width);
9
+ margin-right: var(--font-width);
10
+ max-width: calc((var(--content-width) - 3) * var(--font-width));
11
+ }
12
+
13
+ .input {
14
+ display: none;
15
+ }
16
+
17
+ .pill {
18
+ align-self: start;
19
+ }
20
+
21
+ .command {
22
+ flex-grow: 1;
23
+ width: calc(var(--content-width) - 2 * var(--font-width));
24
+ min-height: var(--line-height);
25
+ }
26
+
27
+ .command-line {
28
+ position: relative;
29
+ display: inline;
30
+ clear: both;
31
+ float: left;
32
+ width: 100%;
33
+ min-height: var(--line-height);
34
+ text-transform: none !important;
35
+ user-select: text !important;
36
+ cursor: text !important;
37
+ text-wrap: wrap;
38
+ white-space: pre-wrap;
39
+ word-break: break-all;
40
+ }
41
+
42
+ .placeholder {
43
+ position: absolute;
44
+ height: var(--line-height);
45
+ user-select: none !important;
46
+ cursor: default !important;
47
+ }
48
+
49
+ .show {
50
+ height: var(--line-height);
51
+ opacity: var(--opacity-tertiary);
52
+ transition: opacity var(--duration-fade) ease-out;
53
+ }
54
+
55
+ .hide {
56
+ height: var(--line-height);
57
+ opacity: 0;
58
+ transition: opacity var(--duration-fade) ease-out;
59
+ }
60
+
61
+ .caret {
62
+ position: absolute;
63
+ display: inline;
64
+ left: 0;
65
+ top: 0;
66
+ width: 100%;
67
+ text-wrap: wrap;
68
+ white-space: pre-wrap;
69
+ word-break: break-all;
70
+ }
71
+
72
+ .submit {
73
+ flex-grow: 0;
74
+ place-self: start;
75
+ }
@@ -0,0 +1,303 @@
1
+ import React, {FormEvent, memo, useEffect, useRef, useState} from "react";
2
+
3
+ import {getPromptPlaceholder} from "@api/api";
4
+ import {BUTTON_SUBMIT} from "@commons/constants";
5
+ import Caret from "@ui/Caret";
6
+ import {formatText} from "@utils/strings";
7
+ import {getVariableFromCSS} from "@utils/styles";
8
+
9
+ import useConfig from "@hooks/useConfig";
10
+
11
+ import cmd from "@console/cmd";
12
+
13
+ import styles from "@cli/Cli.module.css";
14
+
15
+ import cls from "classnames";
16
+
17
+ export const KEYS: string[] = [
18
+ "Escape",
19
+ "Control",
20
+ "Meta",
21
+ "Shift",
22
+ "Alt",
23
+ "Dead",
24
+ "CapsLock",
25
+ "Insert",
26
+ "Delete",
27
+ ];
28
+
29
+ export const RenderCli = (props: {
30
+ command: string[];
31
+ line: number;
32
+ caret: number;
33
+ }) => {
34
+ const {command, line, caret} = props;
35
+ return (
36
+ <div
37
+ style={{
38
+ height: `calc(${command.length} * var(--line-height))`,
39
+ }}>
40
+ {command.map((text: string, i: number) => {
41
+ return (
42
+ <div
43
+ key={`command-line-${i}`}
44
+ className={styles["command-line"]}
45
+ style={{clear: i > 0 ? "both" : "none"}}>
46
+ {text}
47
+ {i === line ? (
48
+ <div className={cls("blink", styles.caret)}>
49
+ <span style={{visibility: "hidden"}}>{"%".repeat(caret)}</span>
50
+ <Caret />
51
+ </div>
52
+ ) : null}
53
+ </div>
54
+ );
55
+ })}
56
+ </div>
57
+ );
58
+ };
59
+
60
+ const Cli = (props: {
61
+ loading: boolean;
62
+ prompt: string[];
63
+ setPrompt: React.Dispatch<React.SetStateAction<string[]>>;
64
+ submitHandler: (query: string) => void;
65
+ }) => {
66
+ const {loading, prompt, setPrompt, submitHandler} = props;
67
+
68
+ const keyEvent = useRef<KeyboardEvent>(undefined);
69
+ const hasRunOnce = useRef<boolean>(false);
70
+
71
+ const [placeholders, setPlaceholders] = useState<string[]>([]);
72
+ const [count, setCount] = useState<number>(0);
73
+ const [line, setLine] = useState<number>(0);
74
+ const [caret, setCaret] = useState<number>(0);
75
+ const [forceUpdate, setForceUpdate] = useState<number>(0);
76
+
77
+ const formRef = useRef<HTMLFormElement>(null);
78
+
79
+ const isDisabled = loading || String(prompt).replace("\n", "").trim() === "";
80
+
81
+ const config = useConfig();
82
+ const {debug} = config.getConfig();
83
+
84
+ const updatePlaceholder = async () => {
85
+ const data = await getPromptPlaceholder();
86
+ setPlaceholders(
87
+ data
88
+ .split("\n")
89
+ .filter((v) => v.trim() !== "")
90
+ .map((v) => formatText(v.trim())),
91
+ );
92
+ };
93
+
94
+ function handleInput(e: KeyboardEvent): void {
95
+ keyEvent.current = e;
96
+ setForceUpdate((n) => (n + 1) % 256);
97
+ }
98
+
99
+ useEffect(() => {
100
+ const {key, shiftKey, ctrlKey, metaKey} = keyEvent.current || {};
101
+
102
+ if (!key || KEYS.includes(key)) return;
103
+
104
+ const tabSize = parseInt(getVariableFromCSS("tab-size")) || 2;
105
+
106
+ let p = [...prompt];
107
+ let l = line;
108
+ let c = caret;
109
+
110
+ switch (key) {
111
+ case "Enter":
112
+ if (shiftKey) {
113
+ l++;
114
+ p.splice(l, 0, p[l - 1].substring(c));
115
+ p[l - 1] = p[l - 1].substring(0, c);
116
+ } else {
117
+ if (p[0].charAt(0) === "/") {
118
+ cmd(p[0].substring(1), debug);
119
+ } else {
120
+ submitHandler(p.join("\n").trim());
121
+ }
122
+ p = [""];
123
+ l = 0;
124
+ }
125
+ c = 0;
126
+ break;
127
+ case "Tab":
128
+ p[l] += " ".repeat(tabSize);
129
+ c += tabSize;
130
+ break;
131
+ case "Backspace":
132
+ if (c > 0) {
133
+ p[l] = `${p[l].substring(0, c - 1)}${p[l].substring(c)}`;
134
+ c--;
135
+ } else if (l > 0) {
136
+ l--;
137
+ p[l] += p[l + 1];
138
+ c = p[l].length - p[l + 1].length;
139
+ p.splice(l + 1, 1);
140
+ }
141
+ break;
142
+ case "ArrowLeft":
143
+ c--;
144
+ if (c < 0) {
145
+ if (l > 0) {
146
+ l--;
147
+ c = p[l].length;
148
+ } else {
149
+ c = 0;
150
+ }
151
+ }
152
+ break;
153
+ case "ArrowRight":
154
+ c++;
155
+ if (c > p[l].length) {
156
+ if (l < prompt.length - 1) {
157
+ l++;
158
+ c = 0;
159
+ } else {
160
+ c = p[l].length;
161
+ }
162
+ }
163
+ break;
164
+ case "ArrowUp":
165
+ l--;
166
+ if (l < 0) {
167
+ l = 0;
168
+ c = 0;
169
+ } else {
170
+ c = Math.min(c, p[l].length);
171
+ }
172
+ break;
173
+ case "ArrowDown":
174
+ l++;
175
+ if (l > prompt.length - 1) {
176
+ l = prompt.length - 1;
177
+ c = p[l].length;
178
+ } else {
179
+ c = Math.min(c, p[l].length);
180
+ }
181
+ break;
182
+ case "PageUp":
183
+ c = 0;
184
+ break;
185
+ case "PageDown":
186
+ c = p[l].length;
187
+ break;
188
+ case "Home":
189
+ l = 0;
190
+ c = 0;
191
+ break;
192
+ case "End":
193
+ l = prompt.length - 1;
194
+ c = p[l].length;
195
+ break;
196
+ default:
197
+ if (!ctrlKey && !metaKey) {
198
+ p[l] = `${p[l].substring(0, c)}${key}${p[l].substring(c)}`;
199
+ c++;
200
+ }
201
+ }
202
+
203
+ if (ctrlKey) {
204
+ switch (key) {
205
+ case "a":
206
+ c = 0;
207
+ break;
208
+ case "e":
209
+ c = p[l].length;
210
+ break;
211
+ case "u":
212
+ p[l] = p[l].substring(c);
213
+ c = 0;
214
+ break;
215
+ case "k":
216
+ p[l] = p[l].substring(0, c);
217
+ break;
218
+ }
219
+ }
220
+
221
+ setPrompt(p);
222
+ setLine(l);
223
+ setCaret(c);
224
+ }, [forceUpdate]);
225
+
226
+ const pasteQuery = (e: ClipboardEvent) => {
227
+ const data = e.clipboardData;
228
+ if (!data) return;
229
+ const text = data.getData("text/plain");
230
+ if (text.trim() === "") return;
231
+ const query = text.split("\n");
232
+ setPrompt(query);
233
+ setLine(query.length - 1);
234
+ setCaret(query[query.length - 1].length);
235
+ };
236
+
237
+ useEffect(() => {
238
+ if (hasRunOnce.current) return;
239
+ hasRunOnce.current = true;
240
+
241
+ updatePlaceholder();
242
+
243
+ window.addEventListener("paste", (e) => pasteQuery(e));
244
+ window.addEventListener("keydown", (e) => handleInput(e));
245
+ return () => {
246
+ window.removeEventListener("keydown", (e) => handleInput(e));
247
+ window.removeEventListener("paste", (e) => pasteQuery(e));
248
+ };
249
+ }, []);
250
+
251
+ useEffect(() => {
252
+ if (prompt[prompt.length - 1].length === 0)
253
+ setCount(Math.round(Math.random() * (placeholders.length - 1)));
254
+ }, [prompt[prompt.length - 1], placeholders]);
255
+
256
+ return (
257
+ <form
258
+ ref={formRef}
259
+ onSubmit={(e: FormEvent) => {
260
+ e.preventDefault();
261
+ submitHandler(prompt.join("\n").trim());
262
+ setPrompt([""]);
263
+ setLine(0);
264
+ setCaret(0);
265
+ }}
266
+ onPaste={(e: React.ClipboardEvent) => {
267
+ e.preventDefault();
268
+ pasteQuery(e.nativeEvent);
269
+ }}
270
+ className={styles.form}>
271
+ <div className={styles.pill}>{">"}</div>
272
+ <div className={cls("ascii", styles.command)}>
273
+ <div className={cls("text", styles.placeholder)}>
274
+ <div
275
+ className={
276
+ styles[
277
+ prompt.join() || placeholders.length === 0 ? "hide" : "show"
278
+ ]
279
+ }>
280
+ {placeholders[count]}
281
+ </div>
282
+ </div>
283
+ <RenderCli command={prompt} line={line} caret={caret} />
284
+ </div>
285
+ <input
286
+ name="command"
287
+ className={styles.input}
288
+ defaultValue={!loading && prompt ? prompt : ""}
289
+ autoComplete="off"
290
+ />
291
+ <div>
292
+ <button
293
+ type="submit"
294
+ disabled={isDisabled}
295
+ className={cls("ascii", styles.submit)}>
296
+ {BUTTON_SUBMIT}
297
+ </button>
298
+ </div>
299
+ </form>
300
+ );
301
+ };
302
+
303
+ export default memo(Cli);
@@ -0,0 +1,93 @@
1
+ import {clamp} from "@utils/math";
2
+ import {getVariableFromCSS, setVariableToCSS} from "@utils/styles";
3
+
4
+ import Config, {ConfigValue} from "@console/config";
5
+
6
+ const cmd = (query: string, debug: boolean) => {
7
+ if (query.trim() === "") return;
8
+
9
+ const config = new Config();
10
+
11
+ const [cmd, arg1, arg2] = query.toLowerCase().split(" ");
12
+
13
+ let value: ConfigValue = "";
14
+
15
+ switch (cmd) {
16
+ case "reboot":
17
+ window.location.reload();
18
+ return;
19
+ case "reset":
20
+ config.delete();
21
+ window.location.reload();
22
+ return;
23
+ case "help":
24
+ window.location.pathname = "/help";
25
+ return;
26
+ case "debug":
27
+ switch (arg1) {
28
+ case "on":
29
+ value = true;
30
+ break;
31
+ case "off":
32
+ value = false;
33
+ break;
34
+ case undefined:
35
+ value = !debug;
36
+ break;
37
+ default:
38
+ console.warn(`unknown debug command "${arg1}"`);
39
+ return;
40
+ }
41
+ config.update(cmd, "", value);
42
+ break;
43
+ case "color":
44
+ if (!arg1 || !arg2) {
45
+ console.warn(`missing arguments for command "${cmd}"`);
46
+ return;
47
+ }
48
+ switch (arg1) {
49
+ case "h":
50
+ value = clamp(parseFloat(arg2), 0, 360).toFixed(1);
51
+ break;
52
+ case "s":
53
+ value = clamp(parseInt(arg2), 0, 100).toFixed(0);
54
+ break;
55
+ case "l":
56
+ value = clamp(parseInt(arg2), 0, 100).toFixed(0);
57
+ break;
58
+ }
59
+ setVariableToCSS(arg1, value);
60
+ config.update(cmd, arg1, value);
61
+ break;
62
+ case "size":
63
+ value = clamp(
64
+ parseInt(arg1) ?? parseInt(getVariableFromCSS("base-size")),
65
+ 4,
66
+ 32,
67
+ );
68
+ setVariableToCSS("font-size", `${value}px`);
69
+ config.update(cmd, "", value);
70
+ window.location.reload();
71
+ break;
72
+ case "height":
73
+ value = clamp(
74
+ parseFloat(arg1) ?? parseInt(getVariableFromCSS("base-height")),
75
+ 0.6,
76
+ 4,
77
+ );
78
+ setVariableToCSS("line-height", `${value.toFixed(1)}rem`);
79
+ config.update(cmd, "", value);
80
+ window.location.reload();
81
+ break;
82
+ default:
83
+ console.warn(`unknown command "${cmd}"`);
84
+ return;
85
+ }
86
+
87
+ if (debug && !value) {
88
+ console.warn(`no value set for command "${cmd}"`);
89
+ return;
90
+ }
91
+ };
92
+
93
+ export default cmd;