|
2 | 2 | <html>
|
3 | 3 | <head>
|
4 | 4 | <meta charset="utf-8" />
|
| 5 | + <meta name="description" content="ChatGPT API Demo" /> |
5 | 6 | <meta name="viewport" content="width=device-width, height=device-height" />
|
6 | 7 | <title>OpenAI Chat</title>
|
7 | 8 | <style>
|
|
25 | 26 | button {
|
26 | 27 | border: none;
|
27 | 28 | background-color: transparent;
|
28 |
| - color: white; |
| 29 | + color: inherit; |
| 30 | + padding: 0; |
29 | 31 | cursor: pointer;
|
30 | 32 | }
|
| 33 | + ul { |
| 34 | + list-style: none; |
| 35 | + padding: 0; |
| 36 | + margin: 0; |
| 37 | + } |
31 | 38 | #app {
|
32 | 39 | display: flex;
|
33 | 40 | flex-direction: column;
|
|
43 | 50 | background-color: #333;
|
44 | 51 | color: white;
|
45 | 52 | }
|
46 |
| - header>button { |
| 53 | + header > button { |
47 | 54 | position: absolute;
|
48 | 55 | padding: 12px;
|
49 | 56 | }
|
| 57 | + button.menu { |
| 58 | + left: 0; |
| 59 | + } |
50 | 60 | button.clear {
|
51 | 61 | right: 0;
|
| 62 | + } |
| 63 | + .hidden { |
| 64 | + display: none; |
| 65 | + } |
| 66 | + aside { |
| 67 | + position: fixed; |
| 68 | + top: 0; |
| 69 | + width: 100%; |
52 | 70 | height: 100%;
|
53 |
| - font-size: 16px; |
| 71 | + z-index: 1; |
| 72 | + display: flex; |
| 73 | + } |
| 74 | + .sidebar-container { |
| 75 | + background-color: #333; |
| 76 | + color: white; |
| 77 | + width: 300px; |
| 78 | + overflow-y: auto; |
| 79 | + } |
| 80 | + .sidebar-modal { |
| 81 | + flex: 1 0; |
| 82 | + min-width: 64px; |
| 83 | + background-color: rgba(0, 0, 0, 0.3); |
| 84 | + } |
| 85 | + li > button { |
| 86 | + width: 100%; |
| 87 | + height: 48px; |
| 88 | + text-align: left; |
| 89 | + padding: 0 16px; |
| 90 | + transition: background-color 0.2s; |
| 91 | + } |
| 92 | + li > button:hover { |
| 93 | + background-color: rgba(255, 255, 255, 0.1); |
54 | 94 | }
|
55 | 95 | main {
|
56 | 96 | flex-grow: 1;
|
|
215 | 255 | );
|
216 | 256 | }
|
217 | 257 |
|
| 258 | + export function Sidebar({ |
| 259 | + numConversations, |
| 260 | + onConversationClick, |
| 261 | + onModalClick, |
| 262 | + onClearClick, |
| 263 | + onSettingsClick, |
| 264 | + }) { |
| 265 | + return [ |
| 266 | + React.createElement( |
| 267 | + "div", |
| 268 | + { key: "sidebar-container", className: "sidebar-container" }, |
| 269 | + [ |
| 270 | + React.createElement("ul", { key: "list" }, [ |
| 271 | + React.createElement("li", { key: "new" }, [ |
| 272 | + React.createElement( |
| 273 | + "button", |
| 274 | + { |
| 275 | + key: "new", |
| 276 | + onClick: () => { |
| 277 | + onConversationClick && onConversationClick(-1); |
| 278 | + }, |
| 279 | + }, |
| 280 | + "New Chat" |
| 281 | + ), |
| 282 | + ]), |
| 283 | + ...Array(numConversations) |
| 284 | + .fill(null) |
| 285 | + .map((_, index) => { |
| 286 | + return React.createElement("li", { key: index }, [ |
| 287 | + React.createElement( |
| 288 | + "button", |
| 289 | + { |
| 290 | + key: `conversation-${index}`, |
| 291 | + onClick: () => { |
| 292 | + onConversationClick && onConversationClick(index); |
| 293 | + }, |
| 294 | + }, |
| 295 | + `Conversation ${index + 1}` |
| 296 | + ), |
| 297 | + ]); |
| 298 | + }), |
| 299 | + React.createElement("li", { key: "clear" }, [ |
| 300 | + React.createElement( |
| 301 | + "button", |
| 302 | + { |
| 303 | + key: "clear", |
| 304 | + onClick: () => { |
| 305 | + onClearClick && onClearClick(); |
| 306 | + }, |
| 307 | + }, |
| 308 | + "Clear Conversations" |
| 309 | + ), |
| 310 | + ]), |
| 311 | + React.createElement("li", { key: "settings" }, [ |
| 312 | + React.createElement( |
| 313 | + "button", |
| 314 | + { |
| 315 | + key: "settings", |
| 316 | + onClick: () => { |
| 317 | + onSettingsClick && onSettingsClick(); |
| 318 | + }, |
| 319 | + }, |
| 320 | + "Settings" |
| 321 | + ), |
| 322 | + ]), |
| 323 | + ]), |
| 324 | + ] |
| 325 | + ), |
| 326 | + React.createElement("div", { |
| 327 | + key: "sidebar-modal", |
| 328 | + className: "sidebar-modal", |
| 329 | + onClick: () => { |
| 330 | + onModalClick && onModalClick(); |
| 331 | + }, |
| 332 | + }), |
| 333 | + ]; |
| 334 | + } |
| 335 | + |
218 | 336 | export function App() {
|
219 | 337 | let token = null;
|
220 | 338 | getApiUrl().then(() => {
|
221 | 339 | if (getApiUrl.tokenRequired) token = getToken();
|
222 | 340 | });
|
223 |
| - const sessions = JSON.parse(localStorage.getItem("sessions") || "[]"); |
224 |
| - sessions[0] = sessions[0] || []; |
| 341 | + const conversations = JSON.parse( |
| 342 | + localStorage.getItem("conversations") || "[]" |
| 343 | + ); |
| 344 | + conversations[0] = conversations[0] || []; |
225 | 345 |
|
226 |
| - const [messages, setMessages] = React.useState(sessions[0]); |
| 346 | + const [conversationIndex, setConversationIndex] = React.useState(0); |
| 347 | + const [numConversations, setNumConversations] = React.useState( |
| 348 | + conversations.length |
| 349 | + ); |
| 350 | + const [messages, setMessages] = React.useState( |
| 351 | + conversations[conversationIndex] |
| 352 | + ); |
227 | 353 | const messagesRef = React.useRef(messages);
|
228 |
| - const saveMessages = (value) => { |
| 354 | + const saveMessages = (value, index) => { |
229 | 355 | setMessages(value);
|
230 |
| - localStorage.setItem("sessions", JSON.stringify([value])); |
| 356 | + conversations[index] = value; |
| 357 | + localStorage.setItem("conversations", JSON.stringify(conversations)); |
231 | 358 | };
|
232 | 359 |
|
| 360 | + const [showSidebar, setShowSidebar] = React.useState(false); |
| 361 | + |
233 | 362 | return [
|
234 | 363 | React.createElement("header", { key: "header" }, [
|
235 | 364 | React.createElement("span", { key: "title" }, "OpenAI Chat"),
|
| 365 | + React.createElement( |
| 366 | + "button", |
| 367 | + { |
| 368 | + key: "menu", |
| 369 | + onClick: () => { |
| 370 | + setShowSidebar(!showSidebar); |
| 371 | + }, |
| 372 | + className: "menu", |
| 373 | + }, |
| 374 | + React.createElement("img", { |
| 375 | + src: "/assets/img/menu.svg", |
| 376 | + width: "24", |
| 377 | + height: "24", |
| 378 | + alt: "Menu", |
| 379 | + }) |
| 380 | + ), |
236 | 381 | React.createElement(
|
237 | 382 | "button",
|
238 | 383 | {
|
239 | 384 | key: "clear",
|
240 | 385 | onClick: () => {
|
241 |
| - saveMessages([]); |
| 386 | + saveMessages([], conversationIndex); |
242 | 387 | messagesRef.current = [];
|
243 | 388 | },
|
244 | 389 | className: "clear",
|
|
252 | 397 | ),
|
253 | 398 | ]),
|
254 | 399 |
|
| 400 | + React.createElement( |
| 401 | + "aside", |
| 402 | + { |
| 403 | + key: "sidebar", |
| 404 | + className: showSidebar ? "sidebar" : "sidebar hidden", |
| 405 | + }, |
| 406 | + |
| 407 | + React.createElement(Sidebar, { |
| 408 | + messages, |
| 409 | + key: "sidebar", |
| 410 | + numConversations, |
| 411 | + |
| 412 | + onConversationClick: (index) => { |
| 413 | + if (index === -1) { |
| 414 | + conversations.unshift([]); |
| 415 | + setNumConversations(conversations.length); |
| 416 | + index = 0; |
| 417 | + } |
| 418 | + setConversationIndex(index); |
| 419 | + saveMessages(conversations[index], index); |
| 420 | + messagesRef.current = conversations[index]; |
| 421 | + setShowSidebar(false); |
| 422 | + }, |
| 423 | + |
| 424 | + onModalClick: () => { |
| 425 | + setShowSidebar(false); |
| 426 | + }, |
| 427 | + |
| 428 | + onClearClick: () => { |
| 429 | + localStorage.removeItem("conversations"); |
| 430 | + setNumConversations(1); |
| 431 | + setConversationIndex(0); |
| 432 | + messagesRef.current = []; |
| 433 | + setMessages([]); |
| 434 | + setShowSidebar(false); |
| 435 | + }, |
| 436 | + }) |
| 437 | + ), |
| 438 | + |
255 | 439 | React.createElement(
|
256 | 440 | "main",
|
257 | 441 | { key: "main" },
|
|
282 | 466 | ...messagesRef.current,
|
283 | 467 | { role: "user", content: message },
|
284 | 468 | ];
|
285 |
| - saveMessages(messagesRef.current); |
| 469 | + saveMessages(messagesRef.current, conversationIndex); |
286 | 470 | try {
|
287 | 471 | const completion = await complete(
|
288 | 472 | messagesRef.current,
|
289 | 473 | token,
|
290 | 474 | (message) =>
|
291 |
| - saveMessages([...messagesRef.current, message]) |
| 475 | + saveMessages( |
| 476 | + [...messagesRef.current, message], |
| 477 | + conversationIndex |
| 478 | + ) |
292 | 479 | );
|
293 | 480 | messagesRef.current = [...messagesRef.current, completion];
|
294 | 481 | } catch (error) {
|
|
0 commit comments