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,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;
|