matt HOFFNER commited on
Commit
22ff301
Β·
1 Parent(s): ed45bdf
package-lock.json CHANGED
@@ -22,6 +22,7 @@
22
  "next": "13.4.2",
23
  "react": "18.2.0",
24
  "react-dom": "18.2.0",
 
25
  "typescript": "5.0.4",
26
  "uuid": "^9.0.0"
27
  },
@@ -137,6 +138,18 @@
137
  "node": ">=6.9.0"
138
  }
139
  },
 
 
 
 
 
 
 
 
 
 
 
 
140
  "node_modules/@babel/helper-compilation-targets": {
141
  "version": "7.22.1",
142
  "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz",
@@ -478,6 +491,33 @@
478
  "node": ">=6.9.0"
479
  }
480
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  "node_modules/@esbuild/android-arm": {
482
  "version": "0.17.19",
483
  "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
@@ -1777,6 +1817,28 @@
1777
  "deep-equal": "^2.0.5"
1778
  }
1779
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1780
  "node_modules/balanced-match": {
1781
  "version": "1.0.2",
1782
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1962,6 +2024,15 @@
1962
  "node": ">=6"
1963
  }
1964
  },
 
 
 
 
 
 
 
 
 
1965
  "node_modules/caniuse-lite": {
1966
  "version": "1.0.30001492",
1967
  "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz",
@@ -2096,6 +2167,26 @@
2096
  "node": ">= 8"
2097
  }
2098
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2099
  "node_modules/csstype": {
2100
  "version": "3.1.2",
2101
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
@@ -3468,6 +3559,15 @@
3468
  "url": "https://github.com/sponsors/ljharb"
3469
  }
3470
  },
 
 
 
 
 
 
 
 
 
3471
  "node_modules/human-signals": {
3472
  "version": "4.3.1",
3473
  "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
@@ -4304,6 +4404,12 @@
4304
  "url": "https://github.com/sponsors/sindresorhus"
4305
  }
4306
  },
 
 
 
 
 
 
4307
  "node_modules/lodash.merge": {
4308
  "version": "4.6.2",
4309
  "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -5282,6 +5388,26 @@
5282
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
5283
  "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
5284
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5285
  "node_modules/readable-stream": {
5286
  "version": "3.6.2",
5287
  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -5628,6 +5754,12 @@
5628
  "node": ">=10"
5629
  }
5630
  },
 
 
 
 
 
 
5631
  "node_modules/sharp": {
5632
  "version": "0.32.1",
5633
  "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.1.tgz",
@@ -5888,6 +6020,57 @@
5888
  "url": "https://github.com/sponsors/sindresorhus"
5889
  }
5890
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5891
  "node_modules/styled-jsx": {
5892
  "version": "5.1.1",
5893
  "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
 
22
  "next": "13.4.2",
23
  "react": "18.2.0",
24
  "react-dom": "18.2.0",
25
+ "react95": "^4.0.0",
26
  "typescript": "5.0.4",
27
  "uuid": "^9.0.0"
28
  },
 
138
  "node": ">=6.9.0"
139
  }
140
  },
141
+ "node_modules/@babel/helper-annotate-as-pure": {
142
+ "version": "7.18.6",
143
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
144
+ "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
145
+ "peer": true,
146
+ "dependencies": {
147
+ "@babel/types": "^7.18.6"
148
+ },
149
+ "engines": {
150
+ "node": ">=6.9.0"
151
+ }
152
+ },
153
  "node_modules/@babel/helper-compilation-targets": {
154
  "version": "7.22.1",
155
  "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz",
 
491
  "node": ">=6.9.0"
492
  }
493
  },
494
+ "node_modules/@emotion/is-prop-valid": {
495
+ "version": "1.2.1",
496
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
497
+ "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
498
+ "peer": true,
499
+ "dependencies": {
500
+ "@emotion/memoize": "^0.8.1"
501
+ }
502
+ },
503
+ "node_modules/@emotion/memoize": {
504
+ "version": "0.8.1",
505
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
506
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
507
+ "peer": true
508
+ },
509
+ "node_modules/@emotion/stylis": {
510
+ "version": "0.8.5",
511
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
512
+ "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==",
513
+ "peer": true
514
+ },
515
+ "node_modules/@emotion/unitless": {
516
+ "version": "0.7.5",
517
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
518
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
519
+ "peer": true
520
+ },
521
  "node_modules/@esbuild/android-arm": {
522
  "version": "0.17.19",
523
  "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
 
1817
  "deep-equal": "^2.0.5"
1818
  }
1819
  },
1820
+ "node_modules/babel-plugin-styled-components": {
1821
+ "version": "2.1.3",
1822
+ "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.3.tgz",
1823
+ "integrity": "sha512-jBioLwBVHpOMU4NsueH/ADcHrjS0Y/WTpt2eGVmmuSFNEv2DF3XhcMncuZlbbjxQ4vzxg+yEr6E6TNjrIQbsJQ==",
1824
+ "peer": true,
1825
+ "dependencies": {
1826
+ "@babel/helper-annotate-as-pure": "^7.18.6",
1827
+ "@babel/helper-module-imports": "^7.21.4",
1828
+ "babel-plugin-syntax-jsx": "^6.18.0",
1829
+ "lodash": "^4.17.21",
1830
+ "picomatch": "^2.3.1"
1831
+ },
1832
+ "peerDependencies": {
1833
+ "styled-components": ">= 2"
1834
+ }
1835
+ },
1836
+ "node_modules/babel-plugin-syntax-jsx": {
1837
+ "version": "6.18.0",
1838
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
1839
+ "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==",
1840
+ "peer": true
1841
+ },
1842
  "node_modules/balanced-match": {
1843
  "version": "1.0.2",
1844
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
 
2024
  "node": ">=6"
2025
  }
2026
  },
