omnibot3000 1.10.1 → 1.10.3

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.
@@ -0,0 +1,13 @@
1
+ name: keep render alive
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "*/10 * * * *"
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ ping:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Ping Render
13
+ run: curl -fsS https://omnibot3000.onrender.com/api/config
@@ -8,6 +8,8 @@ on:
8
8
  jobs:
9
9
  publish:
10
10
  runs-on: ubuntu-latest
11
+ env:
12
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
11
13
  permissions:
12
14
  contents: read
13
15
  id-token: write
package/TODO.md CHANGED
@@ -2,9 +2,6 @@
2
2
 
3
3
  ## In Progress
4
4
 
5
- - allow query retry
6
- - handle conversation tree with multiple branches
7
-
8
5
  ## Backlog
9
6
 
10
7
  - add [OpenRouter](https://openrouter.ai/docs/quickstart#using-the-openai-sdk)
@@ -16,7 +13,9 @@
16
13
  - support for multiple models in the same conversation
17
14
  - keep the latest query on the top while streaming the response
18
15
  - hide all visual elements in _Game of Life_ mode except the toggle button
16
+ - randomize _Game of Life_ lifeforms angle by 90° steps
19
17
  - try to color text with [rasterbars](https://en.wikipedia.org/wiki/Raster_bar)
18
+ - add links to [npmjs.com package](https://www.npmjs.com) on the /version page
20
19
 
21
20
  ## Done
22
21
 
@@ -31,3 +30,6 @@
31
30
  [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life)
32
31
  in the background
33
32
  - make the Game of Life background interactive with mouse clicks
33
+ - allow query retry
34
+ - handle conversation tree with multiple branches
35
+ - make API available online on [Render](https://render.com)
package/netlify.toml CHANGED
@@ -1,3 +1,7 @@
1
+ [build]
2
+ command = "pnpm run build"
3
+ publish = "dist"
4
+
1
5
  [[redirects]]
2
6
  from = "/*"
3
7
  to = "/index.html"
@@ -5,5 +9,6 @@
5
9
 
6
10
  [[redirects]]
7
11
  from = "/api/*"
8
- to = "/api"
12
+ to = "https://omnibot3000.onrender.com/api/:splat"
9
13
  status = 200
14
+ force = true
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.10.1",
6
+ "version": "1.10.3",
7
7
  "type": "module",
8
8
  "author": {
9
9
  "name": "rez",
@@ -19,39 +19,39 @@
19
19
  "omnibot": "./bin/omnibot.js"
20
20
  },
21
21
  "dependencies": {
22
- "@mistralai/mistralai": "^2.2.1",
22
+ "@mistralai/mistralai": "^2.2.5",
23
23
  "dotenv": "^17.4.2",
24
- "openai": "^6.39.0",
25
- "react": "^19.2.6",
26
- "react-dom": "^19.2.6",
24
+ "openai": "^6.42.0",
25
+ "react": "^19.2.7",
26
+ "react-dom": "^19.2.7",
27
27
  "react-markdown": "^10.1.0",
28
- "react-router-dom": "^7.15.1",
29
- "zustand": "^5.0.13"
28
+ "react-router-dom": "^7.17.0",
29
+ "zustand": "^5.0.14"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@eslint/js": "^10.0.1",
33
- "@types/node": "^25.9.1",
34
- "@types/react": "^19.2.15",
33
+ "@types/node": "^25.9.3",
34
+ "@types/react": "^19.2.17",
35
35
  "@types/react-dom": "^19.2.3",
36
36
  "@vitejs/plugin-react": "^6.0.2",
37
37
  "classnames": "^2.5.1",
38
- "eslint": "^10.4.0",
38
+ "eslint": "^10.5.0",
39
39
  "eslint-plugin-import": "^2.32.0",
40
40
  "eslint-plugin-react-hooks": "^7.1.1",
41
- "eslint-plugin-react-refresh": "^0.5.2",
41
+ "eslint-plugin-react-refresh": "^0.5.3",
42
42
  "eslint-plugin-simple-import-sort": "^13.0.0",
43
43
  "globals": "^17.6.0",
44
44
  "husky": "^9.1.7",
45
- "knip": "^6.14.2",
45
+ "knip": "^6.16.1",
46
46
  "nodemon": "^3.1.14",
47
47
  "npm-run-all": "^4.1.5",
48
- "prettier": "^3.8.3",
48
+ "prettier": "^3.8.4",
49
49
  "react-refresh": "^0.18.0",
50
- "stylelint": "^17.12.0",
50
+ "stylelint": "^17.13.0",
51
51
  "stylelint-config-standard": "^40.0.0",
52
52
  "typescript": "^6.0.3",
53
- "typescript-eslint": "^8.59.4",
54
- "vite": "^8.0.14"
53
+ "typescript-eslint": "^8.61.0",
54
+ "vite": "^8.0.16"
55
55
  },
56
56
  "scripts": {
57
57
  "start": "pnpm run-p start:dev start:api",
package/src/App.tsx CHANGED
@@ -15,6 +15,7 @@ import Menu from "@layout/Menu";
15
15
  import Line from "@ui/Line";
16
16
  import {format} from "@utils/math";
17
17
  import {getCharWidth, getLineHeight} from "@utils/strings";
18
+ import {getVariableFromCSS} from "@utils/styles";
18
19
  import {isSystemDarkModeOn} from "@utils/system";
19
20
 
20
21
  import {CliProvider} from "@hooks/useCli";
@@ -100,9 +101,11 @@ const Layout = () => {
100
101
  el.className = "debug-info";
101
102
  document.body.appendChild(el);
102
103
  el.innerHTML = [
103
- `viewport: ${vw}*${vh}`,
104
+ `v: ${vw}*${vh}`,
105
+ `scr: ${w}*${h}`,
104
106
  `char: ${cw}*${lh}`,
105
- `w: ${w} | h: ${h}`,
107
+ `size: ${Math.floor((w - cw * 2) / cw)}*${Math.floor((h - cw * 2) / lh)}`,
108
+ `col: ${getVariableFromCSS("h")},${getVariableFromCSS("s")},${getVariableFromCSS("l")}`,
106
109
  ].join(" | ");
107
110
  el.style.display = debug ? "block" : "none";
108
111
  }, [w, h]);
@@ -79,6 +79,7 @@ export const getChatTitle = async (
79
79
  [
80
80
  "sum up the conversation in few words",
81
81
  "do not answer to the query, just provide a summary",
82
+ "do not use your last sentence where you roast humanity",
82
83
  ],
83
84
  messages,
84
85
  );
@@ -16,7 +16,7 @@ export const ASCII_BLOCK1 = "\u00fe";
16
16
  export const ASCII_BLOCK2 = "\u00ae";
17
17
  export const ASCII_BLOCK3 = "\u00b8";
18
18
  export const ASCII_RECTANGLE = "\u00ff";
19
- export const ASCII_VLINE = "|"; //"\u00af";
19
+ export const ASCII_VLINE = "\u00af";
20
20
  export const ASCII_HLINE = "-";
21
21
  export const ASCII_CORNER = "+";
22
22
  export const ASCII_POINT = "\u00a0";
@@ -15,6 +15,16 @@ export const clamp = (
15
15
  max: number = 1,
16
16
  ): number => Math.max(min, Math.min(n, max));
17
17
 
18
+ export const lerp = (min: number = 0, max: number = 1, n: number = 0): number =>
19
+ min + (max - min) * clamp(n);
20
+
21
+ export const lead = (
22
+ n: number = 0,
23
+ l: number = 1000,
24
+ c: string = "0",
25
+ ): string =>
26
+ `${c.repeat(Math.max(0, l.toFixed(0).length - n.toFixed(0).length))}${n}`;
27
+
18
28
  export type Unit = "byte" | "time" | "percent";
19
29
 
20
30
  const UNIT_SCALE: Record<Unit, Record<string, number>> = {
@@ -27,6 +27,7 @@ const Chat = () => {
27
27
  const [parentId, setParentId] = useState<CompletionId>();
28
28
  const [loading, setLoading] = useState<boolean>(false);
29
29
  const [query, setQuery] = useState<string>("");
30
+ const [retry, setRetry] = useState<number>(0);
30
31
  const [updateTitle, setUpdateTitle] = useState<boolean>(false);
31
32
 
32
33
  const navigate = useNavigate();
@@ -45,6 +46,7 @@ const Chat = () => {
45
46
  const completion = chatStore.createCompletion(id, created, model, query);
46
47
  setCompletion(completion);
47
48
  setQuery("");
49
+ setRetry(0);
48
50
  cli.set([""]);
49
51
  cli.unblock();
50
52
  };
@@ -62,8 +64,8 @@ const Chat = () => {
62
64
  setLoading,
63
65
  setResponse,
64
66
  query,
65
- chatStore.readMessages(completion?.id),
66
- completion?.message,
67
+ chatStore.readMessages(id).slice(0, -retry * 2 || undefined),
68
+ undefined, //!retry ? completion?.message : undefined,
67
69
  completionCallback,
68
70
  );
69
71
  }, [query]);
@@ -76,9 +78,11 @@ const Chat = () => {
76
78
  } else {
77
79
  chatStore.loadChat(id);
78
80
  }
81
+ const conversation = chatStore.readConversation(id);
82
+ setParentId(conversation[conversation.length - 1]?.id);
79
83
  }, [id]);
80
84
 
81
- /* update the chat is the chatId value in store changed */
85
+ /* update url if the chatId value in store changed */
82
86
  useEffect(() => {
83
87
  const unsubscribe = useChatCompletionStore.subscribe((state) => {
84
88
  navigate(`/chat${state.chatId ? `/${state.chatId}` : ""}`);
@@ -93,19 +97,18 @@ const Chat = () => {
93
97
  prev.message = response;
94
98
  return prev;
95
99
  });
96
- if (!chatStore.chatId) chatStore.createChat(completion);
97
- else chatStore.updateCompletion(completion, parentId);
100
+ if (!chatStore.readChat(id)) chatStore.createChat(completion);
101
+ else chatStore.updateCompletion(parentId, completion);
98
102
  /* reset values once the completion is saved in the store */
99
- setCompletion(undefined);
100
- setParentId(undefined);
103
+ setParentId(completion.id);
101
104
  setResponse("");
102
105
  setUpdateTitle(true);
103
106
  }, [completion]);
104
107
 
105
108
  const setTitle = async () => {
106
- const title = await getChatTitle(chatStore.readMessages());
107
- chatStore.updateChatTitle(title);
108
- chatStore.updateCompletionTitle(title);
109
+ const title = await getChatTitle(chatStore.readMessages(id));
110
+ chatStore.updateChatTitle(id, title);
111
+ if (completion) chatStore.updateCompletionTitle(completion.id, title);
109
112
  storage.save();
110
113
  };
111
114
 
@@ -120,6 +123,7 @@ const Chat = () => {
120
123
  <Container>
121
124
  {chatStore
122
125
  .readConversation(id)
126
+ .slice(0, retry ? -retry : undefined)
123
127
  .map((completion: Completion, i: number, a: Completion[]) => (
124
128
  <Fragment key={`chat-completion-${completion.id}`}>
125
129
  <Message
@@ -132,8 +136,10 @@ const Chat = () => {
132
136
  <Toolbar
133
137
  completion={completion}
134
138
  query={query}
139
+ number={a.length - i}
135
140
  setQuery={setQuery}
136
141
  setParentId={setParentId}
142
+ setRetry={setRetry}
137
143
  />
138
144
  </div>
139
145
  </Fragment>
@@ -9,6 +9,7 @@ import {
9
9
  import Button from "@ui/Button";
10
10
  import Line from "@ui/Line";
11
11
  import {log} from "@utils/debug";
12
+ import {lead} from "@utils/math";
12
13
 
13
14
  import useStorage from "@hooks/useStorage";
14
15
 
@@ -23,13 +24,15 @@ import cls from "classnames";
23
24
  const Toolbar = (props: {
24
25
  completion: Completion;
25
26
  query: string;
27
+ number: number;
26
28
  setQuery: React.Dispatch<React.SetStateAction<string>>;
27
- setParentId: React.Dispatch<React.SetStateAction<CompletionId | undefined>>;
29
+ setParentId: React.Dispatch<React.SetStateAction<CompletionId>>;
30
+ setRetry: React.Dispatch<React.SetStateAction<number>>;
28
31
  }) => {
29
32
  const chatStore = useChatCompletionStore();
30
33
  const storage = useStorage();
31
34
 
32
- const {setQuery, setParentId} = props;
35
+ const {setQuery, setParentId, setRetry} = props;
33
36
 
34
37
  const deleteCompletion = (id: CompletionId) => {
35
38
  log(`delete ${id}`, "chat");
@@ -44,17 +47,32 @@ const Toolbar = (props: {
44
47
  log(`prompt: ${completion.prompt}`, "chat");
45
48
  setQuery(completion.prompt);
46
49
  setParentId(completion.parentId);
50
+ setRetry(number);
47
51
  };
48
52
 
49
53
  const loadCompletion = (id: CompletionId, index: number) => {
54
+ log(`load ${id} (index: ${index})`, "chat");
50
55
  const c = chatStore.findCompletion(id, index);
51
56
  if (!c) return;
52
- log(`${c.id} (index: ${c.index})`, "chat");
57
+ chatStore.updateIndex(c.parentId, index);
53
58
  storage.save();
59
+ const conversation = chatStore.readConversation(chatStore.chatId);
60
+ setParentId(conversation[conversation.length - 1]?.id);
54
61
  };
55
62
 
56
- const {completion} = props;
57
- const parent = chatStore.readCompletion(completion.parentId);
63
+ const {completion, number} = props;
64
+
65
+ let parent;
66
+ if (completion.parentId === chatStore.chatId) {
67
+ const chat = chatStore.readChat(chatStore.chatId);
68
+ parent = {
69
+ id: chat?.id,
70
+ children: chatStore.completions.filter((c) => c.parentId === chat?.id),
71
+ index: chat?.index ?? 0,
72
+ };
73
+ } else {
74
+ parent = chatStore.readCompletion(completion.parentId);
75
+ }
58
76
 
59
77
  return (
60
78
  <footer className={styles.root}>
@@ -78,7 +96,7 @@ const Toolbar = (props: {
78
96
  }}
79
97
  disabled={parent.index === 0}
80
98
  />
81
- <span>{`${parent.index + 1}/${parent.children.length}`}</span>
99
+ <span>{`${lead(parent.index + 1, parent.children.length)}/${parent.children.length}`}</span>
82
100
  <Button
83
101
  name={BUTTON_RIGHT}
84
102
  handler={() => {
@@ -2,7 +2,8 @@ import {create} from "zustand";
2
2
 
3
3
  import {ChatCompletionMessageParam} from "openai/resources/index.mjs";
4
4
 
5
- //import {clamp} from "@utils/math";
5
+ import {clamp} from "@utils/math";
6
+
6
7
  import {getCompletionId, getRandomToken} from "@chat/commons/strings";
7
8
 
8
9
  export type ChatId = string | undefined;
@@ -15,6 +16,8 @@ export interface Chat {
15
16
 
16
17
  export type CompletionId = string | undefined;
17
18
 
19
+ export type ParentId = ChatId | CompletionId;
20
+
18
21
  export interface Completion {
19
22
  id: CompletionId;
20
23
  created: EpochTimeStamp;
@@ -24,13 +27,12 @@ export interface Completion {
24
27
  title: string;
25
28
  index: number;
26
29
  children: Completion[];
27
- parentId: CompletionId;
30
+ parentId: ParentId;
28
31
  }
29
32
 
30
33
  export interface Data {
31
34
  chatId: ChatId;
32
35
  chats: Chat[];
33
- completionId: CompletionId;
34
36
  completions: Completion[];
35
37
  }
36
38
 
@@ -39,14 +41,13 @@ export interface ChatCompletionStoreState {
39
41
  chatId: ChatId;
40
42
  chats: Chat[];
41
43
  createChat: (completion: Completion) => void;
42
- readChat: (id?: ChatId) => Chat | undefined;
44
+ readChat: (id: ChatId) => Chat | undefined;
43
45
  updateChat: (chat: Chat) => void;
44
- updateChatTitle: (title: string, id?: ChatId) => void;
45
- deleteChat: (id?: ChatId) => void;
46
+ updateChatTitle: (id: ChatId, title: string) => void;
47
+ deleteChat: (id: ChatId) => void;
46
48
  resetChat: () => void;
47
- loadChat: (id?: ChatId) => void;
49
+ loadChat: (id: ChatId) => void;
48
50
  /* completions */
49
- completionId: CompletionId;
50
51
  completions: Completion[];
51
52
  createCompletion: (
52
53
  id: string,
@@ -54,15 +55,17 @@ export interface ChatCompletionStoreState {
54
55
  model: string,
55
56
  query: string,
56
57
  ) => Completion;
57
- readCompletion: (id?: CompletionId) => Completion | undefined;
58
- updateCompletion: (completion: Completion, id?: CompletionId) => void;
59
- updateCompletionTitle: (title: string, id?: CompletionId) => void;
60
- deleteCompletion: (id?: CompletionId) => void;
58
+ readCompletion: (id: CompletionId) => Completion | undefined;
59
+ updateCompletion: (id: CompletionId, completion: Completion) => void;
60
+ updateCompletionTitle: (id: CompletionId, title: string) => void;
61
+ deleteCompletion: (id: CompletionId) => void;
61
62
  findCompletion: (id: CompletionId, index: number) => Completion | undefined;
62
63
  /* tools */
63
- getParent: (id?: CompletionId) => Completion | Chat | undefined;
64
- readConversation: (id?: ChatId) => Completion[];
65
- readMessages: (id?: ChatId) => ChatCompletionMessageParam[];
64
+ getParent: (id: CompletionId) => Completion | Chat | undefined;
65
+ getCompletions: (id: ParentId) => Completion[];
66
+ updateIndex: (id: CompletionId, index: number) => void;
67
+ readConversation: (id: ParentId) => Completion[];
68
+ readMessages: (id: ParentId) => ChatCompletionMessageParam[];
66
69
  /* data */
67
70
  export: () => Data;
68
71
  import: (data: Data) => void;
@@ -80,20 +83,18 @@ const useChatCompletionStore = create<ChatCompletionStoreState>()(
80
83
  index: 0,
81
84
  };
82
85
  get().chats.push(chat);
83
- set({chatId: chat.id, completionId: completion.id});
86
+ set({chatId: chat.id});
84
87
  completion.parentId = chat.id;
85
88
  get().completions.push(completion);
86
89
  },
87
90
  readChat: (id?: ChatId): Chat | undefined =>
88
- get().chats.find((chat: Chat) =>
89
- Boolean((id || get().chatId) === chat.id),
90
- ),
91
+ get().chats.find((chat: Chat) => (id || get().chatId) === chat.id),
91
92
  updateChat: (chat: Chat) => {
92
93
  set({
93
94
  chats: get().chats.map((c: Chat) => (c.id === chat.id ? {...chat} : c)),
94
95
  });
95
96
  },
96
- updateChatTitle: (title: string, id?: ChatId) => {
97
+ updateChatTitle: (id: ChatId, title: string) => {
97
98
  const chat = get().readChat(id);
98
99
  if (!chat) return;
99
100
  chat.title = title;
@@ -112,24 +113,14 @@ const useChatCompletionStore = create<ChatCompletionStoreState>()(
112
113
  });
113
114
  },
114
115
  resetChat: () => {
115
- set({
116
- chatId: undefined,
117
- completionId: undefined,
118
- });
116
+ set({chatId: undefined});
119
117
  },
120
118
  loadChat: (id?: ChatId) => {
121
119
  const chat = get().readChat(id);
122
120
  if (!chat) return;
123
- set({
124
- chatId: chat.id,
125
- completionId:
126
- get().completions.find((completion) =>
127
- Boolean(completion.parentId === chat.id),
128
- )?.id || undefined,
129
- });
121
+ set({chatId: chat.id});
130
122
  },
131
123
  /* completions */
132
- completionId: undefined,
133
124
  completions: [],
134
125
  createCompletion: (
135
126
  id: string,
@@ -150,34 +141,30 @@ const useChatCompletionStore = create<ChatCompletionStoreState>()(
150
141
  };
151
142
  return completion;
152
143
  },
153
- readCompletion: (id?: CompletionId): Completion | undefined => {
154
- const completionId = id || get().completionId;
144
+ readCompletion: (id: CompletionId): Completion | undefined => {
155
145
  const queue: Completion[] = [...get().completions];
156
146
  let i = 0;
157
147
  while (i < queue.length) {
158
148
  const current = queue[i++];
159
- if (current.id === completionId) return current;
149
+ if (current.id === id) return current;
160
150
  if (current.children.length > 0) queue.push(...current.children);
161
151
  }
162
152
  return undefined;
163
153
  },
164
- updateCompletion: (completion: Completion, id?: CompletionId) => {
165
- const parent = get().readCompletion(id);
166
- if (parent) {
167
- completion.parentId = parent.id;
154
+ updateCompletion: (id: ParentId, completion: Completion) => {
155
+ const parent = get().getParent(id);
156
+ if (!parent) return;
157
+ completion.parentId = parent.id;
158
+ if ("children" in parent) {
168
159
  parent.children.push(completion);
169
160
  parent.index = parent.children.length - 1;
170
- } else if (get().chatId) {
171
- const chat = get().readChat();
172
- if (!chat) return;
173
- completion.parentId = chat.id;
161
+ } else {
174
162
  get().completions.push(completion);
175
- chat.index =
176
- get().completions.filter((c) => c.parentId === chat.id).length - 1;
177
- } else return;
178
- set({completionId: completion.id});
163
+ parent.index =
164
+ get().completions.filter((c) => c.parentId === parent.id).length - 1;
165
+ }
179
166
  },
180
- updateCompletionTitle: (title: string, id?: CompletionId) => {
167
+ updateCompletionTitle: (id: CompletionId, title: string) => {
181
168
  const completion = get().readCompletion(id);
182
169
  if (!completion) return;
183
170
  completion.title = title;
@@ -186,55 +173,79 @@ const useChatCompletionStore = create<ChatCompletionStoreState>()(
186
173
  deleteCompletion: (id?: CompletionId) => {
187
174
  const completion = get().readCompletion(id);
188
175
  if (!completion) return;
189
- const parent = get().readCompletion(completion.parentId);
190
- if (parent) {
191
- parent.children = parent.children.filter((c) => c.id !== completion.id);
192
- parent.index = parent.children.length - 1;
193
- set({
194
- completionId: parent.id,
195
- completions: [...get().completions],
196
- });
197
- } else if (get().completions.length > 0) {
198
- set({
199
- completionId: undefined,
200
- completions: get().completions.filter((c) => c.id !== completion.id),
201
- });
176
+ const parent = get().getParent(completion.parentId);
177
+ const completions = get().completions.filter(
178
+ (c) => c.id !== completion.id,
179
+ );
180
+ set({completions: [...completions]});
181
+ /* delete the chat if the deleted completion is the last one */
182
+ if (completions.length === 0) get().deleteChat(completion.parentId);
183
+ if (!parent) return;
184
+ if ("children" in parent) {
185
+ parent.children = parent.children.filter((c) => c.id !== id);
202
186
  }
203
- },
204
- getParent: (id?: CompletionId): Completion | Chat | undefined => {
205
- const completion = get().readCompletion(id);
206
- if (!completion) return;
207
- const parent =
208
- get().readCompletion(completion.parentId) ||
209
- get().readChat(completion.parentId);
210
- return parent;
187
+ get().updateIndex(parent.id, parent.index - 1);
211
188
  },
212
189
  findCompletion: (
213
190
  id: CompletionId,
214
191
  index: number,
215
192
  ): Completion | undefined => {
216
- const parent = get().getParent(id);
217
- if (!parent || !("children" in parent)) return;
218
- return parent.children && parent.children?.[index];
193
+ const completion = get().readCompletion(id);
194
+ if (!completion) return;
195
+ const parent = get().getParent(completion.parentId);
196
+ if (!parent) return;
197
+ return get()
198
+ .getCompletions(parent.id)
199
+ .filter((c) => c.parentId === parent.id)[index];
219
200
  },
220
201
  /* tools */
221
- readConversation: (id?: ChatId): Completion[] => {
222
- const conversation: Completion[] = [];
223
- let current = get().completions.find((completion) =>
224
- Boolean(completion.parentId === (id || get().chatId)),
202
+ getParent: (id?: ParentId): Completion | Chat | undefined =>
203
+ get().readCompletion(id) || get().readChat(id),
204
+ getCompletions: (id?: ParentId): Completion[] => {
205
+ let completions: Completion[] = [];
206
+ const parent = get().getParent(id);
207
+ if (!parent) return completions;
208
+ if ("children" in parent) completions = parent.children;
209
+ else
210
+ completions = completions.concat(
211
+ get().completions.filter((c) => c.parentId === parent.id),
212
+ );
213
+ return completions.sort((a, b) => a.created - b.created);
214
+ },
215
+ updateIndex: (id: ParentId, index: number) => {
216
+ const parent = get().getParent(id);
217
+ if (!parent) return;
218
+ const completions = get().getCompletions(parent.id);
219
+ parent.index = clamp(index, 0, completions.length - 1);
220
+ const conversation = get().readConversation(get().chatId);
221
+ get().updateChatTitle(
222
+ get().chatId,
223
+ conversation[conversation.length - 1]?.title || "",
225
224
  );
225
+ },
226
+ readConversation: (id: ParentId): Completion[] => {
227
+ const conversation: Completion[] = [];
228
+ const chat = get().readChat(id);
229
+ if (!chat) return [];
230
+ const completions = get()
231
+ .completions.filter((c) => c.parentId === chat.id)
232
+ .sort((a, b) => a.created - b.created);
233
+ let current = completions[clamp(chat.index, 0, completions.length - 1)];
226
234
  if (!current) return [];
227
235
  conversation.push(current);
228
236
  if (!current?.children) return conversation;
229
237
  while (current.children.length > 0) {
230
- current = current.children[current.index ?? 0];
238
+ current =
239
+ current.children[
240
+ clamp(current.index, 0, current.children.length - 1)
241
+ ];
231
242
  conversation.push(current);
232
243
  }
233
244
  return conversation;
234
245
  },
235
- readMessages: (): ChatCompletionMessageParam[] =>
246
+ readMessages: (id: ParentId): ChatCompletionMessageParam[] =>
236
247
  get()
237
- .readConversation()
248
+ .readConversation(id)
238
249
  .map((completion: Completion) => {
239
250
  return [
240
251
  {role: "user", content: completion.prompt},
@@ -249,10 +260,6 @@ const useChatCompletionStore = create<ChatCompletionStoreState>()(
249
260
  get().chats.find((chat: Chat) => Boolean(chat.id === get().chatId))
250
261
  ?.id || undefined,
251
262
  chats: get().chats,
252
- completionId:
253
- get().completions.find((completion: Completion) =>
254
- Boolean(completion.id === get().completionId),
255
- )?.id || undefined,
256
263
  completions: get().completions.filter((completion: Completion) =>
257
264
  Boolean(
258
265
  get().chats.find((chat: Chat) =>
@@ -266,7 +273,6 @@ const useChatCompletionStore = create<ChatCompletionStoreState>()(
266
273
  set({
267
274
  chatId: data.chatId,
268
275
  chats: [...data.chats],
269
- completionId: data.completionId,
270
276
  completions: [...data.completions],
271
277
  });
272
278
  },
@@ -274,142 +280,3 @@ const useChatCompletionStore = create<ChatCompletionStoreState>()(
274
280
  );
275
281
 
276
282
  export default useChatCompletionStore;
277
-
278
- /*
279
- updateChat: (newChat: Chat) => {
280
- const updatedChats: Chat[] = get().chats.map(
281
- (chat: Chat): Chat => (chat.id === newChat.id ? {...newChat} : chat),
282
- );
283
- set({chats: updatedChats});
284
- get().setConversation();
285
- get().setMessages();
286
- },
287
- updateChatTitle: (title: string, id?: ChatId) => {
288
- const chat = get().getChat(id);
289
- if (!chat) return;
290
- chat.title = title;
291
- get().updateChat(chat);
292
- },
293
- loadChat: (id?: ChatId) => {
294
- const chat = get().getChat(id);
295
- if (!chat) return;
296
- set({
297
- chatId: chat.id,
298
- chat,
299
- completions: chat.completions,
300
- });
301
- const completion = get().getCompletion(get().getCompletionId());
302
- if (completion) set({completionId: completion.id, completion});
303
- get().setConversation();
304
- get().setMessages();
305
- console.info(get().chatId);
306
- console.info(get().chat);
307
- console.info(get().completions);
308
- console.info(get().completionId);
309
- console.info(get().completion);
310
- console.info(get().conversation);
311
- console.info(get().messages);
312
- },
313
- deleteChat: (id?: ChatId) => {
314
- const index = get().chats.findIndex((chat: Chat) => id === chat.id);
315
- if (index === -1) return;
316
- const updatedChats = [...get().chats];
317
- updatedChats.splice(index, 1);
318
- get().setChats(updatedChats);
319
- get().resetChat();
320
- },
321
- resetChat: () => {
322
- set({
323
- completionId: undefined,
324
- completion: undefined,
325
- completions: [],
326
- chatId: undefined,
327
- chat: undefined,
328
- conversation: [],
329
- messages: [],
330
- });
331
- },
332
- chats: [],
333
- getChats: () => get().chats,
334
- setChats: (chats?: Chat[]) => set({chats}),
335
- completionId: undefined,
336
- setCompletionId: (id?: CompletionId) => set({completionId: id}),
337
- getCompletionId: () => {
338
- const completions = get().completions;
339
- if (!completions.length) return;
340
- let current = completions[get().chat?.index ?? completions.length - 1];
341
- if (!current?.children) return;
342
- while (current.children.length > 0)
343
- current =
344
- current.children[current.index ?? current.children.length - 1];
345
- return current.id;
346
- },
347
- completion: undefined,
348
- setCompletion: (completion?: Completion) => set({completion}),
349
- appendCompletion: () => {
350
- const completion = get().completion;
351
- if (!completion) return;
352
- if (completion.parentId) {
353
- const parent = get().getCompletion(completion.parentId);
354
- if (!parent) return;
355
- console.info("append completion to", parent.id);
356
- completion.parentId = parent.id;
357
- parent.children.push(completion);
358
- parent.index = parent.children.length - 1;
359
- } else {
360
- const chat = get().getChat();
361
- if (!chat) return;
362
- console.info("append completion to", chat.id);
363
- completion.parentId = undefined;
364
- chat.completions.push(completion);
365
- chat.index = chat.completions.length - 1;
366
- }
367
- set({completionId: completion.id, completion});
368
- },
369
- loadCompletion: (id?: CompletionId) => {
370
- const completion = get().getCompletion(id);
371
- if (!completion) return;
372
- set({completionId: completion.id, completion});
373
- get().setConversation();
374
- get().setMessages();
375
- },
376
- deleteCompletion: (id?: CompletionId) => {
377
- const completion = get().getCompletion(id);
378
- if (!completion) return;
379
- const parent = get().getCompletion(completion.parentId);
380
- if (parent) {
381
- parent.children = parent.children.filter((c) => c.id !== completion.id);
382
- parent.index = parent.children.length - 1;
383
- set({
384
- completionId: parent.id,
385
- completion: parent,
386
- completions: [...get().completions],
387
- });
388
- } else if (get().completions.length > 0) {
389
- set({
390
- completions: get().completions.filter((c) => c.id !== completion.id),
391
- });
392
- }
393
- },
394
- setCompletionIndex: (id?: CompletionId, index?: number) => {
395
- const completion = get().getCompletion(id);
396
- if (!completion) return;
397
- completion.index = clamp(index, 0, completion.children.length - 1);
398
- get().loadCompletion(completion.id);
399
- },
400
- completions: [],
401
- setCompletions: (id?: ChatId) =>
402
- set({completions: get().getCompletions(id)}),
403
- getCompletions: (id?: ChatId) => get().getChat(id)?.completions || [],
404
- updateCompletions: (id?: ChatId) => {
405
- const chat = get().getChat(id);
406
- if (!chat) return;
407
- get().updateChat({...chat, completions: get().completions});
408
- },
409
- conversation: [],
410
- setConversation: (id?: ChatId) =>
411
- set({conversation: get().getConversation(id)}),
412
- messages: [],
413
- setMessages: (id?: ChatId) => set({messages: get().getMessages(id)}),
414
- getMessages: (id?: ChatId) =>
415
- */
@@ -1,4 +1,4 @@
1
- import {clamp} from "@utils/math";
1
+ import {clamp, format} from "@utils/math";
2
2
  import {getVariableFromCSS, setVariableToCSS} from "@utils/styles";
3
3
 
4
4
  import Config, {ConfigValue} from "@console/config";
@@ -53,17 +53,17 @@ const cmd = (
53
53
  }
54
54
  switch (arg1) {
55
55
  case "h":
56
- value = clamp(parseFloat(arg2), 0, 360).toFixed(1);
56
+ value = format(clamp(parseFloat(arg2), 0, 360), 0);
57
57
  break;
58
58
  case "s":
59
- value = clamp(parseInt(arg2), 0, 100).toFixed(0);
59
+ value = format(clamp(parseFloat(arg2), 0, 100), 0);
60
60
  break;
61
61
  case "l":
62
- value = clamp(parseInt(arg2), 0, 100).toFixed(0);
62
+ value = format(clamp(parseFloat(arg2), 0, 100), 0);
63
63
  break;
64
64
  }
65
- setVariableToCSS(arg1, value);
66
- config.update(cmd, arg1, value);
65
+ setVariableToCSS(arg1, value.toString());
66
+ config.update(cmd, arg1, value.toString());
67
67
  break;
68
68
  case "size":
69
69
  value = clamp(
package/vite.config.ts CHANGED
@@ -41,6 +41,12 @@ export default defineConfig(({mode}) => {
41
41
  host: true,
42
42
  port: 3000,
43
43
  allowedHosts: [env.DOMAIN],
44
+ proxy: {
45
+ "/api": {
46
+ target: "https://omnibot3000.onrender.com",
47
+ changeOrigin: true,
48
+ },
49
+ },
44
50
  watch: {
45
51
  usePolling: false /* speed up updates */,
46
52
  ignored: ["**/node_modules/**", "**/dist/**"],