2027
+ "node_modules/camelize": {
2028
+ "version": "1.0.1",
2029
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
2030
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
2031
+ "peer": true,
2032
+ "funding": {
2033
+ "url": "https://github.com/sponsors/ljharb"
2034
+ }
2035
+ },
2036
  "node_modules/caniuse-lite": {
2037
  "version": "1.0.30001492",
2038
  "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz",
 
2167
  "node": ">= 8"
2168
  }
2169
  },
2170
+ "node_modules/css-color-keywords": {
2171
+ "version": "1.0.0",
2172
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
2173
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
2174
+ "peer": true,
2175
+ "engines": {
2176
+ "node": ">=4"
2177
+ }
2178
+ },
2179
+ "node_modules/css-to-react-native": {
2180
+ "version": "3.2.0",
2181
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
2182
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
2183
+ "peer": true,
2184
+ "dependencies": {
2185
+ "camelize": "^1.0.0",
2186
+ "css-color-keywords": "^1.0.0",
2187
+ "postcss-value-parser": "^4.0.2"
2188
+ }
2189
+ },
2190
  "node_modules/csstype": {
2191
  "version": "3.1.2",
2192
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
 
3559
  "url": "https://github.com/sponsors/ljharb"
3560
  }
3561
  },
3562
+ "node_modules/hoist-non-react-statics": {
3563
+ "version": "3.3.2",
3564
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
3565
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
3566
+ "peer": true,
3567
+ "dependencies": {
3568
+ "react-is": "^16.7.0"
3569
+ }
3570
+ },
3571
  "node_modules/human-signals": {
3572
  "version": "4.3.1",
3573
  "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
 
4404
  "url": "https://github.com/sponsors/sindresorhus"
4405
  }
4406
  },
4407
+ "node_modules/lodash": {
4408
+ "version": "4.17.21",
4409
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
4410
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
4411
+ "peer": true
4412
+ },
4413
  "node_modules/lodash.merge": {
4414
  "version": "4.6.2",
4415
  "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
 
5388
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
5389
  "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
5390
  },
5391
+ "node_modules/react95": {
5392
+ "version": "4.0.0",
5393
+ "resolved": "https://registry.npmjs.org/react95/-/react95-4.0.0.tgz",
5394
+ "integrity": "sha512-C/2Ecws+ShHMJa3/ki6d3ufCukEYyZ+up45NJhX+uecFh5sXNdkEX8bPZ4pKTGl0mSTMkiCJTGOJB1Gmf1W3dA==",
5395
+ "funding": [
5396
+ {
5397
+ "type": "paypal",
5398
+ "url": "https://www.paypal.me/react95"
5399
+ },
5400
+ {
5401
+ "type": "patreon",
5402
+ "url": "https://www.patreon.com/arturbien"
5403
+ }
5404
+ ],
5405
+ "peerDependencies": {
5406
+ "react": ">= 16.8.0",
5407
+ "react-dom": ">= 16.8.0",
5408
+ "styled-components": ">= 5.3.3"
5409
+ }
5410
+ },
5411
  "node_modules/readable-stream": {
5412
  "version": "3.6.2",
5413
  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
 
5754
  "node": ">=10"
5755
  }
5756
  },
5757
+ "node_modules/shallowequal": {
5758
+ "version": "1.1.0",
5759
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
5760
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
5761
+ "peer": true
5762
+ },
5763
  "node_modules/sharp": {
5764
  "version": "0.32.1",
5765
  "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.1.tgz",
 
6020
  "url": "https://github.com/sponsors/sindresorhus"
6021
  }
6022
  },
6023
+ "node_modules/styled-components": {
6024
+ "version": "5.3.11",
6025
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz",
6026
+ "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==",
6027
+ "peer": true,
6028
+ "dependencies": {
6029
+ "@babel/helper-module-imports": "^7.0.0",
6030
+ "@babel/traverse": "^7.4.5",
6031
+ "@emotion/is-prop-valid": "^1.1.0",
6032
+ "@emotion/stylis": "^0.8.4",
6033
+ "@emotion/unitless": "^0.7.4",
6034
+ "babel-plugin-styled-components": ">= 1.12.0",
6035
+ "css-to-react-native": "^3.0.0",
6036
+ "hoist-non-react-statics": "^3.0.0",
6037
+ "shallowequal": "^1.1.0",
6038
+ "supports-color": "^5.5.0"
6039
+ },
6040
+ "engines": {
6041
+ "node": ">=10"
6042
+ },
6043
+ "funding": {
6044
+ "type": "opencollective",
6045
+ "url": "https://opencollective.com/styled-components"
6046
+ },
6047
+ "peerDependencies": {
6048
+ "react": ">= 16.8.0",
6049
+ "react-dom": ">= 16.8.0",
6050
+ "react-is": ">= 16.8.0"
6051
+ }
6052
+ },
6053
+ "node_modules/styled-components/node_modules/has-flag": {
6054
+ "version": "3.0.0",
6055
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
6056
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
6057
+ "peer": true,
6058
+ "engines": {
6059
+ "node": ">=4"
6060
+ }
6061
+ },
6062
+ "node_modules/styled-components/node_modules/supports-color": {
6063
+ "version": "5.5.0",
6064
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
6065
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
6066
+ "peer": true,
6067
+ "dependencies": {
6068
+ "has-flag": "^3.0.0"
6069
+ },
6070
+ "engines": {
6071
+ "node": ">=4"
6072
+ }
6073
+ },
6074
  "node_modules/styled-jsx": {
6075
  "version": "5.1.1",
6076
  "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
package.json CHANGED
@@ -22,6 +22,7 @@
22
  "next": "13.4.2",
23
  "react": "18.2.0",
24
  "react-dom": "18.2.0",
 
25
  "typescript": "5.0.4",
26
  "uuid": "^9.0.0"
27
  },
 
22
  "next": "13.4.2",
23
  "react": "18.2.0",
24
  "react-dom": "18.2.0",
25
+ "react95": "^4.0.0",
26
  "typescript": "5.0.4",
27
  "uuid": "^9.0.0"
28
  },
src/components/Chat/Chat.tsx DELETED
@@ -1,355 +0,0 @@
1
- import { IconClearAll, IconSettings } from '@tabler/icons-react';
2
- import {
3
- MutableRefObject,
4
- memo,
5
- useCallback,
6
- useContext,
7
- useEffect,
8
- useRef,
9
- useState,
10
- } from 'react';
11
-
12
- import {
13
- saveConversation,
14
- saveConversations,
15
- updateConversation,
16
- throttle
17
- } from '@/utils';
18
-
19
- import { ChatBody, Conversation, Message } from '@/types/chat';
20
-
21
- import HomeContext from '@/pages/api/home.context';
22
-
23
- import { ChatInput } from './ChatInput';
24
- import { ChatLoader } from './ChatLoader';
25
-
26
- interface Props {
27
- stopConversationRef: MutableRefObject<boolean>;
28
- }
29
-
30
- export const Chat = memo(({ stopConversationRef }: Props) => {
31
- const {
32
- state: {
33
- selectedConversation,
34
- conversations,
35
- loading
36
- },
37
- handleUpdateConversation,
38
- dispatch: homeDispatch,
39
- }: any = useContext(HomeContext);
40
-
41
- const [currentMessage, setCurrentMessage] = useState<Message>();
42
- const [autoScrollEnabled, setAutoScrollEnabled] = useState<boolean>(true);
43
- const [showSettings, setShowSettings] = useState<boolean>(false);
44
- const [showScrollDownButton, setShowScrollDownButton] =
45
- useState<boolean>(false);
46
-
47
- const messagesEndRef = useRef<HTMLDivElement>(null);
48
- const chatContainerRef = useRef<HTMLDivElement>(null);
49
- const textareaRef = useRef<HTMLTextAreaElement>(null);
50
-
51
- const handleSend = useCallback(
52
- async (message: Message, deleteCount = 0, plugin: Plugin | null = null) => {
53
- if (selectedConversation) {
54
- let updatedConversation: Conversation;
55
- if (deleteCount) {
56
- const updatedMessages = [...selectedConversation.messages];
57
- for (let i = 0; i < deleteCount; i++) {
58
- updatedMessages.pop();
59
- }
60
- updatedConversation = {
61
- ...selectedConversation,
62
- messages: [...updatedMessages, message],
63
- };
64
- } else {
65
- updatedConversation = {
66
- ...selectedConversation,
67
- messages: [...selectedConversation.messages, message],
68
- };
69
- }
70
- homeDispatch({
71
- field: 'selectedConversation',
72
- value: updatedConversation,
73
- });
74
- homeDispatch({ field: 'loading', value: true });
75
- homeDispatch({ field: 'messageIsStreaming', value: true });
76
- const chatBody: ChatBody = {
77
- model: updatedConversation.model,
78
- messages: updatedConversation.messages,
79
- prompt: updatedConversation.prompt
80
- };
81
- const endpoint = "/v1/api/create"
82
- const body = JSON.stringify(chatBody);
83
- const controller = new AbortController();
84
- const response = await fetch(endpoint, {
85
- method: 'POST',
86
- headers: {
87
- 'Content-Type': 'application/json',
88
- },
89
- signal: controller.signal,
90
- body,
91
- });
92
- if (!response.ok) {
93
- homeDispatch({ field: 'loading', value: false });
94
- homeDispatch({ field: 'messageIsStreaming', value: false });
95
- console.error(response.statusText);
96
- return;
97
- }
98
- const data = response.body;
99
- if (!data) {
100
- homeDispatch({ field: 'loading', value: false });
101
- homeDispatch({ field: 'messageIsStreaming', value: false });
102
- return;
103
- }
104
- if (!plugin) {
105
- if (updatedConversation.messages.length === 1) {
106
- const { content } = message;
107
- const customName =
108
- content.length > 30 ? content.substring(0, 30) + '...' : content;
109
- updatedConversation = {
110
- ...updatedConversation,
111
- name: customName,
112
- };
113
- }
114
- homeDispatch({ field: 'loading', value: false });
115
- const reader = data.getReader();
116
- const decoder = new TextDecoder();
117
- let done = false;
118
- let isFirst = true;
119
- let text = '';
120
- while (!done) {
121
- if (stopConversationRef.current === true) {
122
- controller.abort();
123
- done = true;
124
- break;
125
- }
126
- const { value, done: doneReading } = await reader.read();
127
- done = doneReading;
128
- const chunkValue = decoder.decode(value);
129
- text += chunkValue;
130
- if (isFirst) {
131
- isFirst = false;
132
- const updatedMessages: Message[] = [
133
- ...updatedConversation.messages,
134
- { role: 'assistant', content: chunkValue },
135
- ];
136
- updatedConversation = {
137
- ...updatedConversation,
138
- messages: updatedMessages,
139
- };
140
- homeDispatch({
141
- field: 'selectedConversation',
142
- value: updatedConversation,
143
- });
144
- } else {
145
- const updatedMessages: Message[] =
146
- updatedConversation.messages.map((message: any, index: number) => {
147
- if (index === updatedConversation.messages.length - 1) {
148
- return {
149
- ...message,
150
- content: text,
151
- };
152
- }
153
- return message;
154
- });
155
- updatedConversation = {
156
- ...updatedConversation,
157
- messages: updatedMessages,
158
- };
159
- homeDispatch({
160
- field: 'selectedConversation',
161
- value: updatedConversation,
162
- });
163
- }
164
- }
165
- saveConversation(updatedConversation);
166
- const updatedConversations: Conversation[] = conversations.map(
167
- (conversation: { id: any; }) => {
168
- if (conversation.id === selectedConversation.id) {
169
- return updatedConversation;
170
- }
171
- return conversation;
172
- },
173
- );
174
- if (updatedConversations.length === 0) {
175
- updatedConversations.push(updatedConversation);
176
- }
177
- homeDispatch({ field: 'conversations', value: updatedConversations });
178
- saveConversations(updatedConversations);
179
- homeDispatch({ field: 'messageIsStreaming', value: false });
180
- } else {
181
- const { answer } = await response.json();
182
- const updatedMessages: Message[] = [
183
- ...updatedConversation.messages,
184
- { role: 'assistant', content: answer },
185
- ];
186
- updatedConversation = {
187
- ...updatedConversation,
188
- messages: updatedMessages,
189
- };
190
- homeDispatch({
191
- field: 'selectedConversation',
192
- value: updateConversation,
193
- });
194
- saveConversation(updatedConversation);
195
- const updatedConversations: Conversation[] = conversations.map(
196
- (conversation: { id: any; }) => {
197
- if (conversation.id === selectedConversation.id) {
198
- return updatedConversation;
199
- }
200
- return conversation;
201
- },
202
- );
203
- if (updatedConversations.length === 0) {
204
- updatedConversations.push(updatedConversation);
205
- }
206
- homeDispatch({ field: 'conversations', value: updatedConversations });
207
- saveConversations(updatedConversations);
208
- homeDispatch({ field: 'loading', value: false });
209
- homeDispatch({ field: 'messageIsStreaming', value: false });
210
- }
211
- }
212
- },
213
- [
214
- conversations,
215
- selectedConversation,
216
- stopConversationRef,
217
- ],
218
- );
219
-
220
- const handleScroll = () => {
221
- if (chatContainerRef.current) {
222
- const { scrollTop, scrollHeight, clientHeight } =
223
- chatContainerRef.current;
224
- const bottomTolerance = 30;
225
-
226
- if (scrollTop + clientHeight < scrollHeight - bottomTolerance) {
227
- setAutoScrollEnabled(false);
228
- setShowScrollDownButton(true);
229
- } else {
230
- setAutoScrollEnabled(true);
231
- setShowScrollDownButton(false);
232
- }
233
- }
234
- };
235
-
236
- const handleScrollDown = () => {
237
- chatContainerRef.current?.scrollTo({
238
- top: chatContainerRef.current.scrollHeight,
239
- behavior: 'smooth',
240
- });
241
- };
242
-
243
- const handleSettings = () => {
244
- setShowSettings(!showSettings);
245
- };
246
-
247
- const onClearAll = () => {
248
- if (
249
- confirm('Are you sure you want to clear all messages?') &&
250
- selectedConversation
251
- ) {
252
- handleUpdateConversation(selectedConversation, {
253
- key: 'messages',
254
- value: [],
255
- });
256
- }
257
- };
258
-
259
- const scrollDown = () => {
260
- if (autoScrollEnabled) {
261
- messagesEndRef.current?.scrollIntoView(true);
262
- }
263
- };
264
- const throttledScrollDown = throttle(scrollDown, 250);
265
-
266
- useEffect(() => {
267
- throttledScrollDown();
268
- selectedConversation &&
269
- setCurrentMessage(
270
- selectedConversation.messages[selectedConversation.messages.length - 2],
271
- );
272
- }, [selectedConversation, throttledScrollDown]);
273
-
274
- useEffect(() => {
275
- const observer = new IntersectionObserver(
276
- ([entry]) => {
277
- setAutoScrollEnabled(entry.isIntersecting);
278
- if (entry.isIntersecting) {
279
- textareaRef.current?.focus();
280
- }
281
- },
282
- {
283
- root: null,
284
- threshold: 0.5,
285
- },
286
- );
287
- const messagesEndElement = messagesEndRef.current;
288
- if (messagesEndElement) {
289
- observer.observe(messagesEndElement);
290
- }
291
- return () => {
292
- if (messagesEndElement) {
293
- observer.unobserve(messagesEndElement);
294
- }
295
- };
296
- }, [messagesEndRef]);
297
-
298
- return (
299
- <div className="relative flex-1 overflow-hidden bg-white dark:bg-[#343541]">
300
- <div
301
- className="max-h-full overflow-x-hidden"
302
- ref={chatContainerRef}
303
- onScroll={handleScroll}
304
- >
305
- {selectedConversation?.messages.length === 0 ? (
306
- <>
307
- <div className="mx-auto flex flex-col space-y-5 md:space-y-10 px-3 pt-5 md:pt-12 sm:max-w-[600px]">
308
-
309
-
310
- </div>
311
- </>
312
- ) : (
313
- <>
314
- <div className="sticky top-0 z-10 flex justify-center border border-b-neutral-300 bg-neutral-100 py-2 text-sm text-neutral-500 dark:border-none dark:bg-[#444654] dark:text-neutral-200">
315
- <button
316
- className="ml-2 cursor-pointer hover:opacity-50"
317
- onClick={handleSettings}
318
- >
319
- <IconSettings size={18} />
320
- </button>
321
- <button
322
- className="ml-2 cursor-pointer hover:opacity-50"
323
- onClick={onClearAll}
324
- >
325
- <IconClearAll size={18} />
326
- </button>
327
- </div>
328
- {loading && <ChatLoader />}
329
- <div
330
- className="h-[162px] bg-white dark:bg-[#343541]"
331
- ref={messagesEndRef}
332
- />
333
- </>
334
- )}
335
- </div>
336
-
337
- <ChatInput
338
- stopConversationRef={stopConversationRef}
339
- textareaRef={textareaRef}
340
- onSend={(message: any) => {
341
- setCurrentMessage(message);
342
- handleSend(message, 0);
343
- }}
344
- onScrollDownClick={handleScrollDown}
345
- onRegenerate={() => {
346
- if (currentMessage) {
347
- handleSend(currentMessage, 2, null);
348
- }
349
- }}
350
- showScrollDownButton={showScrollDownButton}
351
- />
352
- </div>
353
- );
354
- });
355
- Chat.displayName = 'Chat';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/Chat/ChatInput.tsx DELETED
@@ -1,251 +0,0 @@
1
- import {
2
- IconPlayerStop,
3
- IconRepeat,
4
- IconSend,
5
- } from '@tabler/icons-react';
6
- import {
7
- KeyboardEvent,
8
- MutableRefObject,
9
- useCallback,
10
- useContext,
11
- useEffect,
12
- useRef,
13
- useState,
14
- } from 'react';
15
-
16
- import { Message } from '@/types/chat';
17
- import { Prompt } from '@/types/prompt';
18
-
19
- import HomeContext from '@/pages/api/home.context';
20
-
21
- interface Props {
22
- onSend: (message: Message) => void;
23
- onRegenerate: () => void;
24
- onScrollDownClick: () => void;
25
- stopConversationRef: MutableRefObject<boolean>;
26
- textareaRef: MutableRefObject<HTMLTextAreaElement | null>;
27
- showScrollDownButton: boolean;
28
- }
29
-
30
- export const ChatInput = ({
31
- onSend,
32
- onRegenerate,
33
- stopConversationRef,
34
- textareaRef,
35
- }: Props) => {
36
-
37
- const {
38
- state: { selectedConversation, messageIsStreaming, prompts },
39
-
40
- dispatch: homeDispatch,
41
- }: any = useContext(HomeContext);
42
-
43
- const [content, setContent] = useState<string>();
44
- const [isTyping, setIsTyping] = useState<boolean>(false);
45
- const [showPromptList, setShowPromptList] = useState(false);
46
- const [activePromptIndex, setActivePromptIndex] = useState(0);
47
- const [promptInputValue, setPromptInputValue] = useState('');
48
- const [variables, setVariables] = useState<string[]>([]);
49
-
50
- const promptListRef = useRef<HTMLUListElement | null>(null);
51
-
52
- const filteredPrompts = prompts.filter((prompt: { name: string; }) =>
53
- prompt.name.toLowerCase().includes(promptInputValue.toLowerCase()),
54
- );
55
-
56
- const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
57
- const value = e.target.value;
58
- const maxLength = selectedConversation?.model.maxLength;
59
-
60
- if (maxLength && value.length > maxLength) {
61
- alert(
62
- `Message limit is ${maxLength} characters. You have entered ${value.length} characters.`,
63
- );
64
- return;
65
- }
66
-
67
- setContent(value);
68
- updatePromptListVisibility(value);
69
- };
70
-
71
- const handleSend = () => {
72
- if (messageIsStreaming) {
73
- return;
74
- }
75
-
76
- if (!content) {
77
- alert('Please enter a message');
78
- return;
79
- }
80
-
81
- onSend({ role: 'user', content });
82
- setContent('');
83
-
84
- if (window.innerWidth < 640 && textareaRef && textareaRef.current) {
85
- textareaRef.current.blur();
86
- }
87
- };
88
-
89
- const handleStopConversation = () => {
90
- stopConversationRef.current = true;
91
- setTimeout(() => {
92
- stopConversationRef.current = false;
93
- }, 1000);
94
- };
95
-
96
- const isMobile = () => {
97
- const userAgent =
98
- typeof window.navigator === 'undefined' ? '' : navigator.userAgent;
99
- const mobileRegex =
100
- /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i;
101
- return mobileRegex.test(userAgent);
102
- };
103
-
104
- const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
105
- if (e.key === 'Enter' && !isTyping && !isMobile() && !e.shiftKey) {
106
- e.preventDefault();
107
- handleSend();
108
- } else if (e.key === '/' && e.metaKey) {
109
- e.preventDefault();
110
-
111
- }
112
- };
113
-
114
- const parseVariables = (content: string) => {
115
- const regex = /{{(.*?)}}/g;
116
- const foundVariables = [];
117
- let match;
118
-
119
- while ((match = regex.exec(content)) !== null) {
120
- foundVariables.push(match[1]);
121
- }
122
-
123
- return foundVariables;
124
- };
125
-
126
- const updatePromptListVisibility = useCallback((text: string) => {
127
- const match = text.match(/\/\w*$/);
128
-
129
- if (match) {
130
- setShowPromptList(true);
131
- setPromptInputValue(match[0].slice(1));
132
- } else {
133
- setShowPromptList(false);
134
- setPromptInputValue('');
135
- }
136
- }, []);
137
-
138
-
139
- const handleSubmit = (updatedVariables: string[]) => {
140
- const newContent = content?.replace(/{{(.*?)}}/g, (match, variable) => {
141
- const index = variables.indexOf(variable);
142
- return updatedVariables[index];
143
- });
144
-
145
- setContent(newContent);
146
-
147
- if (textareaRef && textareaRef.current) {
148
- textareaRef.current.focus();
149
- }
150
- };
151
-
152
- useEffect(() => {
153
- if (promptListRef.current) {
154
- promptListRef.current.scrollTop = activePromptIndex * 30;
155
- }
156
- }, [activePromptIndex]);
157
-
158
- useEffect(() => {
159
- if (textareaRef && textareaRef.current) {
160
- textareaRef.current.style.height = 'inherit';
161
- textareaRef.current.style.height = `${textareaRef.current?.scrollHeight}px`;
162
- textareaRef.current.style.overflow = `${
163
- textareaRef?.current?.scrollHeight > 400 ? 'auto' : 'hidden'
164
- }`;
165
- }
166
- }, [content]);
167
-
168
- useEffect(() => {
169
- const handleOutsideClick = (e: MouseEvent) => {
170
- if (
171
- promptListRef.current &&
172
- !promptListRef.current.contains(e.target as Node)
173
- ) {
174
- setShowPromptList(false);
175
- }
176
- };
177
-
178
- window.addEventListener('click', handleOutsideClick);
179
-
180
- return () => {
181
- window.removeEventListener('click', handleOutsideClick);
182
- };
183
- }, []);
184
-
185
- return (
186
- <div className="absolute bottom-0 left-0 w-full border-transparent bg-gradient-to-b from-transparent via-white to-white pt-6 dark:border-white/20 dark:via-[#343541] dark:to-[#343541] md:pt-2">
187
- <div className="stretch mx-2 mt-4 flex flex-row gap-3 last:mb-2 md:mx-4 md:mt-[52px] md:last:mb-6 lg:mx-auto lg:max-w-3xl">
188
- {messageIsStreaming && (
189
- <button
190
- className="absolute top-0 left-0 right-0 mx-auto mb-3 flex w-fit items-center gap-3 rounded border border-neutral-200 bg-white py-2 px-4 text-black hover:opacity-50 dark:border-neutral-600 dark:bg-[#343541] dark:text-white md:mb-0 md:mt-2"
191
- onClick={handleStopConversation}
192
- >
193
- <IconPlayerStop size={16} /> {'Stop Generating'}
194
- </button>
195
- )}
196
-
197
- {!messageIsStreaming &&
198
- selectedConversation &&
199
- selectedConversation.messages.length > 0 && (
200
- <button
201
- className="absolute top-0 left-0 right-0 mx-auto mb-3 flex w-fit items-center gap-3 rounded border border-neutral-200 bg-white py-2 px-4 text-black hover:opacity-50 dark:border-neutral-600 dark:bg-[#343541] dark:text-white md:mb-0 md:mt-2"
202
- onClick={onRegenerate}
203
- >
204
- <IconRepeat size={16} /> {'Regenerate response'}
205
- </button>
206
- )}
207
-
208
- <div className="relative mx-2 flex w-full flex-grow flex-col rounded-md border border-black/10 bg-white shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 dark:bg-[#40414F] dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] sm:mx-4">
209
-
210
- <textarea
211
- ref={textareaRef}
212
- className="m-0 w-full resize-none border-0 bg-transparent p-0 py-2 pr-8 pl-10 text-black dark:bg-transparent dark:text-white md:py-3 md:pl-10"
213
- style={{
214
- resize: 'none',
215
- bottom: `${textareaRef?.current?.scrollHeight}px`,
216
- maxHeight: '400px',
217
- overflow: `${
218
- textareaRef.current && textareaRef.current.scrollHeight > 400
219
- ? 'auto'
220
- : 'hidden'
221
- }`,
222
- }}
223
- placeholder={
224
- 'Type a message or type "/" to select a prompt...' || ''
225
- }
226
- value={content}
227
- rows={1}
228
- onCompositionStart={() => setIsTyping(true)}
229
- onCompositionEnd={() => setIsTyping(false)}
230
- onChange={handleChange}
231
- onKeyDown={handleKeyDown}
232
- />
233
-
234
- <button
235
- className="absolute right-2 top-2 rounded-sm p-1 text-neutral-800 opacity-60 hover:bg-neutral-200 hover:text-neutral-900 dark:bg-opacity-50 dark:text-neutral-100 dark:hover:text-neutral-200"
236
- onClick={handleSend}
237
- >
238
- {messageIsStreaming ? (
239
- <div className="h-4 w-4 animate-spin rounded-full border-t-2 border-neutral-800 opacity-60 dark:border-neutral-100"></div>
240
- ) : (
241
- <IconSend size={18} />
242
- )}
243
- </button>
244
- </div>
245
- </div>
246
- <div className="px-3 pt-2 pb-3 text-center text-[12px] text-black/50 dark:text-white/50 md:px-4 md:pt-3 md:pb-6">
247
-
248
- </div>
249
- </div>
250
- );
251
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/Chat/ChatLoader.tsx DELETED
@@ -1,20 +0,0 @@
1
- import { IconRobot } from '@tabler/icons-react';
2
- import { FC } from 'react';
3
-
4
- interface Props { }
5
-
6
- export const ChatLoader: FC<Props> = () => {
7
- return (
8
- <div
9
- className="group border-b border-black/10 bg-gray-50 text-gray-800 dark:border-gray-900/50 dark:bg-[#444654] dark:text-gray-100"
10
- style={{ overflowWrap: 'anywhere' }}
11
- >
12
- <div className="m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
13
- <div className="min-w-[40px] items-end">
14
- <IconRobot size={30} />
15
- </div>
16
- <span className="animate-pulse cursor-default mt-1">▍</span>
17
- </div>
18
- </div>
19
- );
20
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/ChatWindow.jsx CHANGED
@@ -54,8 +54,8 @@ function ChatWindow({
54
  <div className="window-content w-full">
55
  <div className="flex flex-col w-full">
56
  <MessageList
57
- screenName={"screenName"}
58
- assistantScreenName={"assistantScreenName"}
59
  />
60
  {/* <Separator /> */}
61
  <div className="h-4" />
 
54
  <div className="window-content w-full">
55
  <div className="flex flex-col w-full">
56
  <MessageList
57
+ screenName={"me"}
58
+ assistantScreenName={"vicuna"}
59
  />
60
  {/* <Separator /> */}
61
  <div className="h-4" />
src/components/Loader.jsx CHANGED
@@ -28,11 +28,13 @@ const Loader = () => {
28
  alignItems: "center",
29
  flexDirection: "column",
30
  gap: "10px",
 
 
31
  }}
32
  >
33
  <div className="lg:hidden">
34
  <p className="p-1">
35
- web-llm-embed
36
  </p>
37
  <p className="p-1">
38
  No data is sent to the server. Conversations are cached in local
@@ -44,6 +46,9 @@ const Loader = () => {
44
  <p className="p-1">
45
  Powered by Apache TVM and MLC Relax Runtime.
46
  </p>
 
 
 
47
  </div>
48
  <div>
49
  This will download the model and may take a few minutes. After the
 
28
  alignItems: "center",
29
  flexDirection: "column",
30
  gap: "10px",
31
+ fontSize: "24px",
32
+ textAlign: "center"
33
  }}
34
  >
35
  <div className="lg:hidden">
36
  <p className="p-1">
37
+ <b>web-llm-embed</b>
38
  </p>
39
  <p className="p-1">
40
  No data is sent to the server. Conversations are cached in local
 
46
  <p className="p-1">
47
  Powered by Apache TVM and MLC Relax Runtime.
48
  </p>
49
+ <p className="p-1">
50
+ Model is currently Vicuna 13B trained by LLMSys
51
+ </p>
52
  </div>
53
  <div>
54
  This will download the model and may take a few minutes. After the
src/components/MessageList.jsx CHANGED
@@ -1,5 +1,6 @@
1
  import useLLM from "@react-llm/headless";
2
  import { useEffect, useRef } from "react";
 
3
 
4
  function MessageList({
5
  screenName = "endlessbox5",
@@ -20,7 +21,7 @@ function MessageList({
20
  }, [conversation, messages.length]);
21
 
22
  return (
23
- <div style={{ height: "350px" }} className="w-full">
24
  <div className="p-2 leading-6 w-full min-h-full">
25
  {conversation?.messages.map((m) => (
26
  <div key={m.id} style={{ display: "flex" }}>
@@ -44,7 +45,7 @@ function MessageList({
44
  ))}
45
  <div ref={scrollRef}></div>
46
  </div>
47
- </div>
48
  );
49
  }
50
 
 
1
  import useLLM from "@react-llm/headless";
2
  import { useEffect, useRef } from "react";
3
+ import { ScrollView } from "react95";
4
 
5
  function MessageList({
6
  screenName = "endlessbox5",
 
21
  }, [conversation, messages.length]);
22
 
23
  return (
24
+ <ScrollView style={{ height: "350px" }} className="w-full">
25
  <div className="p-2 leading-6 w-full min-h-full">
26
  {conversation?.messages.map((m) => (
27
  <div key={m.id} style={{ display: "flex" }}>
 
45
  ))}
46
  <div ref={scrollRef}></div>
47
  </div>
48
+ </ScrollView>
49
  );
50
  }
51
 
src/pages/api/home.context.tsx DELETED
@@ -1,23 +0,0 @@
1
- import { Dispatch, createContext } from 'react';
2
-
3
- import { ActionType } from '@/utils/';
4
-
5
- import { Conversation } from '@/types/chat';
6
- import { KeyValuePair } from '@/types/data';
7
-
8
- import { HomeInitialState } from './home.state';
9
-
10
- export interface HomeContextProps {
11
- state: HomeInitialState;
12
- dispatch: Dispatch<ActionType<HomeInitialState>>;
13
- handleNewConversation: () => void;
14
- handleSelectConversation: (conversation: Conversation) => void;
15
- handleUpdateConversation: (
16
- conversation: Conversation,
17
- data: KeyValuePair,
18
- ) => void;
19
- }
20
-
21
- const HomeContext = createContext<HomeContextProps>(undefined!);
22
-
23
- export default HomeContext;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/pages/api/home.state.tsx DELETED
@@ -1,42 +0,0 @@
1
- import { Conversation, Message } from '@/types/chat';
2
- import { Prompt } from '@/types/prompt';
3
-
4
- export interface HomeInitialState {
5
- loading: boolean;
6
- lightMode: 'light' | 'dark';
7
- messageIsStreaming: boolean;
8
- modelError: any | null;
9
- models: any[];
10
- conversations: Conversation[];
11
- selectedConversation: Conversation | undefined;
12
- currentMessage: Message | undefined;
13
- prompts: Prompt[];
14
- temperature: number;
15
- showChatbar: boolean;
16
- showPromptbar: boolean;
17
- messageError: boolean;
18
- searchTerm: string;
19
- defaultModelId: any | undefined;
20
- serverSideApiKeyIsSet: boolean;
21
- serverSidePluginKeysSet: boolean;
22
- }
23
-
24
- export const initialState: HomeInitialState = {
25
- loading: false,
26
- lightMode: 'dark',
27
- messageIsStreaming: false,
28
- modelError: null,
29
- models: [],
30
- conversations: [],
31
- selectedConversation: undefined,
32
- currentMessage: undefined,
33
- prompts: [],
34
- temperature: 1,
35
- showPromptbar: true,
36
- showChatbar: true,
37
- messageError: false,
38
- searchTerm: '',
39
- defaultModelId: undefined,
40
- serverSideApiKeyIsSet: false,
41
- serverSidePluginKeysSet: false,
42
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/pages/api/home.tsx DELETED
@@ -1,157 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
-
3
- import { GetServerSideProps } from 'next';
4
- import Head from 'next/head';
5
- import {
6
- DEFAULT_SYSTEM_PROMPT,
7
- DEFAULT_TEMPERATURE,
8
- saveConversation,
9
- saveConversations,
10
- updateConversation,
11
- useCreateReducer
12
- } from '@/utils';
13
-
14
- import { Chat } from '@/components/Chat/Chat';
15
-
16
- import HomeContext from './home.context';
17
- import { HomeInitialState, initialState } from './home.state';
18
-
19
- import { v4 as uuidv4 } from 'uuid';
20
-
21
- interface Props {
22
- serverSideApiKeyIsSet: boolean;
23
- serverSidePluginKeysSet: boolean;
24
- defaultModelId: any;
25
- }
26
-
27
- const Home = ({
28
- defaultModelId,
29
- }: Props) => {
30
- const contextValue = useCreateReducer<HomeInitialState>({
31
- initialState,
32
- });
33
-
34
- const {
35
- state: {
36
- lightMode,
37
- conversations,
38
- selectedConversation
39
- },
40
- dispatch,
41
- } = contextValue;
42
-
43
- const stopConversationRef = useRef<boolean>(false);
44
-
45
- // FETCH MODELS ----------------------------------------------
46
-
47
- const handleSelectConversation = (conversation: any) => {
48
- dispatch({
49
- field: 'selectedConversation',
50
- value: conversation,
51
- });
52
-
53
- saveConversation(conversation);
54
- };
55
-
56
- // CONVERSATION OPERATIONS --------------------------------------------
57
-
58
- const handleNewConversation = () => {
59
- const lastConversation = conversations[conversations.length - 1];
60
-
61
- const newConversation: any = {
62
- id: uuidv4(),
63
- name: 'New Conversation',
64
- messages: [],
65
- model: lastConversation?.model || {
66
- id: "OpenAIModels[defaultModelId].id",
67
- name: "OpenAIModels[defaultModelId].name",
68
- maxLength: "OpenAIModels[defaultModelId].maxLength",
69
- tokenLimit: "OpenAIModels[defaultModelId].tokenLimit",
70
- },
71
- prompt: DEFAULT_SYSTEM_PROMPT,
72
- temperature: lastConversation?.temperature ?? DEFAULT_TEMPERATURE,
73
- folderId: null,
74
- };
75
-
76
- const updatedConversations = [...conversations, newConversation];
77
-
78
- dispatch({ field: 'selectedConversation', value: newConversation });
79
- dispatch({ field: 'conversations', value: updatedConversations });
80
-
81
- saveConversation(newConversation);
82
- saveConversations(updatedConversations);
83
-
84
- dispatch({ field: 'loading', value: false });
85
- };
86
-
87
- const handleUpdateConversation = (
88
- conversation: any,
89
- data: any,
90
- ) => {
91
- const updatedConversation = {
92
- ...conversation,
93
- [data.key]: data.value,
94
- };
95
-
96
- const { single, all } = updateConversation(
97
- updatedConversation,
98
- conversations,
99
- );
100
-
101
- dispatch({ field: 'selectedConversation', value: single });
102
- dispatch({ field: 'conversations', value: all });
103
- };
104
-
105
- // EFFECTS --------------------------------------------
106
-
107
- useEffect(() => {
108
- if (window.innerWidth < 640) {
109
- dispatch({ field: 'showChatbar', value: false });
110
- }
111
- }, [selectedConversation]);
112
-
113
- // ON LOAD --------------------------------------------
114
-
115
- return (
116
- <HomeContext.Provider
117
- value={{
118
- ...contextValue,
119
- handleNewConversation,
120
- handleSelectConversation,
121
- handleUpdateConversation,
122
- }}
123
- >
124
- <Head>
125
- <title>Web LLM Embed</title>
126
- <meta name="description" content="Web LLM Embed" />
127
- <meta
128
- name="viewport"
129
- content="height=device-height ,width=device-width, initial-scale=1, user-scalable=no"
130
- />
131
- <link rel="icon" href="/favicon.ico" />
132
- </Head>
133
- <main
134
- className={`flex h-screen w-screen flex-col text-sm text-white dark:text-white ${lightMode}`}
135
- >
136
- <div className="flex h-full w-full pt-[48px] sm:pt-0">
137
- <div className="flex flex-1">
138
- <Chat stopConversationRef={stopConversationRef} />
139
- </div>
140
- </div>
141
- </main>
142
- </HomeContext.Provider>
143
- );
144
- };
145
- export default Home;
146
-
147
- export const getServerSideProps: GetServerSideProps = async ({ locale }) => {
148
- const defaultModelId = "fallbackModelID"
149
-
150
- return {
151
- props: {
152
- defaultModelId
153
- },
154
- };
155
- };
156
-
157
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/file-handler.ts CHANGED
@@ -1,5 +1,5 @@
 
1
  import fs from 'fs-extra';
2
- import type { OpenAIEmbeddings } from 'langchain/embeddings/openai';
3
  import {
4
  HNSWLib,
5
  type HNSWLib as StoreTypeHNSWLib,
@@ -26,7 +26,7 @@ const HNSWLibModelFilesName = {
26
  // looking forward to a better way to transfrom hnswlibStore <=> indexes
27
  export async function HNSWLibModelToVectorStore(
28
  model: HNSWLibModel,
29
- embeddings: OpenAIEmbeddings,
30
  ) {
31
  await saveHNSWLibModelToLocal(model);
32
  // load from dir
 
1
+ import { XenovaTransformersEmbeddings } from '@/embed/hf';
2
  import fs from 'fs-extra';
 
3
  import {
4
  HNSWLib,
5
  type HNSWLib as StoreTypeHNSWLib,
 
26
  // looking forward to a better way to transfrom hnswlibStore <=> indexes
27
  export async function HNSWLibModelToVectorStore(
28
  model: HNSWLibModel,
29
+ embeddings: XenovaTransformersEmbeddings,
30
  ) {
31
  await saveHNSWLibModelToLocal(model);
32
  // load from dir
src/utils/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { Conversation } from '@/types/chat';
2
  import { useMemo, useReducer } from 'react';
3
 
4
  // Extracts property names from initial state of reducer to allow typesafe dispatch objects
@@ -30,35 +29,6 @@ export const useCreateReducer = <T>({ initialState }: { initialState: T }) => {
30
  return useMemo(() => ({ state, dispatch }), [state, dispatch]);
31
  };
32
 
33
- export const updateConversation = (
34
- updatedConversation: Conversation,
35
- allConversations: Conversation[],
36
- ) => {
37
- const updatedConversations = allConversations.map((c) => {
38
- if (c.id === updatedConversation.id) {
39
- return updatedConversation;
40
- }
41
-
42
- return c;
43
- });
44
-
45
- saveConversation(updatedConversation);
46
- saveConversations(updatedConversations);
47
-
48
- return {
49
- single: updatedConversation,
50
- all: updatedConversations,
51
- };
52
- };
53
-
54
- export const saveConversation = (conversation: Conversation) => {
55
- localStorage.setItem('selectedConversation', JSON.stringify(conversation));
56
- };
57
-
58
- export const saveConversations = (conversations: Conversation[]) => {
59
- localStorage.setItem('conversationHistory', JSON.stringify(conversations));
60
- };
61
-
62
 
63
  export function throttle<T extends (...args: any[]) => any>(
64
  func: T,
 
 
1
  import { useMemo, useReducer } from 'react';
2
 
3
  // Extracts property names from initial state of reducer to allow typesafe dispatch objects
 
29
  return useMemo(() => ({ state, dispatch }), [state, dispatch]);
30
  };
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  export function throttle<T extends (...args: any[]) => any>(
34
  func: T,