Compare commits
1045 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7175f4a57 | |||
| 2b91de3e46 | |||
| 7ec19b728a | |||
| ccc6266a96 | |||
| 1c55b71b3a | |||
| 59e9d50e01 | |||
| aa341de195 | |||
| 587a824d63 | |||
| bf5d630bad | |||
| e92071736c | |||
| 25cd9dd0af | |||
| b2dd482e43 | |||
| fd97ce5221 | |||
| 0c6ac8b72e | |||
| 9c09d27d8e | |||
| 36a990ca46 | |||
| ba637a5e9c | |||
| 5846949020 | |||
| 5c50411472 | |||
| a28e4adf75 | |||
| a5dbac505a | |||
| 87f3dfd3f0 | |||
| 78ee89b7dc | |||
| 70c762733a | |||
| a6a7d4d596 | |||
| ff570aae33 | |||
| 33a6e72469 | |||
| 2cb08dcfb9 | |||
| 0b564d5ef9 | |||
| cea248f085 | |||
| 9c74f1d8e2 | |||
| 08e20e0efc | |||
| bab9336322 | |||
| 2ece915bae | |||
| 8c1e7dc70b | |||
| 03d7bf8d78 | |||
| d70bfaa4fd | |||
| ec8a83b3e6 | |||
| c9b639d73d | |||
| 00d4bcf56c | |||
| 39fba53c78 | |||
| 2d86a46d84 | |||
| 55bf265823 | |||
| 78c0ba9050 | |||
| 8cc0e9e635 | |||
| e09551e838 | |||
| d7ad65b364 | |||
| 28054ac6b1 | |||
| f1b3df286a | |||
| 74f38eab1a | |||
| 901770b843 | |||
| 5658f62e59 | |||
| d8e3fa7c9b | |||
| 9bb34ee41c | |||
| 7b8db43c4c | |||
| 5585dda2eb | |||
| 04d6bc6bb2 | |||
| 219ec7371d | |||
| d340d4bbfb | |||
| 1f8cf750b9 | |||
| 0c69af84d1 | |||
| 9305ad4d3e | |||
| 65498431c4 | |||
| 99b84a6800 | |||
| 09855570c8 | |||
| bb22c59737 | |||
| 242f8fbaeb | |||
| 4f54c05cda | |||
| 6a40d3e2d8 | |||
| c02b0f1f7a | |||
| 5a1a83d7b5 | |||
| 58a12f56d3 | |||
| 3292cfe7a2 | |||
| c064e3f3e2 | |||
| 7571b9477d | |||
| 6df245b0ae | |||
| d691d15b2a | |||
| 0613cf445d | |||
| 7dac629402 | |||
| 6dd6ebe5a1 | |||
| f56201190e | |||
| 0c1481b118 | |||
| 6965419971 | |||
| ec104d9ffc | |||
| 5a4be0fda7 | |||
| 4dc0cc8080 | |||
| 491223963d | |||
| ae00f70033 | |||
| 073a38bb1b | |||
| 55e060249c | |||
| 0cc511d4dd | |||
| 904141e2ab | |||
| 20f3d54e38 | |||
| 65ff1af676 | |||
| 375e9d32a3 | |||
| bedba2aba3 | |||
| 50fd6f2908 | |||
| d12ee78021 | |||
| f90b78020c | |||
| 23d8db139a | |||
| bdccf7424d | |||
| 75e392d554 | |||
| e337252f48 | |||
| 97eb123faf | |||
| d3c92a6aea | |||
| 4305d96bac | |||
| 3829cc8a6e | |||
| 6c0cfc45d9 | |||
| e3e3db29be | |||
| 62db21848c | |||
| e5ada3fbf9 | |||
| cc6005a3a6 | |||
| b6f10eb60c | |||
| 246118cef5 | |||
| 9dc3e0035c | |||
| c65b475c22 | |||
| 2af8efc6e5 | |||
| 4e56f3f5ca | |||
| 023581f883 | |||
| 75fc86c0cf | |||
| fbcd07336c | |||
| bb7e939de2 | |||
| 65aad70ae2 | |||
| 38c60b9107 | |||
| ec78c5dc5b | |||
| 1d0883ca44 | |||
| e500dc6765 | |||
| 7f49886562 | |||
| ed15ae3cbb | |||
| ee57ee1250 | |||
| d383b6a3f5 | |||
| a7f1eafa96 | |||
| 8a2cad6fc1 | |||
| b55c47cd7f | |||
| e67a50a48a | |||
| ed94488250 | |||
| aa150e4779 | |||
| f722c0faa5 | |||
| 8040e5c94f | |||
| 6dd0ab24fa | |||
| 4f740b7bfb | |||
| 7f465830e2 | |||
| d0730c2481 | |||
| a3ef526889 | |||
| 674615608e | |||
| f623d7d235 | |||
| 5031b9eed8 | |||
| 98918cca7a | |||
| be5ec46c4e | |||
| 94e5b8e8eb | |||
| dc0927c7ff | |||
| 5609455af1 | |||
| 6fe9601c03 | |||
| ef595cf162 | |||
| db80565a14 | |||
| ea2a12b76d | |||
| 36750ab7c2 | |||
| 490e56bba2 | |||
| aa6988e622 | |||
| a9ba958900 | |||
| 2cbca313d5 | |||
| ff759a7680 | |||
| 34882ca62d | |||
| 8530769278 | |||
| 8047fff06a | |||
| a3afdef81c | |||
| 157634f6f4 | |||
| 8f0258f1de | |||
| 292472079a | |||
| 4ea4aab5c5 | |||
| c210b5c981 | |||
| cc25484d37 | |||
| 0790dbb4ae | |||
| 307f2d3537 | |||
| 9987c96523 | |||
| 7c386a7017 | |||
| f7f1354f05 | |||
| fd30525886 | |||
| 23463c54cd | |||
| 4f5310b8f1 | |||
| 3a705869a6 | |||
| 917e966e38 | |||
| 480e861e10 | |||
| ee377a5182 | |||
| 15fb86d595 | |||
| 6e3d481aee | |||
| ee82a450ec | |||
| d584451bd2 | |||
| ce90f75f19 | |||
| cd9bcd3d6d | |||
| b10634fd2f | |||
| 353e2e979d | |||
| cd45c32b6d | |||
| 52357ddc17 | |||
| 0f8738e69a | |||
| 77159b91ff | |||
| 8c8fc9eae3 | |||
| 18108ccc1e | |||
| b2b06b5ee4 | |||
| 0c1b67184c | |||
| 10f0e3ab54 | |||
| 801427f767 | |||
| b1d14f3538 | |||
| b1a95221f8 | |||
| 74b93699a5 | |||
| ef1bb7105d | |||
| c5a79be93e | |||
| 2ff398433e | |||
| 29093437c5 | |||
| 08a99f2b4a | |||
| 5d1f66359f | |||
| c81099b240 | |||
| b1062d1f3e | |||
| 03e8eb091d | |||
| 7922a4828e | |||
| 6fb4ca1ada | |||
| 1a3baa7e0f | |||
| a1c4322fb1 | |||
| 946984fb52 | |||
| c362d89195 | |||
| ccd6694b25 | |||
| 99615b8ccb | |||
| dceee25212 | |||
| c9cbc09c3c | |||
| 6adfa81578 | |||
| 2fd571687a | |||
| 596adc3bc3 | |||
| 46ae45566f | |||
| 10f525bb82 | |||
| 3e4be85d91 | |||
| 4dc2d222be | |||
| 0560e3b79c | |||
| 2fa71c8f85 | |||
| 60abe86502 | |||
| cf60e673fd | |||
| f3f94e39bf | |||
| 61ccde4c57 | |||
| fae5807ca8 | |||
| e66b105380 | |||
| cc43b6dbdc | |||
| 592978c7d9 | |||
| 72a2a07ead | |||
| 932f53c137 | |||
| fee1e4cb22 | |||
| f336c34707 | |||
| 45bf7cbace | |||
| 6c0039be27 | |||
| 3e671efc72 | |||
| 852e848aed | |||
| 8c41befaed | |||
| bbc32b79df | |||
| 2165f32e92 | |||
| d20269bf40 | |||
| 439b596a97 | |||
| 395c9b2b56 | |||
| eb69a1147a | |||
| cf77a431b6 | |||
| a42d147644 | |||
| a9f8e1d45f | |||
| bd4ee16261 | |||
| 1e7cd0f4be | |||
| 8a9ce2ff0f | |||
| a8a8567ed3 | |||
| 574fb329ff | |||
| b947eecfe2 | |||
| 2ce5b197b0 | |||
| dd214b6b09 | |||
| 1cc2f77bea | |||
| 0b10f5f265 | |||
| 620381a066 | |||
| aa8bc63b1e | |||
| 8509832acb | |||
| 1104407b1f | |||
| f91ce8f37b | |||
| cf35e17741 | |||
| 45e1ea4643 | |||
| 07718d06e8 | |||
| 37a9499a45 | |||
| 18a44e0bd1 | |||
| df12892951 | |||
| fbc7851d2a | |||
| 9487eb363e | |||
| 7e261de8eb | |||
| 272851d875 | |||
| 3c9dccefb8 | |||
| c60269d077 | |||
| 696c287a33 | |||
| 50c87f37a2 | |||
| 2d9c6648c2 | |||
| 145bf6cdd8 | |||
| 05ab034302 | |||
| 3ab746e449 | |||
| 89279de4a5 | |||
| abfac8eeb8 | |||
| ebfe84daf1 | |||
| ab9b770800 | |||
| ae6b82ecbe | |||
| 9731990b81 | |||
| dfed0db892 | |||
| cab7d81f2a | |||
| d7ee8db704 | |||
| c8035b27b6 | |||
| e8c62fb829 | |||
| 5daf5cca9f | |||
| 7661a55bf2 | |||
| df6d4b2aa0 | |||
| 042e2c5106 | |||
| f74a0fda5d | |||
| b2e3592170 | |||
| 1a03328910 | |||
| bb6a382f09 | |||
| 877bea1477 | |||
| ff1a8764f0 | |||
| fc8025dd6f | |||
| 6f5f2e1d9a | |||
| 5bc47c39f0 | |||
| ea121104df | |||
| b04cde4369 | |||
| ca2a0bd7a1 | |||
| 17d10b449c | |||
| c87615c91a | |||
| 375cb14d0c | |||
| 93e921c13f | |||
| b0f1d81d22 | |||
| 87c6676dee | |||
| ed1bb65325 | |||
| 9937eea1a1 | |||
| 7b90fc6223 | |||
| dce2c5229e | |||
| dfb23deb86 | |||
| 82fd7a172f | |||
| b71f1632d9 | |||
| 3cca459376 | |||
| 07f76e48c3 | |||
| 96192e0578 | |||
| d1a945fe3a | |||
| 7b75e779f4 | |||
| 6eac081669 | |||
| 7882fc07fb | |||
| 0545414693 | |||
| e18ba06db7 | |||
| 194d50548a | |||
| 7b32beb56d | |||
| fbb91fb545 | |||
| 35af397a80 | |||
| f64c6422bd | |||
| c1ce44b6a3 | |||
| a71d22b8d3 | |||
| efa21bd9f4 | |||
| d521318e94 | |||
| 8fe532a501 | |||
| e0b5ea78ae | |||
| 991fdbddcc | |||
| 89a9928e29 | |||
| 107c786786 | |||
| d76c57710c | |||
| 0e796d2d1d | |||
| 32722501d5 | |||
| c1b9d99690 | |||
| a5cbbbd7b6 | |||
| ae26b976bf | |||
| e9ba3321df | |||
| 13a626c2f3 | |||
| df7904232a | |||
| 4e8a98bf52 | |||
| 05f3640b4f | |||
| 992e00776e | |||
| a3cff28de8 | |||
| e41f94dc8b | |||
| 19edd70440 | |||
| ba1f2c7321 | |||
| 69224361c3 | |||
| d5bef789a1 | |||
| e38bfc0311 | |||
| d4a698500d | |||
| b8dbb596d0 | |||
| eecdf87618 | |||
| 4899a9530a | |||
| 50ac5c307b | |||
| a41bf61497 | |||
| 201122a12f | |||
| 6915f371d1 | |||
| 142adc175e | |||
| 2b593793e2 | |||
| e1aab445ef | |||
| 2b28df0e52 | |||
| 0468903cef | |||
| 21d640f9b5 | |||
| 50268e8f04 | |||
| d731e28e06 | |||
| 6493fa5ea5 | |||
| a916b6c20a | |||
| 42414fbcdb | |||
| a76e9e14c8 | |||
| 99eb5e59a0 | |||
| d03a09c9f8 | |||
| 2eb0e0a274 | |||
| 841e9c1655 | |||
| bba5e38061 | |||
| ce7dac75bd | |||
| b7272543e0 | |||
| 8999e7dc0b | |||
| 254b56f7a9 | |||
| b58c26d553 | |||
| fee3be8837 | |||
| 25d148c33d | |||
| 114c286e72 | |||
| 8fe3e57680 | |||
| cf7e223c15 | |||
| 3e3eae7bc4 | |||
| 5d2603cbd7 | |||
| f8673394ae | |||
| 41501cdea9 | |||
| 8d3ec0cf8e | |||
| fa6e64f115 | |||
| 596017b3b9 | |||
| 8d6dfc2906 | |||
| 6e1e514788 | |||
| 8235830261 | |||
| 91547ebbd8 | |||
| 9ec4e6acff | |||
| e3bc3736ca | |||
| 14e26b1cd1 | |||
| 52a63dbf7d | |||
| 480be6aec3 | |||
| bedfd40cec | |||
| 27431991e3 | |||
| b2c1ee77e5 | |||
| 1853bc47e3 | |||
| 6957bdae93 | |||
| 9fc1d15fb4 | |||
| 98ae198dde | |||
| 50f0d28c5b | |||
| 38a3c5df15 | |||
| 16a226c103 | |||
| 2d11750089 | |||
| a5dc01bcab | |||
| fcd1422b4f | |||
| bf68660922 | |||
| 07ad988944 | |||
| e2d5431b04 | |||
| 7ad6d60b0c | |||
| 40138e2bda | |||
| bc9c96f033 | |||
| cd7d65b075 | |||
| 1942d8f835 | |||
| 1798d691a8 | |||
| 5125d908ec | |||
| 8c23671e0c | |||
| 3037c2c93a | |||
| fe30a03f6b | |||
| 728bb2fd02 | |||
| 39ea8eae13 | |||
| ccfdff6a9a | |||
| bb1cee5b59 | |||
| 5c5c28414b | |||
| 525ea6d7b4 | |||
| 923a5f91c1 | |||
| 17e35c717d | |||
| d7bccb0985 | |||
| c9df0dbb58 | |||
| 704ecf44b0 | |||
| b2f728b4d9 | |||
| 13a29160d3 | |||
| 66c82ad734 | |||
| ca247033a8 | |||
| fa3dfccb83 | |||
| bd40ccc5f1 | |||
| b2d5dc7a59 | |||
| 64028ed8e1 | |||
| 54f1886871 | |||
| 431ee245ec | |||
| d551aa274d | |||
| e306859c86 | |||
| 77fb3d7a2c | |||
| 4164dfed41 | |||
| c17bb3fcb9 | |||
| 1d058cb9c8 | |||
| af6fd656fc | |||
| 88fdbe9b06 | |||
| efc9f3b477 | |||
| 0d272b5bcc | |||
| cfa79e0eb3 | |||
| 2ab5e10f6a | |||
| cb8911c06b | |||
| 6f69743df4 | |||
| d500ab6fb7 | |||
| 6ab0adc9ef | |||
| 0a152afe9c | |||
| ae587ba327 | |||
| 3f1b3da750 | |||
| 404d7fbec7 | |||
| aff4d619da | |||
| ada9c25b3e | |||
| 3bdabaf943 | |||
| 4c23382c11 | |||
| c3b389ce5b | |||
| d32b43787a | |||
| 077bcd2f5b | |||
| 85afab3594 | |||
| 6d6551505b | |||
| 08b7e040fd | |||
| 6f44bb40f0 | |||
| 261a7f2162 | |||
| 7e573c2b7d | |||
| a03a59b4e5 | |||
| cd782b2095 | |||
| ce7ae583e7 | |||
| a05045c406 | |||
| 66dd2fe2c0 | |||
| 8ecc049827 | |||
| 5c30ca894c | |||
| 7ed184adc0 | |||
| 8c5d9d07f2 | |||
| 09fab81b33 | |||
| 018d6a4e91 | |||
| d831833d49 | |||
| fbd74ddc62 | |||
| 38caaa9cfc | |||
| c04cf5a5fc | |||
| 32b60f1298 | |||
| 4ddb12246d | |||
| 50b21532fc | |||
| 35bd421b84 | |||
| 3045985952 | |||
| ebe4375834 | |||
| 6ab6cfd9ba | |||
| b0fdd38532 | |||
| 8b5b810f88 | |||
| a42f78cf9d | |||
| 1249f6a92f | |||
| b9508abfef | |||
| 80e636df5c | |||
| 4b477ccd76 | |||
| 9d0e6ee096 | |||
| 1a1acccfdb | |||
| 0273099ea6 | |||
| 183ba56c32 | |||
| c85b849206 | |||
| 7d798b13f9 | |||
| 26728b283a | |||
| 232538fffb | |||
| ec3d8ec293 | |||
| 6ca96e3deb | |||
| 9d1d66a07d | |||
| 208229b153 | |||
| 7b5860410b | |||
| 2454a0676f | |||
| d488109ce7 | |||
| 6c7e8d8af4 | |||
| b4f4fffa5e | |||
| 118ed10817 | |||
| 3aa45fcc64 | |||
| 8063cc6074 | |||
| 0013ffc888 | |||
| f075d7c922 | |||
| 884d027610 | |||
| dc26074881 | |||
| 612d773fe8 | |||
| 8715e71903 | |||
| 17ec4ed348 | |||
| 2ad3fe7d91 | |||
| dacbd5aab9 | |||
| a9e22de8ca | |||
| 75981287ca | |||
| f513713959 | |||
| 1fed246ca9 | |||
| 4e0d0cf91d | |||
| 1aea646c6c | |||
| 58fe7691ec | |||
| fcb522a69a | |||
| c263c7a7a8 | |||
| e8209d60c9 | |||
| 6f1d037c9a | |||
| bc832bc723 | |||
| b0329695f3 | |||
| d55977d60b | |||
| 66f60c0145 | |||
| e7e5bcb25e | |||
| 519d226c92 | |||
| 892919b8c9 | |||
| 24e5d27999 | |||
| f55e856720 | |||
| 25ccbe0044 | |||
| 3b04e1f767 | |||
| 91487506f9 | |||
| 24f92630d8 | |||
| 5caa936b5d | |||
| 4ac2377fcc | |||
| c85d192a0f | |||
| 5619481b8c | |||
| 2ea42fcb87 | |||
| 334bebb0ef | |||
| 7aed4a53f2 | |||
| d482cb4d15 | |||
| 35a3f37173 | |||
| c15795187c | |||
| 8bf89df4ee | |||
| 28d3153d24 | |||
| 3b06996fbd | |||
| bda9da79c0 | |||
| 302341e1c7 | |||
| 2e0e01e9d9 | |||
| 738f44aee7 | |||
| 0978bc5811 | |||
| 6417397761 | |||
| c0622df993 | |||
| c938ff60b3 | |||
| bea26f6ea6 | |||
| 16fa7f860b | |||
| b028e68204 | |||
| 17db772f59 | |||
| 0d18bb6f26 | |||
| cc892519c8 | |||
| 71d24d4142 | |||
| 71e9de654a | |||
| 970cbfbb88 | |||
| ae788f7cf1 | |||
| 1959d185d8 | |||
| ec0cd9d330 | |||
| 47306c3370 | |||
| a3d5c21afc | |||
| 199b172cd7 | |||
| 11f22715f4 | |||
| c976499ff3 | |||
| d7f18b2bcb | |||
| dfc321b1a3 | |||
| f87c69b30c | |||
| 27bb49e373 | |||
| b6a94e7a26 | |||
| cb67717712 | |||
| 53c8e6a175 | |||
| b1f9627c12 | |||
| 1ef2693872 | |||
| 1a11bf755c | |||
| 9cb68d5c13 | |||
| 025234dbe8 | |||
| 067dac6f98 | |||
| c5c1a4d24a | |||
| bde86630d5 | |||
| ea8ba0f1fd | |||
| 529620b83a | |||
| fe46e8c733 | |||
| 2accdc3d16 | |||
| 0eeb9bb141 | |||
| f88e26b452 | |||
| 6d230498c4 | |||
| eeceae5d36 | |||
| 2b2e32be95 | |||
| cae634dfd4 | |||
| 185be39172 | |||
| 67bc7ddb32 | |||
| ffb1aa6bdc | |||
| ae0e5b0643 | |||
| fe28a80908 | |||
| e0e3e59960 | |||
| fbe47e98bd | |||
| aa5d76333c | |||
| 8bca0595b4 | |||
| 9aa9e0ba49 | |||
| c9828b7524 | |||
| d8902f9cb6 | |||
| 2de7e3cad3 | |||
| a64fed2444 | |||
| d679ae8c3c | |||
| 3fd78c0d4c | |||
| e72f2bda8c | |||
| c24be71585 | |||
| 457422687d | |||
| f6fa166d87 | |||
| ee732ceca8 | |||
| 719664d1e5 | |||
| effbdc1976 | |||
| be0da3d1e2 | |||
| 534853d67a | |||
| 2e2e766d77 | |||
| d49912e9a0 | |||
| dc9e9df907 | |||
| 25af621e09 | |||
| 2c809331b4 | |||
| 505ba15806 | |||
| d417384204 | |||
| 83a561c7b9 | |||
| 543513b982 | |||
| f7bb8a1100 | |||
| c0d9c3d4c7 | |||
| 556650d471 | |||
| c695aadfbe | |||
| ff83e272c0 | |||
| cc4e90f397 | |||
| 5462151e3f | |||
| 470889a797 | |||
| d77c975800 | |||
| 3b5d0432ae | |||
| 7b81fd05da | |||
| 897acf0f2c | |||
| b4df4cf747 | |||
| ee9835248f | |||
| e4d9ea737a | |||
| 840ee56423 | |||
| ff29aa4ddd | |||
| 7176630ef7 | |||
| b66027d22f | |||
| 7271e9673a | |||
| 2486784250 | |||
| 782085059b | |||
| 7e9800be47 | |||
| c9cd93b264 | |||
| 7a69714efb | |||
| 29f44f3aed | |||
| 0a10810cf2 | |||
| b891a22d2d | |||
| 82f495acb1 | |||
| bb05adb483 | |||
| edb9b367c1 | |||
| 7ecb8589fb | |||
| feebb22094 | |||
| 98be8a0022 | |||
| e135a5fcce | |||
| d23925c7d5 | |||
| f8289fa809 | |||
| fd5e952695 | |||
| 626c77d340 | |||
| ac3e61d7b2 | |||
| db33126664 | |||
| 822e20f80b | |||
| c21051b9a7 | |||
| dcda888de6 | |||
| 0c775ba2e5 | |||
| 6867e9c6db | |||
| 1334c2013c | |||
| 94885de71b | |||
| b86775846b | |||
| 4019bd877b | |||
| 7e483b9123 | |||
| ee615e6ade | |||
| b092947e2e | |||
| 1d96655b01 | |||
| 8ed7c78074 | |||
| be3df774dc | |||
| 2c89041b3f | |||
| e4277bfcf4 | |||
| 2e351d4218 | |||
| e24dee3b07 | |||
| 8eeff3a01c | |||
| a05f34422e | |||
| 74241b43fe | |||
| fae15c9f4d | |||
| 2c1b35144d | |||
| c25c52532f | |||
| 805c65b2b2 | |||
| 0bb61e5301 | |||
| d2038061e7 | |||
| 7a42f4df93 | |||
| ed9961c822 | |||
| fb91551364 | |||
| 8dc725a697 | |||
| 6c3abf30b0 | |||
| 1314217e00 | |||
| 871617649a | |||
| 85c3a40457 | |||
| 0661e9e04f | |||
| 6045eb5105 | |||
| 72954a3557 | |||
| cd65b84b7c | |||
| c0e9fdcd8b | |||
| 780fd97006 | |||
| e985d65165 | |||
| 223cfdc0c9 | |||
| 38a700feb0 | |||
| faee688924 | |||
| a9e5a2978f | |||
| ab0e1493a0 | |||
| 76b197baeb | |||
| aa63a6dabc | |||
| 1b4a1f15a9 | |||
| 3734a16b6a | |||
| 8e28466cae | |||
| a8ece30ced | |||
| a8b3f7c9bd | |||
| 963660db19 | |||
| 5b176c90f5 | |||
| 200b425e0b | |||
| 8d3bffb64d | |||
| 32dc3ced0b | |||
| c9afb25ed3 | |||
| de3ceb76be | |||
| 72d55870fe | |||
| 858b5090b4 | |||
| ebe156e2d8 | |||
| 3fc01d1906 | |||
| 582b2aa572 | |||
| 04c3e88224 | |||
| 52128039cb | |||
| 1b66cc6218 | |||
| 60e8b532de | |||
| d0b5b63840 | |||
| c2849758e1 | |||
| 7699739712 | |||
| 7b8b702f92 | |||
| 53e8cd2182 | |||
| e0c10d4769 | |||
| 65681521ab | |||
| 22f892760f | |||
| 4ce470d3e7 | |||
| 7c9487b62b | |||
| 6df34d48ee | |||
| d3f3c4e3a7 | |||
| 11e8ad43ae | |||
| 88457e4b7a | |||
| 181f68109e | |||
| 00f1dcef38 | |||
| 4cb374e64d | |||
| fd76681f81 | |||
| eb72065e5e | |||
| e9f1aadc67 | |||
| f1c6db6053 | |||
| 40fd5fac88 | |||
| 97e0e04987 | |||
| e250519c1b | |||
| 2e866cd2d7 | |||
| 3bfd8ad893 | |||
| a3f6cb1aba | |||
| 0804dc7be6 | |||
| e328bd7f4c | |||
| e435e526b4 | |||
| 747ec51143 | |||
| 96dabd9e6e | |||
| 8eefe53473 | |||
| 0ce1f62c0d | |||
| 4bad34278b | |||
| 1516b18a11 | |||
| fb5c258cf6 | |||
| 34170a38ac | |||
| a846eb9131 | |||
| 32db721f0e | |||
| c18458ac41 | |||
| 2fac3881eb | |||
| 37fe307035 | |||
| c1349c930a | |||
| c54ad18cc3 | |||
| be1a4d2ad3 | |||
| 9e43a78ecb | |||
| 22529a4348 | |||
| 91fb441dcd | |||
| 9c8db3364b | |||
| 8a80f4fce6 | |||
| a762b3cbfe | |||
| b238170e7c | |||
| 6b24e4280c | |||
| beb208973f | |||
| 528257664a | |||
| f278ca16b8 | |||
| 0d370a8c7e | |||
| 7299f5fc4d | |||
| 8acdbec6e3 | |||
| e2bdbe9e8a | |||
| c440a51f51 | |||
| c172f2d6af | |||
| 0ff26739e7 | |||
| 9b13520b9e | |||
| b02d510490 | |||
| 5855990bd5 | |||
| 596b7552dd | |||
| f051c1d2cd | |||
| ef368f89fb | |||
| 72390e2fe6 | |||
| 580289e05d | |||
| 39dae08b6b | |||
| e16782e2db | |||
| ed42f9b752 | |||
| b25b82b9dc | |||
| fd36eafb91 | |||
| a2e6466bb9 | |||
| f776913ca3 | |||
| 9a065175ca | |||
| a7cab344f8 | |||
| e455a9d96b | |||
| 85af31b614 | |||
| 0e959412d5 | |||
| 6f24f50497 | |||
| 74e2ffeb96 | |||
| bbb533f49f | |||
| bdff917e04 | |||
| 52cf4afbec | |||
| 0ef1ec9247 | |||
| aa862a6b5f | |||
| f10f310aef | |||
| 18b47f168a | |||
| f7ad05275b | |||
| d62e0cd56c | |||
| 1caa73a13a | |||
| 844af50008 | |||
| b26d924890 | |||
| da1b469f8d | |||
| 0189c74a0e | |||
| a0a4f9d7b0 | |||
| eeb1b07898 | |||
| 78e5f79bce | |||
| 764e815204 | |||
| 03d25f2a48 | |||
| aa36c8cb2a | |||
| 2146637022 | |||
| 0a4e0ed90d | |||
| ceeb5bd70e | |||
| f77343918d | |||
| e2984ea87e | |||
| 694d398d7b | |||
| a2bca433f6 | |||
| 60698704ca | |||
| 7d4e64d575 | |||
| 2c03efc167 | |||
| 539281110a | |||
| 3ed88544f8 | |||
| 9ee73ea5f9 | |||
| c934d0e30d | |||
| d9f0144e62 | |||
| 57923f83ee | |||
| a38eee71f0 | |||
| c8e03ec401 | |||
| 4ca32b5070 | |||
| d8660251af | |||
| 26af271edc | |||
| da49a4dd82 | |||
| cf02f0f91d | |||
| 2de95c4ef9 | |||
| 2434d36564 | |||
| 73d919868b | |||
| 39cbd7114e | |||
| a16ab4abfa | |||
| 6e12d2638d | |||
| 0d3c42beb2 | |||
| 66f65b25b4 | |||
| 3f85a93a03 | |||
| e9e9155366 | |||
| 30c9d3bce5 | |||
| dba5d90b94 | |||
| 0705659f3e | |||
| 4815651a1d | |||
| 71ebdc5d80 | |||
| 49ac3218f7 | |||
| 1c0c8080bf | |||
| 46f6c09739 | |||
| 93eb2d22b3 | |||
| c4d2524922 | |||
| 7482edde0a | |||
| 09b9a02284 | |||
| f895963ab9 | |||
| 637ab43626 | |||
| 811bb7ab44 | |||
| a65d330256 | |||
| 04cff622b2 | |||
| b2f260d04d | |||
| adda7bc6ac | |||
| fb936c45b6 | |||
| d0ade211ad | |||
| 90397f8029 | |||
| 7ee89d9e58 | |||
| 050fcec835 | |||
| f714a99108 | |||
| 5623c33d04 | |||
| 8d937b1e5f | |||
| 1613b12765 | |||
| db13c1b51e | |||
| 78e577114e | |||
| f5999bef99 | |||
| 1284a8f6aa | |||
| 1a89eec108 | |||
| dfb9b81c8b | |||
| 68ca55c82e | |||
| f68a338dec | |||
| 435170659e | |||
| 1a0e29e6dd | |||
| c9cf7d3ae8 | |||
| 0d4104b629 | |||
| c361682cab | |||
| 55b81a3c8f | |||
| c5e1ed8e86 | |||
| 4baf8f0d55 | |||
| 5a55397ef8 | |||
| 05200a195b | |||
| 7167849d6a | |||
| edfa90d820 | |||
| 3511452d93 | |||
| 1408f358c7 | |||
| 0297e1d22b | |||
| 10a17b0146 | |||
| bba60ea45d | |||
| 64e1e7da3e | |||
| 14e5bee7d6 | |||
| 2576987c44 | |||
| ea7d6c48db | |||
| 52f37fc3ed | |||
| 8cb0816f24 | |||
| ca59f02f4f | |||
| 7af761c5f6 | |||
| f2b75fc184 | |||
| 3bf457caef | |||
| 90b5596b76 | |||
| 93c0d6c83b | |||
| 9b5aa5f901 | |||
| bad92d3569 | |||
| 1f0e4d2260 | |||
| d460dc59f5 | |||
| eb64695697 | |||
| 308bebeb48 | |||
| bd71866342 | |||
| b55d58ec30 | |||
| 2cf4ef54c3 | |||
| 34a5e8bac1 | |||
| 502ec8ba2e | |||
| ac25897e9e | |||
| c44824c89d | |||
| 673de84936 | |||
| 78aa9c6ddc | |||
| ea2e5b9747 | |||
| ea1a33b51f | |||
| 7a441d50b9 | |||
| 364b74568e | |||
| 5f5cb1f55b | |||
| 4479fcd655 | |||
| ad56672819 | |||
| b4bcabbb9e | |||
| aaf1a1989b | |||
| 2025e366d2 | |||
| 201d1ce4e4 | |||
| 2d1e27926d | |||
| e449011f90 | |||
| 84b84453a7 | |||
| 00cee2d2fd | |||
| 78cce1970e | |||
| 977a822cc8 | |||
| 1558970619 | |||
| 86de211fa8 | |||
| 2923c6504b | |||
| 3e23950519 | |||
| 92eb32b4da | |||
| e175ea5c12 | |||
| 167c97120d | |||
| e73bd632f5 | |||
| f61e4f08a4 | |||
| 0ad8b9e8e5 | |||
| dbed559799 | |||
| 305d9cd581 | |||
| 212df695f2 |
@@ -1,9 +0,0 @@
|
||||
__pycache__/
|
||||
**/dist/*
|
||||
**/build/
|
||||
**/node_modules/
|
||||
**/tmp/*
|
||||
**/src/.venv/*
|
||||
logs/*
|
||||
*.pyc
|
||||
.DS_Store
|
||||
@@ -1,45 +1,39 @@
|
||||
# Main settings of Leon
|
||||
# https://docs.getleon.ai/configuration
|
||||
# Leon profile secrets.
|
||||
# Non-secret profile configuration lives in config.yml.
|
||||
|
||||
# Language currently used
|
||||
LEON_LANG=en-US
|
||||
# LLM provider API keys
|
||||
|
||||
# Server
|
||||
LEON_HOST=http://localhost
|
||||
LEON_PORT=1337
|
||||
# Optional local llama.cpp API key
|
||||
LEON_LLAMACPP_API_KEY=
|
||||
|
||||
# Time zone (current one by default)
|
||||
LEON_TIME_ZONE=
|
||||
# Optional Linux high-performance local provider API key (OpenAI-compatible)
|
||||
LEON_SGLANG_API_KEY=
|
||||
|
||||
# Enable/disable after speech
|
||||
LEON_AFTER_SPEECH=false
|
||||
# OpenRouter API key
|
||||
LEON_OPENROUTER_API_KEY=
|
||||
|
||||
# Enable/disable Leon's speech-to-text
|
||||
LEON_STT=false
|
||||
# Speech-to-text provider
|
||||
LEON_STT_PROVIDER=coqui-stt
|
||||
# Z.AI API key
|
||||
LEON_ZAI_API_KEY=
|
||||
|
||||
# Enable/disable Leon's text-to-speech
|
||||
LEON_TTS=false
|
||||
# Text-to-speech provider
|
||||
LEON_TTS_PROVIDER=flite
|
||||
# OpenAI API key
|
||||
LEON_OPENAI_API_KEY=
|
||||
|
||||
# Enable/disable skills to be available over HTTP
|
||||
LEON_OVER_HTTP=true
|
||||
# HTTP API key (use "npm run generate:http-api-key" to regenerate one)
|
||||
# Anthropic API key
|
||||
LEON_ANTHROPIC_API_KEY=
|
||||
|
||||
# Moonshot AI API key
|
||||
LEON_MOONSHOTAI_API_KEY=
|
||||
|
||||
# HuggingFace API key
|
||||
LEON_HUGGINGFACE_API_KEY=
|
||||
|
||||
# Cerebras API key
|
||||
LEON_CEREBRAS_API_KEY=
|
||||
|
||||
# Groq API key
|
||||
LEON_GROQ_API_KEY=
|
||||
|
||||
# Misc
|
||||
|
||||
# HTTP API key (use "pnpm run generate:http-api-key" to regenerate one)
|
||||
LEON_HTTP_API_KEY=
|
||||
# Language used for the HTTP API
|
||||
LEON_HTTP_API_LANG=en-US
|
||||
|
||||
# Enable/disable telemetry
|
||||
LEON_TELEMETRY=true
|
||||
|
||||
# Python TCP server
|
||||
LEON_PY_TCP_SERVER_HOST=0.0.0.0
|
||||
LEON_PY_TCP_SERVER_PORT=1342
|
||||
|
||||
# Path to the Pipfile
|
||||
PIPENV_PIPFILE=bridges/python/src/Pipfile
|
||||
|
||||
# Path to the virtual env in .venv/
|
||||
PIPENV_VENV_IN_PROJECT=true
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"prettier"
|
||||
],
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": true,
|
||||
"node": true
|
||||
}
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": {
|
||||
"node": true,
|
||||
"browser": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
},
|
||||
"globals": {
|
||||
"io": true
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "unicorn", "import"],
|
||||
"ignorePatterns": "*.spec.js",
|
||||
"rules": {
|
||||
"@typescript-eslint/no-non-null-assertion": ["off"],
|
||||
"no-async-promise-executor": ["off"],
|
||||
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
|
||||
"prefer-destructuring": ["error"],
|
||||
"comma-dangle": ["error", "never"],
|
||||
"semi": ["error", "never"],
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"unicorn/prefer-node-protocol": "error",
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"error",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "none",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "comma",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/consistent-type-definitions": "error",
|
||||
"import/no-named-as-default": "off",
|
||||
"import/no-named-as-default-member": "off",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"groups": [
|
||||
"builtin",
|
||||
"external",
|
||||
"internal",
|
||||
"parent",
|
||||
"sibling",
|
||||
"index"
|
||||
],
|
||||
"newlines-between": "always"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["skills/**/*.ts"],
|
||||
"rules": {
|
||||
"import/order": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -34,7 +34,7 @@ Here are few examples about how you could help on Leon, by:
|
||||
To do so, you can run:
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
- Make sure your **code passes the tests**. You can run the tests via the following command:
|
||||
@@ -65,32 +65,16 @@ git clone https://github.com/leon-ai/leon.git leon
|
||||
cd leon
|
||||
|
||||
# Install
|
||||
npm install
|
||||
pnpm install
|
||||
|
||||
# Check the setup went well
|
||||
npm run check
|
||||
pnpm run check
|
||||
|
||||
# Run the development server
|
||||
npm run dev:server
|
||||
pnpm run dev:server
|
||||
|
||||
# Run the development web app
|
||||
npm run dev:app
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```sh
|
||||
# Clone the repository
|
||||
git clone https://github.com/leon-ai/leon.git leon
|
||||
|
||||
# Go to the project root
|
||||
cd leon
|
||||
|
||||
# Build
|
||||
npm run docker:build
|
||||
|
||||
# Run the development server and the development web app
|
||||
npm run docker:dev
|
||||
pnpm run dev:app
|
||||
```
|
||||
|
||||
## Versioning
|
||||
@@ -125,7 +109,6 @@ Scopes define high-level nodes of Leon.
|
||||
|
||||
- bridge/python
|
||||
- bridge/nodejs
|
||||
- docker
|
||||
- hotword
|
||||
- scripts
|
||||
- server
|
||||
@@ -142,6 +125,11 @@ git commit -m "chore: split training script into awesome blocks"
|
||||
git commit -m "style(web app): remove chatbot useless parentheses"
|
||||
```
|
||||
|
||||
### GUI Clients
|
||||
|
||||
If you are using a GUI client such as GitKraken, you may need to disable the default Git executable to make sure to use your default shell.
|
||||
Otherwise you may encounter an error such as "npx not found".
|
||||
|
||||
## Sponsor
|
||||
|
||||
You can also contribute by [sponsoring Leon](http://sponsor.getleon.ai).
|
||||
@@ -190,37 +178,18 @@ Once Pyenv installed, run:
|
||||
|
||||
```bash
|
||||
# Install Python
|
||||
pyenv install 3.9.10 --force
|
||||
pyenv global 3.9.10
|
||||
|
||||
# Install Pipenv
|
||||
pip install pipenv==2022.7.24
|
||||
pyenv install 3.11.9 --force
|
||||
pyenv global 3.11.9
|
||||
```
|
||||
|
||||
Your Python environment should be ready now. So now you can set up the respective environments according to what you are going to contribute to and build them:
|
||||
Your Python environment should be ready now. Leon uses `uv` as the Python dependency standard, and `postinstall` provisions the Python bridge and TCP server environments automatically from `pyproject.toml`:
|
||||
|
||||
```bash
|
||||
# Set up the Python bridge environment
|
||||
npm run setup:python-bridge
|
||||
|
||||
# Set up the TCP server environment
|
||||
npm run setup:tcp-server
|
||||
# If you are in China, you can run this to download models faster:
|
||||
npm run setup:tcp-server cn
|
||||
|
||||
# Once your code changes are done, you can build via:
|
||||
|
||||
# Build the Python bridge
|
||||
npm run build:python-bridge
|
||||
|
||||
# Build the TCP server
|
||||
npm run build:tcp-server
|
||||
|
||||
# Run the Python bridge
|
||||
./bridges/python/dist/{OS-CPU_ARCH}/leon-python-bridge server/src/intent-object.sample.json
|
||||
npm run python-bridge
|
||||
|
||||
# Run the TCP server
|
||||
./tcp_server/dist/{OS-CPU_ARCH}/leon-tcp-server en
|
||||
npm run start:tcp-server
|
||||
```
|
||||
|
||||
## Spread the Word
|
||||
|
||||
@@ -15,8 +15,7 @@ If the bug is related to the setup, please submit the issue at: https://github.c
|
||||
- Leon version:
|
||||
- OS (or browser) version:
|
||||
- Node.js version:
|
||||
- Complete "leon check" (or "npm run check") output:
|
||||
- (if using Docker) Complete "npm run docker:check" output:
|
||||
- Complete "leon check" (or "pnpm run check") output:
|
||||
- (optional) Leon skill version:
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="301" height="70" fill="none" viewBox="0 0 301 70">
|
||||
<path fill="#000" d="M89.4844 43.5858c0 .8464-.1792 1.5933-.5377 2.2505-.3585.6573-.8564 1.1651-1.5137 1.5236-.6572.3585-1.3941.5378-2.2406.5378H81v6.1044h5.0787c1.912 0 3.6248-.4282 5.1484-1.2846 1.5236-.8564 2.7186-2.0415 3.585-3.5452.8663-1.5037 1.3045-3.1966 1.3045-5.0886V24.0178h-6.6322v19.568Zm17.8556-1.8224h13.891v-5.6065H107.34v-6.3633h15.355v-5.7758h-21.818v29.9743h22.246v-5.7757H107.34v-6.453Zm17.865-11.8005h8.882v24.0193h6.633V29.9629h8.842v-5.9451h-24.367v5.9551l.01-.01Zm47.022 9.0022c-.517-.2788-1.085-.4879-1.673-.6472.449-.1295.877-.2888 1.275-.488 1.096-.5676 1.962-1.3643 2.579-2.39.618-1.0257.936-2.2007.936-3.5351 0-1.5237-.418-2.8879-1.244-4.0929-.827-1.195-1.992-2.131-3.486-2.8082-1.494-.6672-3.206-1.0058-5.118-1.0058h-13.315v29.9743h13.574c2.011 0 3.804-.3485 5.387-1.0556 1.573-.707 2.798-1.6829 3.675-2.9476.866-1.2547 1.304-2.6887 1.304-4.302 0-1.4837-.338-2.8082-1.026-3.9833-.687-1.175-1.633-2.0812-2.858-2.7285l-.01.0099Zm-13.603-9.9184h5.886c.816 0 1.533.1494 2.161.4382.627.2888 1.115.707 1.464 1.2547.348.5378.527 1.1751.527 1.9021 0 .7269-.179 1.414-.527 1.9817-.349.5676-.837.9958-1.464 1.3045-.628.3087-1.345.4581-2.161.4581h-5.886v-7.3492.0099Zm10.138 18.134c-.378.5676-.916 1.0058-1.603 1.3145-.697.3087-1.484.4581-2.39.4581h-6.145v-7.6878h6.145c.886 0 1.673.1693 2.37.4979.687.3286 1.235.7867 1.613 1.3842.378.5975.578 1.2747.578 2.0414 0 .7668-.19 1.4241-.568 1.9917Zm29.596-5.3077c1.663-.7967 2.947-1.922 3.864-3.3659.916-1.444 1.374-3.117 1.374-5.0289 0-1.912-.448-3.5253-1.344-4.9592-.897-1.434-2.171-2.5394-3.814-3.3261-1.644-.7867-3.546-1.1751-5.717-1.1751h-13.124v29.9743h6.642V43.0779h4.322l6.084 10.9142h7.578l-6.851-11.7208c.339-.1195.677-.249.996-.3983h-.01Zm-2.151-6.1244c-.369.6274-.896 1.1154-1.583 1.444-.688.3386-1.494.5078-2.42.5078h-5.975v-8.2952h5.975c.926 0 1.732.1693 2.42.4979.687.3287 1.214.8166 1.583 1.434.368.6174.558 1.3544.558 2.1908 0 .8365-.19 1.5734-.558 2.2008v.0199Zm20.594-11.7308-10.706 29.9743h6.742l2.121-6.6122h11.114l2.27 6.6122h6.612L223.99 24.0178h-7.189Zm-.339 18.3431 3.445-10.5756.409-1.922.408 1.922 3.685 10.5756h-7.947Zm20.693 11.6312h6.851V24.0178h-6.851v29.9743Zm31.02-9.6993-12.896-20.275h-6.463v29.9743h6.055V33.7172l12.826 20.2749h6.533V24.0178h-6.055v20.275Zm31.528-3.3559c-.647-1.2448-1.564-2.2904-2.729-3.1369-1.165-.8464-2.509-1.4041-4.023-1.6929l-5.098-1.0456c-.797-.1892-1.434-.5178-1.902-.9958-.469-.478-.708-1.0755-.708-1.7825 0-.6473.17-1.205.518-1.683.339-.478.827-.8464 1.444-1.1153.618-.2689 1.335-.3983 2.151-.3983.817 0 1.554.1394 2.181.4182.627.2788 1.115.6672 1.464 1.1751s.528 1.0755.528 1.7228h6.642c-.04-1.7427-.528-3.2863-1.444-4.6207-.916-1.3443-2.201-2.3899-3.834-3.1468-1.633-.7568-3.505-1.1352-5.597-1.1352-2.091 0-3.943.3884-5.566 1.1751-1.623.7867-2.898 1.8721-3.804 3.2663-.906 1.3941-1.364 2.9775-1.364 4.76 0 1.444.288 2.7485.876 3.9036.587 1.1652 1.414 2.1311 2.479 2.8979 1.076.7668 2.311 1.3045 3.725 1.6033l5.397 1.1153c.886.2091 1.584.5975 2.101 1.1551.518.5577.767 1.2448.767 2.0813 0 .6672-.189 1.2747-.567 1.8025-.379.5277-.907.936-1.584 1.2248-.677.2888-1.474.4282-2.39.4282-.916 0-1.782-.1593-2.529-.478-.747-.3186-1.325-.7767-1.733-1.3742-.418-.5875-.617-1.2747-.617-2.0414h-6.642c.029 1.8721.527 3.5152 1.513 4.9492.976 1.424 2.32 2.5394 4.033 3.336 1.713.7967 3.675 1.195 5.886 1.195 2.21 0 4.202-.4083 5.915-1.2249 1.723-.8165 3.057-1.9418 4.023-3.3758.966-1.434 1.444-3.0572 1.444-4.8696 0-1.4838-.329-2.848-.976-4.1028l.02.01Z"/>
|
||||
<path fill="#000" fill-rule="evenodd" d="M6.83994 69.9901H37.9378V70c3.9071 0 7.5856-1.5308 10.3693-4.2942l17.398-17.3957C68.4391 45.5765 70 41.7992 70 37.9423V6.83897C70 3.06163 66.928 0 63.1601 0H32.0523c-3.8972 0-7.5856 1.53082-10.3594 4.29424L4.29484 21.6799C1.56086 24.4135 0 28.1909 0 32.0477v31.1034c0 3.7773 3.07201 6.839 6.83994 6.839ZM11.5583 19 23.9795 6.58052c2.1674-2.15706 5.0306-3.33996 8.0728-3.33996h31.1078c1.9883 0 3.5989 1.62028 3.5989 3.59841V37.9423c0 3.002-1.2129 5.9444-3.3405 8.0716L51 58.4307V19H11.5583ZM11 19.5582V59h39.4306l-4.4101 4.4095c-2.1574 2.1472-5.0306 3.34-8.0728 3.34H6.83994c-1.98836 0-3.59892-1.6203-3.59892-3.5984V32.0477c0-3.002 1.21289-5.9443 3.34043-8.0716L11 19.5582ZM33 50H16v4h17v-4Z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="298" height="64" fill="none" viewBox="0 0 298 64">
|
||||
<path fill="#fff" d="M86.484 40.586c0 .846-.179 1.593-.537 2.25a3.718 3.718 0 0 1-1.514 1.524c-.657.358-1.394.538-2.24.538H78v6.104h5.079c1.912 0 3.625-.428 5.148-1.285a9.36 9.36 0 0 0 3.585-3.545c.866-1.503 1.305-3.196 1.305-5.088V21.018h-6.633v19.568Zm17.856-1.823h13.891v-5.606H104.34v-6.363h15.355v-5.776H97.877v29.974h22.246v-5.776H104.34v-6.453Zm17.865-11.8h8.882v24.02h6.633v-24.02h8.842v-5.945h-24.367v5.955l.01-.01Zm47.022 9.002a7.85 7.85 0 0 0-1.673-.647 7.47 7.47 0 0 0 1.275-.488c1.096-.568 1.962-1.364 2.579-2.39.618-1.026.936-2.2.936-3.535 0-1.524-.418-2.888-1.244-4.093-.827-1.195-1.992-2.131-3.486-2.808-1.494-.668-3.206-1.006-5.118-1.006h-13.315v29.974h13.574c2.011 0 3.804-.348 5.387-1.055 1.573-.707 2.798-1.683 3.675-2.948.866-1.255 1.304-2.689 1.304-4.302 0-1.484-.338-2.808-1.026-3.983a7.05 7.05 0 0 0-2.858-2.729l-.01.01Zm-13.603-9.918h5.886c.816 0 1.533.15 2.161.438a3.353 3.353 0 0 1 1.464 1.255c.348.537.527 1.175.527 1.902 0 .727-.179 1.414-.527 1.981-.349.568-.837.996-1.464 1.305-.628.309-1.345.458-2.161.458h-5.886v-7.35.01Zm10.138 18.134c-.378.567-.916 1.006-1.603 1.314-.697.309-1.484.458-2.39.458h-6.145v-7.688h6.145c.886 0 1.673.17 2.37.498.687.329 1.235.787 1.613 1.385.378.597.578 1.274.578 2.041 0 .767-.19 1.424-.568 1.992Zm29.596-5.308c1.663-.797 2.947-1.922 3.864-3.366.916-1.444 1.374-3.117 1.374-5.029s-.448-3.525-1.344-4.959c-.897-1.434-2.171-2.54-3.814-3.326-1.644-.787-3.546-1.175-5.717-1.175h-13.124v29.974h6.642V40.078h4.322l6.084 10.914h7.578l-6.851-11.72c.339-.12.677-.25.996-.399h-.01Zm-2.151-6.124a3.599 3.599 0 0 1-1.583 1.444c-.688.338-1.494.507-2.42.507h-5.975v-8.295h5.975c.926 0 1.732.17 2.42.498a3.637 3.637 0 0 1 1.583 1.434c.368.617.558 1.355.558 2.19 0 .837-.19 1.574-.558 2.202v.02Zm20.594-11.731-10.706 29.974h6.742l2.121-6.612h11.114l2.27 6.612h6.612L220.99 21.018h-7.189Zm-.339 18.343 3.445-10.576.409-1.922.408 1.922 3.685 10.576h-7.947Zm20.693 11.631h6.851V21.018h-6.851v29.974Zm31.02-9.7-12.896-20.274h-6.463v29.974h6.055V30.717l12.826 20.275h6.533V21.018h-6.055v20.275Zm31.528-3.355c-.647-1.245-1.564-2.29-2.729-3.137-1.165-.846-2.509-1.404-4.023-1.693l-5.098-1.045c-.797-.19-1.434-.518-1.902-.996-.469-.478-.708-1.076-.708-1.783 0-.647.17-1.205.518-1.683.339-.478.827-.846 1.444-1.115.618-.269 1.335-.398 2.151-.398.817 0 1.554.139 2.181.418.627.279 1.115.667 1.464 1.175a2.96 2.96 0 0 1 .528 1.723h6.642c-.04-1.743-.528-3.287-1.444-4.621-.916-1.344-2.201-2.39-3.834-3.147-1.633-.757-3.505-1.135-5.597-1.135-2.091 0-3.943.388-5.566 1.175-1.623.787-2.898 1.872-3.804 3.266-.906 1.395-1.364 2.978-1.364 4.76 0 1.444.288 2.749.876 3.904a7.908 7.908 0 0 0 2.479 2.898c1.076.767 2.311 1.304 3.725 1.603l5.397 1.115c.886.21 1.584.598 2.101 1.156.518.557.767 1.244.767 2.08a3.03 3.03 0 0 1-.567 1.803c-.379.528-.907.936-1.584 1.225-.677.289-1.474.428-2.39.428-.916 0-1.782-.159-2.529-.478-.747-.318-1.325-.776-1.733-1.374-.418-.587-.617-1.275-.617-2.041h-6.642c.029 1.872.527 3.515 1.513 4.949.976 1.424 2.32 2.54 4.033 3.336 1.713.797 3.675 1.195 5.886 1.195 2.21 0 4.202-.408 5.915-1.225 1.723-.816 3.057-1.942 4.023-3.376.966-1.434 1.444-3.057 1.444-4.87 0-1.483-.329-2.847-.976-4.102l.02.01ZM13 47h17v4H13v-4Z"/>
|
||||
<path fill="#fff" fill-rule="evenodd" d="M20.34 3.66 8 16l-4.34 4.34A12.504 12.504 0 0 0 0 29.18V59c0 2.76 2.24 5 5 5h29.82c3.32 0 6.49-1.32 8.84-3.66L48 56l12.34-12.34c2.34-2.34 3.66-5.52 3.66-8.84V5c0-2.76-2.24-5-5-5H29.18c-3.32 0-6.49 1.32-8.84 3.66ZM48 56V16H8v40h40Z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" version="1.1" width="180" height="70" viewBox="0 0 1525.6 589.8">
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #fe9000;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<rect class="st0" x="281" y="444.8" width="29" height="145"/>
|
||||
<rect class="st0" x="280" width="29" height="145"/>
|
||||
<rect class="st0" x="153.1" y="491.6" width="145" height="29" transform="translate(-325.8 560.4) rotate(-71.6)"/>
|
||||
<rect class="st0" x="90.9" y="460.1" width="145" height="29" transform="translate(-316.6 327.8) rotate(-54)"/>
|
||||
<rect class="st0" x="41.9" y="409.9" width="145" height="29" transform="translate(-225.7 145.9) rotate(-35.6)"/>
|
||||
<rect class="st0" x="10.4" y="348.3" width="145" height="29" transform="translate(-108.1 43.4) rotate(-18)"/>
|
||||
<rect class="st0" y="279.8" width="145" height="29"/>
|
||||
<rect class="st0" x="68.3" y="153.9" width="29" height="145" transform="translate(-158.1 235.1) rotate(-72)"/>
|
||||
<rect class="st0" x="99.7" y="91.3" width="29" height="145" transform="translate(-85.4 158.9) rotate(-53.7)"/>
|
||||
<rect class="st0" x="211.1" y="10.8" width="29" height="145" transform="translate(-14.7 74.2) rotate(-18.1)"/>
|
||||
<rect class="st0" x="149.6" y="41.3" width="29" height="145" transform="translate(-35.5 119.2) rotate(-36.3)"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M349,307.4c-.8-20.3-1.5-44.5-1.5-69.3h-.5c-5.5,21.5-12.5,45.5-19,65.3l-20.5,65.8h-29.8l-18-65.2c-5.5-19.8-11-43.5-15.2-65.8h-.5c-.8,22.8-1.8,48.8-2.8,69.8l-3.2,64h-35.2l10.8-168.5h50.8l16.5,56.2c5.5,19.5,10.5,40.5,14.2,60.3h1c4.5-19.5,10.2-41.8,16-60.5l17.8-56h50l9,168.5h-37l-2.9-64.6h0Z"/>
|
||||
<path class="st1" d="M518.5,342.4c0,11.5.5,22.8,2,29.5h-34.2l-2.2-12.2h-.8c-8,9.8-20.5,15-35,15-24.8,0-39.5-18-39.5-37.5,0-31.8,28.5-46.8,71.8-46.8v-1.5c0-6.5-3.5-15.8-22.2-15.8s-25.8,4.2-33.8,9l-7-24.2c8.5-4.8,25.2-11,47.5-11,40.8,0,53.5,24,53.5,52.8v42.7h0ZM481.8,314.1c-20,0-35.5,4.8-35.5,19.2s6.5,14.5,15,14.5,17.2-6.2,19.8-14c.5-2,.8-4.2.8-6.5v-13.2h0Z"/>
|
||||
<path class="st1" d="M640.5,368.6c-6.8,3.2-19.5,5.8-34,5.8-39.5,0-64.8-24.2-64.8-62.5s24.5-64.8,70-64.8,21,1.8,29,4.8l-6,28.2c-4.5-2-11.2-3.8-21.2-3.8-20,0-33,14.3-32.8,34,0,22.5,15,34.2,33.5,34.2s16-1.5,21.8-4l4.5,28.1h0Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M687.5,345.4c9.8,6,24,11,39,11,22.2,0,35.2-11.8,35.2-28.8s-9-24.8-31.8-33.5c-27.5-9.8-44.5-24-44.5-47.8s21.8-45.8,54.5-45.8,29.7,4,37.2,8.2l-6,17.8c-5.5-3-16.7-8-32-8-23,0-31.8,13.8-31.8,25.2s10.2,23.5,33.5,32.5c28.5,11,43,24.8,43,49.5s-19.2,48.5-59,48.5-34-4.8-43-10.8l5.7-18h0Z"/>
|
||||
<path class="st1" d="M838.8,222v28.8h31.5v16.8h-31.5v65.2c0,15,4.2,23.5,16.5,23.5s10-.8,12.8-1.5l1,16.5c-4.2,1.8-11,3-19.5,3s-18.5-3.2-23.8-9.2c-6.2-6.5-8.5-17.2-8.5-31.5v-66h-18.8v-16.8h18.8v-22.3l21.5-6.5h0Z"/>
|
||||
<path class="st1" d="M962,371.9l-1.8-15.2h-.8c-6.8,9.5-19.8,18-37,18-24.5,0-37-17.2-37-34.8,0-29.2,26-45.2,72.8-45v-2.5c0-10-2.8-28-27.5-28s-23,3.5-31.5,9l-5-14.5c10-6.5,24.5-10.8,39.8-10.8,37,0,46,25.2,46,49.5v45.2c0,10.5.5,20.8,2,29h-20ZM958.8,310.1c-24-.5-51.2,3.8-51.2,27.2s9.5,21,20.8,21,25.8-10,29.2-20.2c.8-2.2,1.2-4.8,1.2-7v-21h0Z"/>
|
||||
<path class="st1" d="M1120,194.4v146.2c0,10.8.2,23,1,31.2h-19.8l-1-21h-.5c-6.8,13.5-21.5,23.8-41.2,23.8-29.2,0-51.8-24.8-51.8-61.5-.2-40.2,24.8-65,54.2-65s31,8.8,36.5,18.5h.5v-72.2h22.1ZM1098,300.1c0-2.8-.2-6.5-1-9.2-3.2-14-15.2-25.5-31.8-25.5s-36.2,20-36.2,46.8,12,44.8,35.8,44.8,28.2-9.8,32.2-26.2c.8-3,1-6,1-9.5v-21.2h0Z"/>
|
||||
<path class="st1" d="M1181,216.9c.2,7.5-5.2,13.5-14,13.5s-13.2-6-13.2-13.5,5.8-13.8,13.8-13.8,13.4,6,13.4,13.8h0ZM1156.5,371.9v-121h22v121h-22Z"/>
|
||||
<path class="st1" d="M1316.3,338.9c0,12.5.2,23.5,1,33h-19.5l-1.2-19.8h-.5c-5.8,9.8-18.5,22.5-40,22.5s-41.8-10.5-41.8-53v-70.8h22v67c0,23,7,38.5,27,38.5s25-10.2,29-20c1.2-3.2,2-7.2,2-11.2v-74.2h22v88h0Z"/>
|
||||
<path class="st1" d="M1352.8,283.6c0-12.5-.2-22.8-1-32.8h19.2l1,19.5h.8c6.8-11.5,18-22.2,38-22.2s29,10,34.2,24.2h.5c3.8-6.8,8.5-12,13.5-15.8,7.2-5.5,15.2-8.5,26.8-8.5s39.8,10.5,39.8,52.5v71.2h-21.6v-68.5c0-23.2-8.5-37.2-26.2-37.2s-22.2,9.2-26,20c-1,3-1.8,7-1.8,11v74.8h-21.5v-72.5c0-19.2-8.5-33.2-25.2-33.2s-23.8,11-27.2,22c-1.2,3.2-1.8,7-1.8,10.8v73h-21.5v-88.3h0Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" version="1.1" width="180" height="70" viewBox="0 0 1525.6 589.8">
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #fe9000;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #111;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<rect class="st0" x="281" y="444.8" width="29" height="145"/>
|
||||
<rect class="st0" x="280" width="29" height="145"/>
|
||||
<rect class="st0" x="153.1" y="491.6" width="145" height="29" transform="translate(-325.8 560.4) rotate(-71.6)"/>
|
||||
<rect class="st0" x="90.9" y="460.1" width="145" height="29" transform="translate(-316.6 327.8) rotate(-54)"/>
|
||||
<rect class="st0" x="41.9" y="409.9" width="145" height="29" transform="translate(-225.7 145.9) rotate(-35.6)"/>
|
||||
<rect class="st0" x="10.4" y="348.3" width="145" height="29" transform="translate(-108.1 43.4) rotate(-18)"/>
|
||||
<rect class="st0" y="279.8" width="145" height="29"/>
|
||||
<rect class="st0" x="68.3" y="153.9" width="29" height="145" transform="translate(-158.1 235.1) rotate(-72)"/>
|
||||
<rect class="st0" x="99.7" y="91.3" width="29" height="145" transform="translate(-85.4 158.9) rotate(-53.7)"/>
|
||||
<rect class="st0" x="211.1" y="10.8" width="29" height="145" transform="translate(-14.7 74.2) rotate(-18.1)"/>
|
||||
<rect class="st0" x="149.6" y="41.3" width="29" height="145" transform="translate(-35.5 119.2) rotate(-36.3)"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M349,307.4c-.8-20.3-1.5-44.5-1.5-69.3h-.5c-5.5,21.5-12.5,45.5-19,65.3l-20.5,65.8h-29.8l-18-65.2c-5.5-19.8-11-43.5-15.2-65.8h-.5c-.8,22.8-1.8,48.8-2.8,69.8l-3.2,64h-35.2l10.8-168.5h50.8l16.5,56.2c5.5,19.5,10.5,40.5,14.2,60.3h1c4.5-19.5,10.2-41.8,16-60.5l17.8-56h50l9,168.5h-37l-2.9-64.6h0Z"/>
|
||||
<path class="st1" d="M518.5,342.4c0,11.5.5,22.8,2,29.5h-34.2l-2.2-12.2h-.8c-8,9.8-20.5,15-35,15-24.8,0-39.5-18-39.5-37.5,0-31.8,28.5-46.8,71.8-46.8v-1.5c0-6.5-3.5-15.8-22.2-15.8s-25.8,4.2-33.8,9l-7-24.2c8.5-4.8,25.2-11,47.5-11,40.8,0,53.5,24,53.5,52.8v42.7h0ZM481.8,314.1c-20,0-35.5,4.8-35.5,19.2s6.5,14.5,15,14.5,17.2-6.2,19.8-14c.5-2,.8-4.2.8-6.5v-13.2h0Z"/>
|
||||
<path class="st1" d="M640.5,368.6c-6.8,3.2-19.5,5.8-34,5.8-39.5,0-64.8-24.2-64.8-62.5s24.5-64.8,70-64.8,21,1.8,29,4.8l-6,28.2c-4.5-2-11.2-3.8-21.2-3.8-20,0-33,14.3-32.8,34,0,22.5,15,34.2,33.5,34.2s16-1.5,21.8-4l4.5,28.1h0Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M687.5,345.4c9.8,6,24,11,39,11,22.2,0,35.2-11.8,35.2-28.8s-9-24.8-31.8-33.5c-27.5-9.8-44.5-24-44.5-47.8s21.8-45.8,54.5-45.8,29.7,4,37.2,8.2l-6,17.8c-5.5-3-16.7-8-32-8-23,0-31.8,13.8-31.8,25.2s10.2,23.5,33.5,32.5c28.5,11,43,24.8,43,49.5s-19.2,48.5-59,48.5-34-4.8-43-10.8l5.7-18h0Z"/>
|
||||
<path class="st1" d="M838.8,222v28.8h31.5v16.8h-31.5v65.2c0,15,4.2,23.5,16.5,23.5s10-.8,12.8-1.5l1,16.5c-4.2,1.8-11,3-19.5,3s-18.5-3.2-23.8-9.2c-6.2-6.5-8.5-17.2-8.5-31.5v-66h-18.8v-16.8h18.8v-22.3l21.5-6.5h0Z"/>
|
||||
<path class="st1" d="M962,371.9l-1.8-15.2h-.8c-6.8,9.5-19.8,18-37,18-24.5,0-37-17.2-37-34.8,0-29.2,26-45.2,72.8-45v-2.5c0-10-2.8-28-27.5-28s-23,3.5-31.5,9l-5-14.5c10-6.5,24.5-10.8,39.8-10.8,37,0,46,25.2,46,49.5v45.2c0,10.5.5,20.8,2,29h-20ZM958.8,310.1c-24-.5-51.2,3.8-51.2,27.2s9.5,21,20.8,21,25.8-10,29.2-20.2c.8-2.2,1.2-4.8,1.2-7v-21h0Z"/>
|
||||
<path class="st1" d="M1120,194.4v146.2c0,10.8.2,23,1,31.2h-19.8l-1-21h-.5c-6.8,13.5-21.5,23.8-41.2,23.8-29.2,0-51.8-24.8-51.8-61.5-.2-40.2,24.8-65,54.2-65s31,8.8,36.5,18.5h.5v-72.2h22.1ZM1098,300.1c0-2.8-.2-6.5-1-9.2-3.2-14-15.2-25.5-31.8-25.5s-36.2,20-36.2,46.8,12,44.8,35.8,44.8,28.2-9.8,32.2-26.2c.8-3,1-6,1-9.5v-21.2h0Z"/>
|
||||
<path class="st1" d="M1181,216.9c.2,7.5-5.2,13.5-14,13.5s-13.2-6-13.2-13.5,5.8-13.8,13.8-13.8,13.4,6,13.4,13.8h0ZM1156.5,371.9v-121h22v121h-22Z"/>
|
||||
<path class="st1" d="M1316.3,338.9c0,12.5.2,23.5,1,33h-19.5l-1.2-19.8h-.5c-5.8,9.8-18.5,22.5-40,22.5s-41.8-10.5-41.8-53v-70.8h22v67c0,23,7,38.5,27,38.5s25-10.2,29-20c1.2-3.2,2-7.2,2-11.2v-74.2h22v88h0Z"/>
|
||||
<path class="st1" d="M1352.8,283.6c0-12.5-.2-22.8-1-32.8h19.2l1,19.5h.8c6.8-11.5,18-22.2,38-22.2s29,10,34.2,24.2h.5c3.8-6.8,8.5-12,13.5-15.8,7.2-5.5,15.2-8.5,26.8-8.5s39.8,10.5,39.8,52.5v71.2h-21.6v-68.5c0-23.2-8.5-37.2-26.2-37.2s-22.2,9.2-26,20c-1,3-1.8,7-1.8,11v74.8h-21.5v-72.5c0-19.2-8.5-33.2-25.2-33.2s-23.8,11-27.2,22c-1.2,3.2-1.8,7-1.8,10.8v73h-21.5v-88.3h0Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="180" height="48" viewBox="220 230 1165 310" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1637_3573)">
|
||||
<path d="M358.8 240.1C293.02 240.1 239.2 293.92 239.2 359.7C239.2 425.48 293.02 479.3 358.8 479.3C424.58 479.3 478.4 425.812 478.4 359.7C478.4 293.587 424.912 240.1 358.8 240.1ZM358.8 436.443C317.937 436.443 285.046 402.889 285.046 359.7C285.046 316.511 317.937 282.956 358.8 282.956C399.663 282.956 432.553 316.511 432.553 359.7C432.553 402.889 399.663 436.443 358.8 436.443Z" fill="white"/>
|
||||
<path d="M599.326 306.544C577.732 306.544 556.802 315.182 545.838 329.8V309.866H502.649V542.422H545.838V458.37C556.802 471.991 577.067 479.3 599.326 479.3C645.837 479.3 682.382 442.755 682.382 392.922C682.382 343.089 645.837 306.544 599.326 306.544ZM592.017 441.759C567.433 441.759 545.506 422.49 545.506 392.922C545.506 363.354 567.433 344.085 592.017 344.085C616.602 344.085 638.528 363.354 638.528 392.922C638.528 422.49 616.602 441.759 592.017 441.759Z" fill="white"/>
|
||||
<path d="M784.046 306.544C736.871 306.544 699.662 343.421 699.662 392.922C699.662 442.423 732.22 479.3 785.375 479.3C828.896 479.3 856.803 453.054 865.441 423.486H823.249C817.933 435.779 802.983 444.416 785.043 444.416C762.784 444.416 745.841 428.802 741.854 406.543H867.434V389.6C867.434 344.417 835.873 306.544 784.046 306.544ZM742.186 375.979C746.838 355.049 764.113 341.428 785.043 341.428C807.302 341.428 824.245 356.045 826.239 375.979H742.186Z" fill="white"/>
|
||||
<path d="M982.38 306.544C963.111 306.544 942.845 315.182 933.543 329.468V309.866H890.354V475.978H933.543V386.61C933.543 360.696 947.496 343.753 970.087 343.753C991.017 343.753 1002.31 359.7 1002.31 381.959V475.978H1045.5V374.982C1045.5 333.786 1020.25 306.544 982.38 306.544Z" fill="white"/>
|
||||
<path d="M1156.12 243.428L1062.1 475.984H1108.28L1128.21 425.154H1235.19L1255.12 475.984H1301.96L1208.61 243.428H1156.12ZM1143.16 386.616L1181.7 289.275L1219.9 386.616H1143.16Z" fill="white"/>
|
||||
<path d="M1363.42 243.428H1319.57V475.984H1363.42V243.428Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1637_3573">
|
||||
<rect width="1603.2" height="717.6" fill="white" transform="translate(0 0.899902)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="180" height="48" viewBox="220 230 1165 310" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1637_2932)">
|
||||
<path d="M358.8 239.3C293.02 239.3 239.2 293.12 239.2 358.9C239.2 424.68 293.02 478.5 358.8 478.5C424.58 478.5 478.4 425.012 478.4 358.9C478.4 292.787 424.912 239.3 358.8 239.3ZM358.8 435.643C317.937 435.643 285.046 402.089 285.046 358.9C285.046 315.711 317.937 282.156 358.8 282.156C399.663 282.156 432.553 315.711 432.553 358.9C432.553 402.089 399.663 435.643 358.8 435.643Z" fill="black"/>
|
||||
<path d="M599.326 305.744C577.732 305.744 556.802 314.382 545.838 329V309.066H502.649V541.622H545.838V457.57C556.802 471.191 577.067 478.5 599.326 478.5C645.837 478.5 682.382 441.955 682.382 392.122C682.382 342.289 645.837 305.744 599.326 305.744ZM592.017 440.959C567.433 440.959 545.506 421.69 545.506 392.122C545.506 362.554 567.433 343.285 592.017 343.285C616.602 343.285 638.528 362.554 638.528 392.122C638.528 421.69 616.602 440.959 592.017 440.959Z" fill="black"/>
|
||||
<path d="M784.046 305.744C736.871 305.744 699.662 342.621 699.662 392.122C699.662 441.623 732.22 478.5 785.375 478.5C828.896 478.5 856.803 452.254 865.441 422.686H823.249C817.933 434.979 802.983 443.616 785.043 443.616C762.784 443.616 745.841 428.002 741.854 405.743H867.434V388.8C867.434 343.617 835.873 305.744 784.046 305.744ZM742.186 375.179C746.838 354.249 764.113 340.627 785.043 340.627C807.302 340.627 824.245 355.245 826.239 375.179H742.186Z" fill="black"/>
|
||||
<path d="M982.38 305.744C963.111 305.744 942.845 314.382 933.543 328.667V309.066H890.354V475.177H933.543V385.81C933.543 359.896 947.496 342.953 970.087 342.953C991.017 342.953 1002.31 358.9 1002.31 381.159V475.177H1045.5V374.182C1045.5 332.986 1020.25 305.744 982.38 305.744Z" fill="black"/>
|
||||
<path d="M1156.12 242.628L1062.1 475.184H1108.28L1128.21 424.354H1235.19L1255.12 475.184H1301.96L1208.61 242.628H1156.12ZM1143.16 385.816L1181.7 288.475L1219.9 385.816H1143.16Z" fill="black"/>
|
||||
<path d="M1363.42 242.628H1319.57V475.184H1363.42V242.628Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1637_2932">
|
||||
<rect width="1603.2" height="717.6" fill="white" transform="translate(0 0.0999756)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,13 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
day: 'friday'
|
||||
time: '22:00'
|
||||
commit-message:
|
||||
prefix: 'chore'
|
||||
include: 'scope'
|
||||
reviewers:
|
||||
- 'louistiti'
|
||||
@@ -23,17 +23,17 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.x
|
||||
cache: npm
|
||||
node-version: 24.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install Pipenv
|
||||
run: pip install --upgrade pip && pip install pipenv
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install
|
||||
run: npm install
|
||||
run: pnpm install
|
||||
|
||||
- name: Check setup
|
||||
run: npm run check
|
||||
run: pnpm run check
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
run: pnpm run build
|
||||
|
||||
@@ -23,14 +23,14 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.x
|
||||
cache: npm
|
||||
node-version: 24.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install Pipenv
|
||||
run: pip install --upgrade pip && pip install pipenv
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install
|
||||
run: npm install
|
||||
run: pnpm install
|
||||
|
||||
- name: Run linter
|
||||
run: npm run lint
|
||||
run: pnpm run lint
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
name: Pre-release Node.js bridge
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Set Node.js bridge version
|
||||
working-directory: bridges/nodejs/src
|
||||
run: |
|
||||
echo "NODEJS_BRIDGE_VERSION=$(node --require fs --eval "const fs = require('node:fs'); const [, VERSION] = fs.readFileSync('version.ts', 'utf8').split(\"'\"); console.log(VERSION)")" >> $GITHUB_ENV
|
||||
|
||||
- name: Display Node.js bridge version
|
||||
run: |
|
||||
echo "Node.js bridge version: ${{ env.NODEJS_BRIDGE_VERSION }}"
|
||||
|
||||
- name: Install core
|
||||
run: npm install
|
||||
|
||||
- name: Build Node.js bridge
|
||||
run: npm run build:nodejs-bridge
|
||||
|
||||
- name: Upload Node.js bridge
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: bridges/nodejs/dist/*.zip
|
||||
|
||||
draft-release:
|
||||
name: Draft-release
|
||||
needs: [build]
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Set Node.js bridge version
|
||||
working-directory: bridges/nodejs/src
|
||||
run: |
|
||||
echo "NODEJS_BRIDGE_VERSION=$(node --require fs --eval "const fs = require('node:fs'); const [, VERSION] = fs.readFileSync('version.ts', 'utf8').split(\"'\"); console.log(VERSION)")" >> $GITHUB_ENV
|
||||
|
||||
- name: Download Node.js bridge
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: bridges/nodejs/dist
|
||||
|
||||
- uses: marvinpinto/action-automatic-releases@latest
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
automatic_release_tag: nodejs-bridge_v${{ env.NODEJS_BRIDGE_VERSION }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
title: Node.js Bridge ${{ env.NODEJS_BRIDGE_VERSION }}
|
||||
files: bridges/nodejs/dist/artifact/*.zip
|
||||
@@ -1,91 +0,0 @@
|
||||
name: Pre-release Python bridge
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
env:
|
||||
PIPENV_PIPFILE: bridges/python/src
|
||||
PIPENV_VENV_IN_PROJECT: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9.10
|
||||
|
||||
- name: Install Pipenv
|
||||
run: pip install --upgrade pip && pip install pipenv==2022.7.24
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Set Python bridge version
|
||||
working-directory: bridges/python/src
|
||||
run: |
|
||||
echo "PYTHON_BRIDGE_VERSION=$(python -c "from version import __version__; print(__version__)")" >> $GITHUB_ENV
|
||||
|
||||
- name: Display Python bridge version
|
||||
run: |
|
||||
echo "Python bridge version: ${{ env.PYTHON_BRIDGE_VERSION }}"
|
||||
|
||||
- name: Install core
|
||||
run: npm install
|
||||
|
||||
- name: Set up Python bridge
|
||||
run: npm run setup:python-bridge
|
||||
|
||||
- name: Build Python bridge
|
||||
run: npm run build:python-bridge
|
||||
|
||||
- name: Upload Python bridge
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: bridges/python/dist/*.zip
|
||||
|
||||
draft-release:
|
||||
name: Draft-release
|
||||
needs: [build]
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9.10
|
||||
|
||||
- name: Set Python bridge version
|
||||
working-directory: bridges/python/src
|
||||
run: |
|
||||
echo "PYTHON_BRIDGE_VERSION=$(python -c "from version import __version__; print(__version__)")" >> $GITHUB_ENV
|
||||
|
||||
- name: Download Python bridge
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: bridges/python/dist
|
||||
|
||||
- uses: marvinpinto/action-automatic-releases@latest
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
automatic_release_tag: python-bridge_v${{ env.PYTHON_BRIDGE_VERSION }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
title: Python Bridge ${{ env.PYTHON_BRIDGE_VERSION }}
|
||||
files: bridges/python/dist/artifact/*.zip
|
||||
@@ -1,91 +0,0 @@
|
||||
name: Pre-release TCP server
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
env:
|
||||
PIPENV_PIPFILE: tcp_server/src
|
||||
PIPENV_VENV_IN_PROJECT: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9.10
|
||||
|
||||
- name: Install Pipenv
|
||||
run: pip install --upgrade pip && pip install pipenv==2022.7.24
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Set TCP server version
|
||||
working-directory: tcp_server/src
|
||||
run: |
|
||||
echo "TCP_SERVER_VERSION=$(python -c "from version import __version__; print(__version__)")" >> $GITHUB_ENV
|
||||
|
||||
- name: Display TCP server version
|
||||
run: |
|
||||
echo "TCP server version: ${{ env.TCP_SERVER_VERSION }}"
|
||||
|
||||
- name: Install core
|
||||
run: npm install
|
||||
|
||||
- name: Set up TCP server
|
||||
run: npm run setup:tcp-server
|
||||
|
||||
- name: Build TCP server
|
||||
run: npm run build:tcp-server
|
||||
|
||||
- name: Upload TCP server
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: tcp_server/dist/*.zip
|
||||
|
||||
draft-release:
|
||||
name: Draft-release
|
||||
needs: [build]
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9.10
|
||||
|
||||
- name: Set TCP server version
|
||||
working-directory: tcp_server/src
|
||||
run: |
|
||||
echo "TCP_SERVER_VERSION=$(python -c "from version import __version__; print(__version__)")" >> $GITHUB_ENV
|
||||
|
||||
- name: Download TCP server
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: tcp_server/dist
|
||||
|
||||
- uses: marvinpinto/action-automatic-releases@latest
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
automatic_release_tag: tcp-server_v${{ env.TCP_SERVER_VERSION }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
title: TCP Server ${{ env.TCP_SERVER_VERSION }}
|
||||
files: tcp_server/dist/artifact/*.zip
|
||||
@@ -23,29 +23,20 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.x
|
||||
cache: npm
|
||||
node-version: 24.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install Pipenv
|
||||
run: pip install --upgrade pip && pip install pipenv
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install
|
||||
run: npm install
|
||||
run: pnpm install
|
||||
|
||||
- name: Run JSON tests
|
||||
run: npm run test:json
|
||||
- name: Run agent unit tests
|
||||
run: pnpm run test:agent:unit
|
||||
|
||||
- name: Run over HTTP tests
|
||||
run: npm run test:over-http
|
||||
- name: Run workflow unit tests
|
||||
run: pnpm run test:workflow:unit
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npm run test:e2e
|
||||
|
||||
- name: Install offline STT
|
||||
run: npm run setup:offline-stt
|
||||
|
||||
- name: Install offline TTS
|
||||
run: npm run setup:offline-tts
|
||||
|
||||
- name: Run unit tests
|
||||
run: npm run test:unit
|
||||
- name: Run workflow e2e tests
|
||||
run: pnpm run test:workflow:e2e
|
||||
|
||||
@@ -5,13 +5,13 @@ __pycache__/
|
||||
**/dist/*
|
||||
**/build/
|
||||
**/node_modules/
|
||||
**/.venv/
|
||||
**/.last-skill-deps-sync
|
||||
**/.last-source-deps-sync
|
||||
test/coverage/
|
||||
**/tmp/*
|
||||
**/src/.venv/*
|
||||
logs/*
|
||||
core/config/**/*.json
|
||||
bin/coqui/*
|
||||
bin/flite/*
|
||||
scripts/out/*.md
|
||||
|
||||
package-lock.json
|
||||
*.pyc
|
||||
@@ -19,18 +19,17 @@ package-lock.json
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
debug.log
|
||||
# Keep ignoring a repo-root .env on the safe side.
|
||||
.env
|
||||
.last-skill-npm-install
|
||||
leon.json
|
||||
.last-native-node-modules-rebuild
|
||||
bridges/nodejs/.last-nodejs-bridge-deps-sync
|
||||
bridges/python/src/.last-python-bridge-deps-sync
|
||||
tcp_server/src/.last-tcp-server-deps-sync
|
||||
bridges/python/src/Pipfile.lock
|
||||
tcp_server/src/Pipfile.lock
|
||||
!tcp_server/**/.gitkeep
|
||||
!bridges/python/**/.gitkeep
|
||||
!bridges/nodejs/**/.gitkeep
|
||||
!**/*.sample*
|
||||
skills/**/src/settings.json
|
||||
skills/**/memory/*.json
|
||||
core/data/models/*.nlp
|
||||
package.json.backup
|
||||
.python-version
|
||||
schemas/**/*.json
|
||||
|
||||
@@ -1,11 +1 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
if ! [ -x "$(command -v npm)" ]; then
|
||||
echo "npm: command not found"
|
||||
echo "If you use a version manager tool such as nvm and a git GUI such as GitKraken, please read: https://typicode.github.io/husky/#/?id=command-not-found" >&2
|
||||
exit 1
|
||||
else
|
||||
npx ts-node scripts/commit-msg.js
|
||||
fi
|
||||
|
||||
tsx scripts/commit-msg.js
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
lint-staged
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"*": ["npm run lint"]
|
||||
"*.{js,jsx,ts,tsx,mjs,cjs}": ["eslint --fix --ignore-pattern .gitignore"]
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"semi": false,
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
FROM ubuntu:20.04
|
||||
ENV IS_DOCKER true
|
||||
|
||||
# Replace shell with bash so we can source files
|
||||
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
|
||||
|
||||
# Set debconf to run non-interactively
|
||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
|
||||
# Install base dependencies
|
||||
RUN apt-get update && apt-get install --yes -q --no-install-recommends \
|
||||
apt-transport-https \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
wget \
|
||||
libssl-dev \
|
||||
openssl \
|
||||
libz-dev \
|
||||
zlib1g-dev \
|
||||
libbz2-dev \
|
||||
libreadline-dev \
|
||||
libsqlite3-dev \
|
||||
llvm \
|
||||
libncurses5-dev \
|
||||
xz-utils \
|
||||
tk-dev libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libffi-dev \
|
||||
liblzma-dev \
|
||||
libgdbm-dev \
|
||||
libnss3-dev \
|
||||
libc6-dev
|
||||
|
||||
# Run the container as an unprivileged user
|
||||
RUN groupadd docker && useradd -g docker -s /bin/bash -m docker
|
||||
USER docker
|
||||
WORKDIR /home/docker
|
||||
|
||||
# Install Node.js with nvm
|
||||
ENV NVM_DIR /home/docker/.nvm
|
||||
ENV NODE_VERSION v16.18.0
|
||||
|
||||
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash
|
||||
RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm use --delete-prefix $NODE_VERSION"
|
||||
|
||||
ENV NODE_PATH $NVM_DIR/versions/node/$NODE_VERSION/lib/node_modules
|
||||
ENV PATH $NVM_DIR/versions/node/$NODE_VERSION/bin:$PATH
|
||||
|
||||
# Install Leon
|
||||
WORKDIR /home/docker/leon
|
||||
USER root
|
||||
RUN chown -R docker /home/docker/leon
|
||||
USER docker
|
||||
COPY --chown=docker ./ ./
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
CMD ["npm", "start"]
|
||||
@@ -7,261 +7,177 @@
|
||||
Leon
|
||||
</h1>
|
||||
|
||||
_<p align="center">Your open-source personal assistant.</p>_
|
||||
_<p align="center">Your open-source personal AI assistant.</p>_
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/leon-ai/leon/blob/develop/LICENSE.md"><img src="https://img.shields.io/badge/license-MIT-blue.svg?label=License&style=flat" /></a>
|
||||
<a href="https://github.com/leon-ai/leon/blob/develop/.github/CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" /></a>
|
||||
<br>
|
||||
<a href="https://github.com/leon-ai/leon/actions/workflows/build.yml"><img src="https://github.com/leon-ai/leon/actions/workflows/build.yml/badge.svg?branch=develop" /></a>
|
||||
<a href="https://github.com/leon-ai/leon/actions/workflows/tests.yml"><img src="https://github.com/leon-ai/leon/actions/workflows/tests.yml/badge.svg?branch=develop" /></a>
|
||||
<a href="https://github.com/leon-ai/leon/actions/workflows/lint.yml"><img src="https://github.com/leon-ai/leon/actions/workflows/lint.yml/badge.svg?branch=develop" /></a>
|
||||
<br>
|
||||
<a href="https://discord.gg/MNQqqKg"><img src="https://svgshare.com/i/V09.svg"/></a>
|
||||
<a href="https://discord.gg/MNQqqKg"><img src="https://img.shields.io/badge/Discord-%235865F2.svg?style=for-the-badge&logo=discord&logoColor=white" /></a>
|
||||
<a href="https://github.com/leon-ai/leon/blob/develop/LICENSE.md"><img src="https://img.shields.io/badge/License-MIT-1c75db?style=for-the-badge" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://getleon.ai">Website</a> ::
|
||||
<a href="https://docs.getleon.ai">Documentation</a> ::
|
||||
<a href="http://roadmap.getleon.ai">Roadmap</a> ::
|
||||
<a href="https://github.com/leon-ai/leon/blob/develop/.github/CONTRIBUTING.md">Contributing</a> ::
|
||||
I share Leon progress most regularly on <a href="https://x.com/grenlouis"><strong>X / @grenlouis</strong></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://x.com/grenlouis">Follow progress on X / @grenlouis</a> ·
|
||||
<a href="https://getleon.ai">Website</a> ·
|
||||
<a href="https://leonai.substack.com/subscribe">Newsletter</a> ·
|
||||
<a href="http://roadmap.getleon.ai">Roadmap</a> ·
|
||||
<a href="https://blog.getleon.ai/the-story-behind-leon/">Story</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
## ⚠️ Important Notice (as of 2026-03-29)
|
||||
|
||||
### Why is there a small amount of contributors?
|
||||
> [!IMPORTANT]
|
||||
> Leon is currently focused on the **2.0 Developer Preview** on the `develop` branch.
|
||||
>
|
||||
> - The new documentation is **not ready yet**.
|
||||
> - The current docs site and older guides mostly reflect the legacy architecture.
|
||||
> - If you want the legacy, more stable pre-agentic version of Leon, use the `master` branch.
|
||||
> - If you want to explore or contribute to Leon's new core, `develop` is the right place.
|
||||
|
||||
I'm taking a lot of time to work on the new core of Leon due to personal reasons. I can only work on it after work and on weekends. Hence, **I'm blocking any potential contribution as the whole core of Leon is coming with many breaking changes**. Many of you are willing to contribute in Leon (create new skills, help to improve the core, translations and so on...), a big thanks to every one of you!
|
||||
The most accurate high-level references for Leon's current state are:
|
||||
|
||||
While I would love to devote more time to Leon, I'm currently unable to do so because I have bills to pay. I have some ideas about how to monetize Leon in the future (Leon's core will always remain open source), but before to get there there is still a long way to go.
|
||||
|
||||
Until then, any financial support by [sponsoring Leon](http://sponsor.getleon.ai) is much appreciated 🙂
|
||||
|
||||
### How about large language models and Leon?
|
||||
|
||||
Since AI gained in popularity and large language models are getting more and more traction, many of you joined our community. A huge welcome to all of you! 🤗
|
||||
|
||||
At the moment, Leon's NLU will remain intents first with his own model without relying on an LLM. It is important that Leon can run 100% offline and I'm confident that with the downsizing techniques such as quantization Leon will sooner or later work with LLMs at his core and still be able to run on edge.
|
||||
|
||||
Here is how LLMs may help Leon in the future:
|
||||
|
||||
- Intent fallback: when an utterance cannot match an intent, then rely on an LLM to provide results.
|
||||
- New named entity recognition engine: provide a better solution to extract entities from utterances such as fruits, numbers, cities, durations, persons, etc.
|
||||
- Skill features: let skills leverage LLMs to provide out-of-the-box NLP features such as summarization, knowledge base, translation, sentiment analysis and so on...
|
||||
- Skill building: LLMs can help to develop skills such as paraphrasing utterance samples, translate answers, convert code from our Python bridge to the upcoming JavaScript bridge and vice versa, etc.
|
||||
- More...
|
||||
|
||||
### What's Next?
|
||||
|
||||
Once the new core released, we'll work on the community aspect of Leon. For example, better organize [our Discord](https://discord.gg/MNQqqKg), planify regular calls, work on skills together, etc. It is very important for Leon to have a real community. At that moment, the skills platform will already be online, so it'll be easier to sync our progress and publish new skills.
|
||||
|
||||
- Feel free to check out the Git development branches and our [next major milestones](https://blog.getleon.ai/a-much-better-nlp-and-future-1-0-0-beta-7/#whats-next).
|
||||
- And the [detailed roadmap](http://roadmap.getleon.ai).
|
||||
- Many exciting things are coming up, hence no new documentation and test are going to be written until the official release of Leon.
|
||||
|
||||
---
|
||||
|
||||
## Latest Release
|
||||
|
||||
Check out the [latest release blog post](https://blog.getleon.ai/binaries-and-typescript-rewrite-1-0-0-beta-8/).
|
||||
|
||||
<a href="https://blog.getleon.ai/binaries-and-typescript-rewrite-1-0-0-beta-8/"><img width="400" src="https://blog.getleon.ai/static/a0d1cbafd1968e7531dc17e229f8cc61/aa440/beta-8.png" /></a>
|
||||
|
||||
---
|
||||
- [`core/context/LEON.md`](./core/context/LEON.md)
|
||||
- [`core/context/ARCHITECTURE.md`](./core/context/ARCHITECTURE.md)
|
||||
|
||||
## 👋 Introduction
|
||||
|
||||
**Leon** is an **open-source personal assistant** who can live **on your server**.
|
||||
**Leon** is **your open-source personal AI assistant** built around **tools, context, memory, and agentic execution**.
|
||||
|
||||
He **does stuff** when you **ask him to**.
|
||||
Leon is designed to stay practical, privacy-aware, and grounded in your real environment. It can operate locally, use dedicated tools instead of relying on free-form guessing, and complete tasks from start to finish across deterministic workflows and agent-style execution.
|
||||
|
||||
You can **talk to him** and he can **talk to you**.
|
||||
You can also **text him** and he can also **text you**.
|
||||
If you want to, Leon can communicate with you by being **offline to protect your privacy**.
|
||||
## 🧠 What Leon Is Today
|
||||
|
||||
### Why?
|
||||
Leon is no longer just a classic intent-classification assistant like it was for its first release in 2019.
|
||||
|
||||
> 1. If you are a developer (or not), you may want to build many things that could help in your daily life.
|
||||
> Instead of building a dedicated project for each of those ideas, Leon can help you with his
|
||||
> Skills structure.
|
||||
> 2. With this generic structure, everyone can create their own skills and share them with others.
|
||||
> Therefore there is only one core (to rule them all).
|
||||
> 3. Leon uses AI concepts, which is cool.
|
||||
> 4. Privacy matters, you can configure Leon to talk with him offline. You can already text with him without any third party services.
|
||||
> 5. Open source is great.
|
||||
Today, Leon is being built as a more capable assistant that can understand a goal, choose how to handle it, use tools, remember useful information, and recover when something goes wrong.
|
||||
|
||||
### What is this repository for?
|
||||
- Leon can run in different ways depending on the task: `smart` mode chooses for you, `controlled` mode follows deterministic native skills and actions, and `agent` mode can plan step by step.
|
||||
- Leon supports native skills for controlled actions and agent skills for `SKILL.md`-backed workflows.
|
||||
- Leon can use real tools to get work done instead of only replying with plain text.
|
||||
- Leon can use context about your environment so answers stay grounded in what is actually happening on your machine and setup.
|
||||
- Leon keeps layered memory so it can remember durable preferences, day-to-day context, and recent discussion context.
|
||||
- Leon supports both local and remote AI providers, which helps balance privacy, control, and capability.
|
||||
- Under the hood, Leon-native skills follow `Skills -> Actions -> Tools -> Functions (-> Binaries)`.
|
||||
|
||||
> This repository contains the following nodes of Leon:
|
||||
>
|
||||
> - The server
|
||||
> - Skills
|
||||
> - The web app
|
||||
> - The hotword node
|
||||
> - The TCP server (for inter-process communication between Leon and third-party nodes such as spaCy)
|
||||
> - The Python bridge (the connector between the core and skills made with Python)
|
||||
Leon also keeps a compact self-model and a bounded proactive pulse system so it can stay more consistent over time without flooding itself with unnecessary context.
|
||||
|
||||
### What is Leon able to do?
|
||||
## Why?
|
||||
|
||||
> Today, the most interesting part is about his core and the way he can scale up. He is pretty young but can easily scale to have new features (skills).
|
||||
> You can find what he is able to do by browsing the [skills list](https://github.com/leon-ai/leon/tree/develop/skills).<br>
|
||||
> Please do know that after the official release, we will build many skills along with the community. Feel free to [join us on Discord](https://discord.gg/MNQqqKg) to be part of the journey.
|
||||
|
||||
Sounds good to you? Then let's get started!
|
||||
|
||||
## ☁️ Try with a Single-Click
|
||||
|
||||
Gitpod will automatically set up an environment and run an instance for you.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/leon-ai/leon)
|
||||
- **Privacy matters**: Leon can work with local models and local context instead of forcing everything through third-party services.
|
||||
- **Grounded behavior matters**: Leon prefers explicit tools, context, and memory over vague model-only responses.
|
||||
- **Extensibility matters**: skills, toolkits, bridges, and binaries make it possible to keep Leon modular.
|
||||
- **Open source matters**: anyone can inspect the architecture, build on top of it, and help shape where it goes next.
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) >= 16
|
||||
- [npm](https://npmjs.com/) >= 8
|
||||
- Supported OSes: Linux, macOS and Windows
|
||||
- [Node.js](https://nodejs.org/) >= 24.0.0
|
||||
- Supported OSes: Linux, macOS, and Windows
|
||||
|
||||
To install these prerequisites, you can follow the [How To section](https://docs.getleon.ai/how-to/) of the documentation.
|
||||
Recommended: manage Node.js with [Volta](https://volta.sh/).
|
||||
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
# Install the Leon CLI
|
||||
npm install --global @leon-ai/cli
|
||||
# Clone the repository
|
||||
git clone https://github.com/leon-ai/leon.git
|
||||
|
||||
# Install Leon (stable branch)
|
||||
leon create birth
|
||||
# OR install from the develop branch: leon create birth --develop
|
||||
# Go to the project root
|
||||
cd leon
|
||||
|
||||
# Install pnpm
|
||||
npm install --global pnpm@latest
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Usage
|
||||
### Run Leon
|
||||
|
||||
```sh
|
||||
# Run Leon
|
||||
pnpm start
|
||||
```
|
||||
|
||||
### Check Your Setup
|
||||
|
||||
```sh
|
||||
# Check the setup went well
|
||||
leon check
|
||||
|
||||
# Run
|
||||
leon start
|
||||
|
||||
# Go to http://localhost:1337
|
||||
# Hooray! Leon is running
|
||||
pnpm run check
|
||||
```
|
||||
|
||||
### Docker Installation
|
||||
By default, Leon runs locally and the app is available on `http://localhost:5366`.
|
||||
|
||||
```sh
|
||||
# Install Leon
|
||||
leon create birth --docker
|
||||
## 🏗️ Architecture Snapshot
|
||||
|
||||
# Run
|
||||
leon start
|
||||
At a high level, Leon currently consists of:
|
||||
|
||||
# Go to http://localhost:1337
|
||||
# Hooray! Leon is running
|
||||
```
|
||||
- `server/`: the main runtime, routing, memory, context management, HTTP API, and agent/controlled execution
|
||||
- `app/`: the web application
|
||||
- `aurora/`: UI components and preview environment
|
||||
- `skills/`: built-in capabilities, split between `native/` skills and `agent/` skills
|
||||
- `bridges/`: Node.js and Python bridges plus toolkit definitions and tool runtimes
|
||||
- `tcp_server/`: Python services used by parts of the runtime stack
|
||||
- `core/context/`: generated identity and architecture context documents that describe Leon's current behavior
|
||||
|
||||
## 📚 Documentation
|
||||
This repository already includes skills and toolkits for areas such as search, productivity, system utilities, media workflows, coding assistance, memory-backed interactions, and voice/audio features.
|
||||
|
||||
For full documentation, visit [docs.getleon.ai](https://docs.getleon.ai).
|
||||
## 📚 Documentation Status
|
||||
|
||||
## 📺 Video
|
||||
The new docs for Leon 2.0 are not ready yet.
|
||||
|
||||
[Watch a demo](https://www.youtube.com/watch?v=p7GRGiicO1c).
|
||||
For now:
|
||||
|
||||
## 🧭 Roadmap
|
||||
|
||||
To know what is going on, follow [roadmap.getleon.ai](http://roadmap.getleon.ai).
|
||||
- treat this repository as the source of truth for the **2.0 Developer Preview**
|
||||
- use [`core/context/LEON.md`](./core/context/LEON.md) for Leon's current identity and behavior
|
||||
- use [`core/context/ARCHITECTURE.md`](./core/context/ARCHITECTURE.md) for the current architecture overview
|
||||
- expect the public docs site to lag behind the new core until the updated documentation is published
|
||||
|
||||
## ❤️ Contributing
|
||||
|
||||
If you have an idea for improving Leon, do not hesitate.
|
||||
We are starting to progressively onboard contributors for the **2.0 Developer Preview**.
|
||||
|
||||
**Leon needs open source to live**, the more skills he has, the more skillful he becomes.
|
||||
If you want to follow the project or express interest in joining that onboarding:
|
||||
|
||||
- [2.0 Developer Preview contributor form](https://forms.gle/6PCG2D5rYo1q8tKMA)
|
||||
- [Roadmap](http://roadmap.getleon.ai)
|
||||
- [Discord](https://discord.gg/MNQqqKg)
|
||||
- [GitHub issues](https://github.com/leon-ai/leon/issues)
|
||||
|
||||
### Why is there a small amount of contributors?
|
||||
|
||||
Leon has been evolving for a long time, but the current 2.0 work is a major transition period.
|
||||
|
||||
For a long time, Leon was a smaller assistant project with a simpler architecture. Today, the core is being rebuilt into a much more capable system around tools, memory, context, and agent-style execution. That means a lot of things are still moving, and it makes contribution harder than it will be once the new docs and architecture settle down.
|
||||
|
||||
Another important reason is simply time: Leon is still developed largely during spare time. So progress can be uneven, and opening the project more broadly has to be balanced with keeping the direction coherent while the 2.0 Developer Preview is still taking shape.
|
||||
|
||||
## 📖 The Story Behind Leon
|
||||
|
||||
You'll find a write-up on this [blog post](https://blog.getleon.ai/the-story-behind-leon/).
|
||||
Leon started in 2017 and has been active since 2019. If you want the longer backstory, read [the story behind Leon](https://blog.getleon.ai/the-story-behind-leon/).
|
||||
|
||||
## 🔔 Stay Tuned
|
||||
|
||||
- [Twitter](https://twitter.com/grenlouis)
|
||||
- [Newsletter](https://newsletter.getleon.ai/subscription/form)
|
||||
- [X / Twitter](https://x.com/grenlouis) is the main place where I share Leon progress updates
|
||||
- [Newsletter](https://leonai.substack.com/subscribe)
|
||||
- [Blog](https://blog.getleon.ai)
|
||||
- [GitHub issues](https://github.com/leon-ai/leon/issues)
|
||||
- [YouTube](https://www.youtube.com/channel/UCW6mk6j6nQUzFYY97r47emQ)
|
||||
- [#LeonAI](<https://twitter.com/search?f=live&q=%23LeonAI%20(from%3Agrenlouis%20OR%20from%3Alouistiti_fr)&src=typed_query>)
|
||||
|
||||
## 👨 Author
|
||||
|
||||
**Louis Grenard** ([@grenlouis](https://twitter.com/grenlouis))
|
||||
**Louis Grenard** ([@grenlouis](https://x.com/grenlouis))
|
||||
|
||||
## 👍 Sponsors
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle" width="128">
|
||||
<a href="https://github.com/Appwrite">
|
||||
<img src="https://github.com/Appwrite.png?size=128" />
|
||||
Appwrite
|
||||
</a><br>
|
||||
<sub><sup>250 USD / month</sup></sub>
|
||||
</td>
|
||||
<td align="center" valign="middle" width="128">
|
||||
<img src="https://getleon.ai/img/anonymous.svg" width="128" />
|
||||
Anonymous
|
||||
<br>
|
||||
<sub><sup>100 USD / month</sup></sub>
|
||||
</td>
|
||||
<td align="center" valign="middle" width="128">
|
||||
<a href="https://github.com/herbundkraut">
|
||||
<img src="https://github.com/herbundkraut.png?size=128" />
|
||||
herbundkraut
|
||||
</a><br>
|
||||
<sub><sup>10 USD / month</sup></sub>
|
||||
</td>
|
||||
<td align="center" valign="middle" width="128">
|
||||
<a href="http://sponsor.getleon.ai/">
|
||||
You?
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
You can also contribute by [sponsoring Leon](http://sponsor.getleon.ai).
|
||||
|
||||
Please note that I dedicate most of my free time to Leon.
|
||||
## Thanks
|
||||
|
||||
By sponsoring the project you make the project sustainable and faster to develop features.
|
||||
|
||||
The focus is not only limited to the activity you see on GitHub but also a lot of thinking about the direction of the project. Which is naturally related to the overall design, architecture, vision, learning process and so on...
|
||||
|
||||
### Special Thanks
|
||||
|
||||
<a href="https://vercel.com/?utm_source=leon-ai&utm_campaign=oss">
|
||||
<img src="https://i.imgur.com/S5olXWh.png" alt="Vercel" width="128" />
|
||||
</a>
|
||||
|
||||
<a href="https://www.macstadium.com/">
|
||||
<img src="https://getleon.ai/img/thanks/mac-stadium.svg" alt="MacStadium" width="128" />
|
||||
</a>
|
||||
|
||||
<a href="https://www.aoz.studio">
|
||||
<img src="https://getleon.ai/_next/image?url=%2Fimg%2Fthanks%2Faoz-studio.png&w=384&q=75" alt="AOZ Studio" width="128" />
|
||||
</a>
|
||||
|
||||
## 📝 License
|
||||
|
||||
[MIT License](https://github.com/leon-ai/leon/blob/develop/LICENSE.md)
|
||||
|
||||
Copyright (c) 2019-present, Louis Grenard <louis@getleon.ai>
|
||||
|
||||
## Cheers!
|
||||
|
||||

|
||||
|  |  |  |
|
||||
| --- | --- | --- |
|
||||
| [openai.com/form/codex-for-oss](https://openai.com/form/codex-for-oss/) | [jb.gg/OpenSource](https://jb.gg/OpenSource) | [macstadium.com/company/opensource](https://macstadium.com/company/opensource) |
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
.built-in-commands-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 120;
|
||||
display: block;
|
||||
opacity: 0;
|
||||
visibility: visible;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 0.18s ease,
|
||||
visibility 0.18s ease;
|
||||
}
|
||||
|
||||
.built-in-commands-modal--hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.built-in-commands-modal--open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.56);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.built-in-commands-modal__panel {
|
||||
position: absolute;
|
||||
top: 33%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -33%) scale(0.985);
|
||||
width: min(864px, 100%);
|
||||
min-height: 400px;
|
||||
max-height: min(78vh, 756px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 24px;
|
||||
background:
|
||||
linear-gradient(
|
||||
var(--a-color-background-surface),
|
||||
var(--a-color-background-surface)
|
||||
)
|
||||
padding-box,
|
||||
linear-gradient(135deg, rgba(28, 117, 219, 0.8) 0%, rgba(237, 41, 122, 0.4) 100%)
|
||||
border-box;
|
||||
box-shadow:
|
||||
0 28px 60px rgba(0, 0, 0, 0.45),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.03) inset;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transition:
|
||||
transform 0.18s cubic-bezier(0.18, 0.89, 0.32, 1.08),
|
||||
opacity 0.18s ease;
|
||||
}
|
||||
|
||||
.built-in-commands-modal--open .built-in-commands-modal__panel {
|
||||
transform: translate(-50%, -33%) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__section {
|
||||
padding: 20px 22px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__section--top {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.built-in-commands-modal__section--bottom {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.built-in-commands-modal__section--middle {
|
||||
flex: 0 0 512px;
|
||||
height: 512px;
|
||||
min-height: 512px;
|
||||
padding: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__scroll-area {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__input-field {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__input-field .aurora-input-container {
|
||||
--a-icon-container-width: 72px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__input-field .aurora-input {
|
||||
height: 72px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__input-field .aurora-input-icon-container {
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__return-button .aurora-button {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
padding: 0 14px;
|
||||
font-size: var(--a-font-size-md);
|
||||
}
|
||||
|
||||
.built-in-commands-modal__return-button .aurora-button .aurora-icon {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__suggestion-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.built-in-commands-modal .aurora-list-item > .aurora-flexbox .aurora-icon {
|
||||
font-size: 28px;
|
||||
padding: 23px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal
|
||||
.aurora-list-item--selected
|
||||
.aurora-list-item-clickable-icon
|
||||
.aurora-icon {
|
||||
color: var(--a-color-accent);
|
||||
}
|
||||
|
||||
.built-in-commands-modal__result-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__result-copy,
|
||||
.built-in-commands-modal__result-value {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__result-value {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.built-in-commands-modal .aurora-scroll-container-scrollview {
|
||||
color-scheme: dark;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.2) rgba(10, 14, 18, 0.9);
|
||||
}
|
||||
|
||||
.built-in-commands-modal .aurora-scroll-container-scrollview::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal
|
||||
.aurora-scroll-container-scrollview::-webkit-scrollbar-track {
|
||||
background: rgba(10, 14, 18, 0.9);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal
|
||||
.aurora-scroll-container-scrollview::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(54, 65, 76, 0.95) 0%,
|
||||
rgba(30, 38, 46, 0.95) 100%
|
||||
);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal
|
||||
.aurora-scroll-container-scrollview::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(70, 82, 95, 0.98) 0%,
|
||||
rgba(40, 49, 58, 0.98) 100%
|
||||
);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.built-in-commands-modal__panel {
|
||||
width: auto;
|
||||
min-height: 360px;
|
||||
max-height: calc(100vh - 24px);
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
top: 18%;
|
||||
transform: translate(0, 0) scale(0.985);
|
||||
}
|
||||
|
||||
.built-in-commands-modal--open .built-in-commands-modal__panel {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
|
||||
.built-in-commands-modal__section {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__section--middle {
|
||||
flex-basis: 280px;
|
||||
height: 280px;
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.built-in-commands-modal__result-item {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
.file-system-autocomplete {
|
||||
position: fixed;
|
||||
z-index: 150;
|
||||
overflow-y: auto;
|
||||
padding: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 8px;
|
||||
background: #111315;
|
||||
box-shadow:
|
||||
0 18px 42px rgba(0, 0, 0, 0.42),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.03) inset;
|
||||
color: var(--white-color);
|
||||
}
|
||||
|
||||
.file-system-autocomplete--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-system-autocomplete__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
padding: 9px 10px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
line-height: 1.35;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.file-system-autocomplete__item:hover,
|
||||
.file-system-autocomplete__item--selected {
|
||||
background: rgba(28, 117, 219, 0.22);
|
||||
}
|
||||
|
||||
.file-system-autocomplete__item i {
|
||||
flex: 0 0 auto;
|
||||
width: 18px;
|
||||
color: var(--blue-color);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.file-system-autocomplete__label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
line-height: 1.35;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-system-autocomplete__empty {
|
||||
padding: 10px;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.file-system-autocomplete::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.file-system-autocomplete::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
}
|
||||
@@ -1,415 +0,0 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800);
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframes,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
sub,
|
||||
sup,
|
||||
tt,
|
||||
var,
|
||||
u,
|
||||
i,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before,
|
||||
blockquote:after,
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
--black-color: #000;
|
||||
--light-black-color: #222426;
|
||||
--white-color: #fff;
|
||||
--grey-color: #323739;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--white-color);
|
||||
background-color: var(--black-color);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
body > * {
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
body.settingup > * {
|
||||
opacity: 0;
|
||||
}
|
||||
body.settingup::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: #777;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
animation: scaleout 0.6s infinite ease-in-out;
|
||||
}
|
||||
@keyframes scaleout {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
position: absolute;
|
||||
width: 63%;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
height: 100%;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
color: var(--white-color);
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-bottom: 2px solid var(--grey-color);
|
||||
background: none;
|
||||
font-weight: 400;
|
||||
font-size: 4em;
|
||||
padding-right: 39px;
|
||||
}
|
||||
|
||||
small {
|
||||
color: var(--white-color);
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#logo {
|
||||
background: no-repeat url(../img/logo.svg);
|
||||
margin: 0 auto;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#feed {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 10%;
|
||||
height: 50%;
|
||||
overflow-y: auto;
|
||||
border: 2px solid var(--grey-color);
|
||||
border-radius: 12px;
|
||||
}
|
||||
#feed::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
#feed::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
#no-bubble {
|
||||
margin-top: 64px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#is-typing {
|
||||
position: absolute;
|
||||
top: 59%;
|
||||
padding: 0 8px;
|
||||
opacity: 0;
|
||||
margin-top: 20px;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
#is-typing.on {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#is-typing .circle {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: var(--white-color);
|
||||
transform: scale(1);
|
||||
}
|
||||
#is-typing .circle:nth-child(1) {
|
||||
animation: typing 0.2s linear infinite alternate;
|
||||
background-color: #0071f0;
|
||||
}
|
||||
#is-typing .circle:nth-child(2) {
|
||||
animation: typing 0.2s 0.2s linear infinite alternate;
|
||||
background-color: var(--white-color);
|
||||
}
|
||||
#is-typing .circle:nth-child(3) {
|
||||
animation: typing 0.2s linear infinite alternate;
|
||||
background-color: #ec297a;
|
||||
}
|
||||
@keyframes typing {
|
||||
100% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
.bubble-container {
|
||||
padding: 6px;
|
||||
}
|
||||
.bubble-container.me {
|
||||
text-align: right;
|
||||
}
|
||||
.bubble-container.leon {
|
||||
text-align: left;
|
||||
}
|
||||
.show-more {
|
||||
margin: 3px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.show-more:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.show-all {
|
||||
max-height: 100% !important;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
padding: 6px 12px;
|
||||
border-radius: 16px;
|
||||
display: inline-block;
|
||||
max-width: 60%;
|
||||
word-break: break-word;
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.2s ease-in forwards;
|
||||
overflow: hidden;
|
||||
}
|
||||
#feed .me .bubble {
|
||||
background-color: #1c75db;
|
||||
color: var(--white-color);
|
||||
right: 0;
|
||||
}
|
||||
#feed .leon .bubble {
|
||||
background-color: var(--light-black-color);
|
||||
color: var(--white-color);
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#suggestions-container {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
bottom: 36%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
column-gap: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.suggestion {
|
||||
border: 1px solid var(--white-color);
|
||||
background-color: transparent;
|
||||
color: var(--white-color);
|
||||
border-radius: 8px;
|
||||
padding: 2px 8px;
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
.suggestion:hover {
|
||||
color: var(--black-color);
|
||||
background-color: var(--white-color);
|
||||
}
|
||||
|
||||
#input-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 22%;
|
||||
}
|
||||
|
||||
#mic-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-top: 38px;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#mic-button {
|
||||
position: absolute;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
border-radius: 50%;
|
||||
background-color: #888;
|
||||
-webkit-mask-image: url(../img/mic.svg);
|
||||
mask-image: url(../img/mic.svg);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
#mic-button:not(.enabled) {
|
||||
margin-left: -26px;
|
||||
}
|
||||
#mic-button:hover {
|
||||
background-color: var(--white-color);
|
||||
}
|
||||
#mic-button.enabled {
|
||||
background-color: #00e676;
|
||||
}
|
||||
#mic-button.enabled + #sonar {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
opacity: 0.3;
|
||||
background-color: #575757;
|
||||
pointer-events: none;
|
||||
animation: sonar 1.3s linear infinite;
|
||||
}
|
||||
@keyframes sonar {
|
||||
25% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
60% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
75% {
|
||||
transform: scale(2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,860 @@
|
||||
@import '@fontsource/source-sans-pro/200.css';
|
||||
@import '@fontsource/source-sans-pro/300.css';
|
||||
@import '@fontsource/source-sans-pro/400.css';
|
||||
@import '@fontsource/source-sans-pro/600.css';
|
||||
@import '@fontsource/source-sans-pro/700.css';
|
||||
@import '@fontsource/source-sans-pro/900.css';
|
||||
@import 'remixicon/fonts/remixicon.css';
|
||||
@import 'tool-ui.scss';
|
||||
@import 'voice-energy/main.scss';
|
||||
@import 'file-system-autocomplete.scss';
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframes,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
sub,
|
||||
sup,
|
||||
tt,
|
||||
var,
|
||||
u,
|
||||
i,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before,
|
||||
blockquote:after,
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
--black-color: #000;
|
||||
--light-black-color: #222426;
|
||||
--white-color: #fff;
|
||||
--grey-color: #323739;
|
||||
--blue-color: #1c75db;
|
||||
--pink-color: #ed297a;
|
||||
--sessions-panel-width: 292px;
|
||||
|
||||
--a-loader-size-md: 20px !important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#feed ul li:not(.aurora-list-item) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--white-color);
|
||||
background-color: var(--black-color);
|
||||
font-family:
|
||||
'Source Sans Pro',
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
Arial,
|
||||
'Noto Sans',
|
||||
sans-serif,
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
|
||||
@keyframes plan-tool-status-cozy-shine {
|
||||
0% {
|
||||
opacity: 0.4;
|
||||
filter: drop-shadow(0 0 0 rgba(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.22));
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
filter: drop-shadow(0 0 0 rgba(255, 255, 255, 0));
|
||||
}
|
||||
}
|
||||
|
||||
#feed
|
||||
.bubble-container[data-message-id^='plan-']
|
||||
.aurora-list
|
||||
.aurora-list-item--center:last-child
|
||||
.aurora-status
|
||||
.aurora-icon {
|
||||
animation: plan-tool-status-cozy-shine 2.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
body > * {
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
body.settingup > *:not(#init) {
|
||||
opacity: 0;
|
||||
}
|
||||
#init .not-initialized {
|
||||
visibility: hidden;
|
||||
}
|
||||
#init .initialized {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
kbd {
|
||||
font-family: 'Source Sans Pro', monospace;
|
||||
display: inline-block;
|
||||
background-color: var(--light-black-color);
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
line-height: 16px !important;
|
||||
padding: 2px 6px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#sessions-panel {
|
||||
position: absolute;
|
||||
left: 2%;
|
||||
top: 2%;
|
||||
bottom: 8%;
|
||||
width: var(--sessions-panel-width);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.12);
|
||||
color: var(--white-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#sessions-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 14px 14px 0;
|
||||
}
|
||||
|
||||
#sessions-eyebrow {
|
||||
color: rgba(255, 255, 255, 0.42);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#new-session,
|
||||
.session-action {
|
||||
border: 0;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: var(--white-color);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.18s,
|
||||
color 0.18s,
|
||||
transform 0.18s;
|
||||
}
|
||||
|
||||
#new-session {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
#new-session:hover,
|
||||
.session-action:hover {
|
||||
background: rgba(28, 117, 219, 0.22);
|
||||
color: #86bdff;
|
||||
}
|
||||
|
||||
#sessions-list {
|
||||
height: calc(100% - 48px);
|
||||
padding-right: 14px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#sessions-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
#sessions-list::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.16);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.session-group {
|
||||
position: relative;
|
||||
padding: 0 0 12px 14px;
|
||||
}
|
||||
|
||||
.session-group::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
top: 22px;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background: linear-gradient(
|
||||
rgba(28, 117, 219, 0.7),
|
||||
rgba(237, 41, 122, 0.22)
|
||||
);
|
||||
}
|
||||
|
||||
.session-group-title {
|
||||
position: relative;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(255, 255, 255, 0.44);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.session-group-title::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -14px;
|
||||
top: 7px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--blue-color);
|
||||
box-shadow: 0 0 12px rgba(28, 117, 219, 0.8);
|
||||
}
|
||||
|
||||
.session-item {
|
||||
position: relative;
|
||||
padding: 10px 8px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
animation: sessionTimelineIn 0.24s ease-out both;
|
||||
}
|
||||
|
||||
.session-item:hover,
|
||||
.session-item--active {
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
|
||||
.session-item--active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -11px;
|
||||
top: 18px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: var(--pink-color);
|
||||
box-shadow: 0 0 10px rgba(237, 41, 122, 0.9);
|
||||
}
|
||||
|
||||
.session-title {
|
||||
overflow: hidden;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 0.98rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.session-meta {
|
||||
overflow: hidden;
|
||||
margin-top: 2px;
|
||||
color: rgba(255, 255, 255, 0.36);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.session-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.16s;
|
||||
}
|
||||
|
||||
.session-item:hover .session-actions,
|
||||
.session-item--active .session-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.session-action {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.session-action[aria-pressed='true'] {
|
||||
color: #ff7caf;
|
||||
}
|
||||
|
||||
@keyframes sessionTimelineIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
position: absolute;
|
||||
width: calc(94% - var(--sessions-panel-width));
|
||||
top: 0;
|
||||
left: calc(4% + var(--sessions-panel-width));
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#footer {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
left: calc(4% + var(--sessions-panel-width));
|
||||
bottom: 2%;
|
||||
width: calc(94% - var(--sessions-panel-width));
|
||||
line-height: 18px;
|
||||
}
|
||||
#logo-container {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
#version {
|
||||
color: var(--grey-color);
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
text-align: center;
|
||||
color: var(--white-color);
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-bottom: 2px solid var(--grey-color);
|
||||
background: none;
|
||||
font-weight: 600;
|
||||
font-size: 4em;
|
||||
padding-right: 39px;
|
||||
height: 140px;
|
||||
resize: none;
|
||||
overflow-y: auto;
|
||||
}
|
||||
textarea::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
textarea::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
#tip {
|
||||
display: inline-flex;
|
||||
margin-top: 2px;
|
||||
color: var(--white-color);
|
||||
line-height: 22px;
|
||||
font-size: 0.9em;
|
||||
gap: 20px;
|
||||
li {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#logo {
|
||||
background: no-repeat url(../img/logo.svg);
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
#top-container {
|
||||
position: absolute;
|
||||
top: 2%;
|
||||
color: var(--grey-color);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#mood {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
}
|
||||
#info {
|
||||
position: relative;
|
||||
text-decoration: underline;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--grey-color);
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#feed {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 6%;
|
||||
height: 58%;
|
||||
overflow-y: auto;
|
||||
border: 2px solid var(--grey-color);
|
||||
border-radius: 12px;
|
||||
}
|
||||
#feed::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
#feed::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
#no-bubble {
|
||||
margin-top: 64px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#is-typing {
|
||||
position: absolute;
|
||||
top: 65%;
|
||||
padding: 0 8px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
#is-typing.on {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#is-typing .circle {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: var(--white-color);
|
||||
transform: scale(1);
|
||||
}
|
||||
#is-typing .circle:nth-child(1) {
|
||||
animation: typing 0.2s linear infinite alternate;
|
||||
background-color: #0071f0;
|
||||
}
|
||||
#is-typing .circle:nth-child(2) {
|
||||
animation: typing 0.2s 0.2s linear infinite alternate;
|
||||
background-color: var(--white-color);
|
||||
}
|
||||
#is-typing .circle:nth-child(3) {
|
||||
animation: typing 0.2s linear infinite alternate;
|
||||
background-color: #ec297a;
|
||||
}
|
||||
@keyframes typing {
|
||||
100% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
.llm-token {
|
||||
opacity: 0;
|
||||
transition: opacity 2.5s;
|
||||
}
|
||||
|
||||
.llm-token.fade-in {
|
||||
animation: fadeIn 2.5s forwards;
|
||||
}
|
||||
|
||||
.reasoning-block-container {
|
||||
padding: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.reasoning-block {
|
||||
max-width: 72%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
border-radius: 12px;
|
||||
background-color: #111315;
|
||||
overflow: hidden;
|
||||
animation: fadeIn 0.2s ease-in forwards;
|
||||
}
|
||||
|
||||
.reasoning-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.reasoning-icon {
|
||||
color: var(--blue-color);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.reasoning-title {
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.reasoning-content {
|
||||
padding: 10px 12px;
|
||||
font-family: 'Courier New', Consolas, monospace;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.45;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 296px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.reasoning-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.reasoning-content::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.reasoning-token {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.reasoning-token.fade-in {
|
||||
animation: fadeIn 0.35s forwards;
|
||||
}
|
||||
|
||||
.bubble-container {
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: fit-content;
|
||||
max-width: 85%;
|
||||
}
|
||||
.bubble-container.me {
|
||||
margin-left: auto;
|
||||
align-items: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
.bubble-container.leon {
|
||||
margin-right: auto;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
.show-more {
|
||||
margin: 3px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.show-more:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.show-all {
|
||||
max-height: 100% !important;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
padding: 10px 16px;
|
||||
border-radius: 16px;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
word-break: break-word;
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.2s ease-in forwards;
|
||||
overflow: hidden;
|
||||
font-size: 1.8rem;
|
||||
line-height: 2.4rem;
|
||||
}
|
||||
.bubble-metrics {
|
||||
cursor: default;
|
||||
margin-top: 2px;
|
||||
padding: 0 4px;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.2rem;
|
||||
color: var(--grey-color);
|
||||
align-self: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
.bubble-metric-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.bubble-metric-item i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
#feed .me .bubble-metrics {
|
||||
text-align: right;
|
||||
}
|
||||
#feed .me .bubble {
|
||||
background-color: #1c75db;
|
||||
color: var(--white-color);
|
||||
right: 0;
|
||||
}
|
||||
#feed .leon .bubble {
|
||||
background-color: var(--light-black-color);
|
||||
color: var(--white-color);
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#input-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 13%;
|
||||
}
|
||||
|
||||
#mic-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-top: 38px;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#mic-button {
|
||||
position: absolute;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
border-radius: 50%;
|
||||
background-color: #888;
|
||||
-webkit-mask-image: url(../img/mic.svg);
|
||||
mask-image: url(../img/mic.svg);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
#mic-button:not(.enabled) {
|
||||
margin-left: -26px;
|
||||
}
|
||||
#mic-button:hover {
|
||||
background-color: var(--white-color);
|
||||
}
|
||||
#mic-button.enabled {
|
||||
background-color: #00e676;
|
||||
}
|
||||
#mic-button.enabled + #sonar {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
opacity: 0.3;
|
||||
background-color: #575757;
|
||||
pointer-events: none;
|
||||
animation: sonar 1.3s linear infinite;
|
||||
}
|
||||
@keyframes sonar {
|
||||
25% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
60% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
75% {
|
||||
transform: scale(2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Clickable URL styles */
|
||||
.clickable-url {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Clickable file path styles */
|
||||
.clickable-path {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 940px) {
|
||||
:root {
|
||||
--sessions-panel-width: 100%;
|
||||
}
|
||||
|
||||
#sessions-panel {
|
||||
left: 4%;
|
||||
right: 4%;
|
||||
top: 1%;
|
||||
bottom: auto;
|
||||
width: auto;
|
||||
height: 168px;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
#sessions-list {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
height: 112px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.session-group {
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
main {
|
||||
top: 176px;
|
||||
left: 4%;
|
||||
width: 92%;
|
||||
height: calc(100% - 176px);
|
||||
}
|
||||
|
||||
#footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#top-container {
|
||||
top: 1%;
|
||||
}
|
||||
|
||||
#feed {
|
||||
top: 7%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
#is-typing {
|
||||
top: 58%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-size: 2.4em;
|
||||
}
|
||||
|
||||
#tip {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
/* Tool Output Container Styles */
|
||||
.tool-group-container {
|
||||
margin: 10px 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 14px;
|
||||
background:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.04) 0%,
|
||||
rgba(255, 255, 255, 0.02) 100%
|
||||
),
|
||||
#15181a;
|
||||
box-shadow:
|
||||
0 10px 30px rgba(0, 0, 0, 0.24),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.3s ease-in forwards;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tool-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.tool-header:hover {
|
||||
background-color: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.tool-icon {
|
||||
color: var(--blue-color);
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tool-name {
|
||||
flex: 1;
|
||||
font-weight: 600;
|
||||
color: var(--white-color);
|
||||
font-size: 0.95em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
color: rgba(255, 255, 255, 0.48);
|
||||
font-size: 18px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.expand-icon.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.tool-content {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease, border-top-color 0.2s ease;
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
|
||||
.tool-content.expanded {
|
||||
max-height: 900px;
|
||||
overflow-y: auto;
|
||||
border-top-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.tool-content::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.tool-content::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.shell-output {
|
||||
padding: 12px;
|
||||
background-color: #0d1117;
|
||||
border-radius: 0 0 14px 14px;
|
||||
min-height: 40px;
|
||||
font-family: 'Courier New', Consolas, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.shell-message {
|
||||
margin: 2px 0;
|
||||
line-height: 1.4;
|
||||
color: #e6edf3;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.shell-prompt {
|
||||
color: var(--pink-color);
|
||||
font-weight: bold;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.shell-message .clickable-path {
|
||||
color: var(--blue-color);
|
||||
background-color: rgba(28, 117, 219, 0.1);
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid rgba(28, 117, 219, 0.3);
|
||||
}
|
||||
|
||||
.shell-message .clickable-url {
|
||||
color: var(--blue-color);
|
||||
}
|
||||
|
||||
.tool-activity-card {
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at top right,
|
||||
rgba(28, 117, 219, 0.14),
|
||||
transparent 32%
|
||||
),
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.04) 0%,
|
||||
rgba(255, 255, 255, 0.02) 100%
|
||||
),
|
||||
#15181a;
|
||||
}
|
||||
|
||||
.agent-skill-activity-card {
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at top right,
|
||||
rgba(237, 41, 122, 0.14),
|
||||
transparent 32%
|
||||
),
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.04) 0%,
|
||||
rgba(255, 255, 255, 0.02) 100%
|
||||
),
|
||||
#15181a;
|
||||
}
|
||||
|
||||
.agent-skill-activity-card .tool-icon {
|
||||
color: #ed297a;
|
||||
}
|
||||
|
||||
.tool-activity-header {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.tool-activity-heading {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tool-activity-title-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.tool-title {
|
||||
color: var(--white-color);
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tool-subtitle {
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tool-activity-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tool-status-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 72px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid transparent;
|
||||
font-size: 0.76rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.tool-status-chip.running {
|
||||
color: #9fd2ff;
|
||||
background-color: rgba(28, 117, 219, 0.14);
|
||||
border-color: rgba(28, 117, 219, 0.28);
|
||||
}
|
||||
|
||||
.tool-status-chip.success {
|
||||
color: #93f0c1;
|
||||
background-color: rgba(36, 178, 101, 0.15);
|
||||
border-color: rgba(36, 178, 101, 0.28);
|
||||
}
|
||||
|
||||
.tool-status-chip.selected {
|
||||
color: #93f0c1;
|
||||
background-color: rgba(36, 178, 101, 0.15);
|
||||
border-color: rgba(36, 178, 101, 0.28);
|
||||
}
|
||||
|
||||
.tool-status-chip.observed {
|
||||
color: #f5d48a;
|
||||
background-color: rgba(210, 157, 38, 0.15);
|
||||
border-color: rgba(210, 157, 38, 0.3);
|
||||
}
|
||||
|
||||
.tool-status-chip.error {
|
||||
color: #ffb0b0;
|
||||
background-color: rgba(237, 41, 122, 0.14);
|
||||
border-color: rgba(237, 41, 122, 0.28);
|
||||
}
|
||||
|
||||
.tool-activity-summary {
|
||||
padding: 16px 16px 4px;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.tool-activity-sections {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
padding: 12px 16px 16px;
|
||||
}
|
||||
|
||||
.tool-activity-sections.single {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tool-activity-panel {
|
||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||
border-radius: 12px;
|
||||
background-color: rgba(255, 255, 255, 0.025);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tool-panel-heading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 12px 14px 10px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.tool-panel-title {
|
||||
color: var(--white-color);
|
||||
font-size: 0.84rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.tool-panel-description {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.tool-panel-body {
|
||||
padding: 12px 14px 14px;
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
.tool-output-summary {
|
||||
margin-bottom: 10px;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.tool-empty-state {
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tool-kv-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tool-kv-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tool-kv-key {
|
||||
color: rgba(255, 255, 255, 0.52);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.tool-kv-value {
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tool-rich-text {
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
line-height: 1.45;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tool-command-output {
|
||||
margin: 0;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
font-family: 'Courier New', Consolas, monospace;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.45;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tool-rich-text .clickable-path {
|
||||
color: var(--blue-color);
|
||||
background-color: rgba(28, 117, 219, 0.1);
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid rgba(28, 117, 219, 0.3);
|
||||
}
|
||||
|
||||
.tool-rich-text .clickable-url {
|
||||
color: var(--blue-color);
|
||||
}
|
||||
|
||||
.tool-value-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-left: 18px;
|
||||
}
|
||||
|
||||
.tool-value-list li {
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tool-raw-details {
|
||||
margin: 0 16px 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.tool-raw-details summary {
|
||||
cursor: pointer;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tool-raw-details summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tool-raw-content {
|
||||
margin-top: 12px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
font-family: 'Courier New', Consolas, monospace;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.45;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.tool-activity-sections {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tool-activity-header {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tool-activity-meta {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.tool-status-chip {
|
||||
min-width: 64px;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Overlay and containers
|
||||
*/
|
||||
|
||||
body.voice-mode-enabled {
|
||||
#voice-overlay-transitor,
|
||||
#voice-overlay-bg {
|
||||
visibility: visible;
|
||||
}
|
||||
#voice-overlay-bg {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
#voice-overlay-transitor {
|
||||
position: fixed;
|
||||
background-color: var(--black-color);
|
||||
z-index: 10;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
will-change: transform;
|
||||
animation: scaleIn 1s;
|
||||
}
|
||||
@keyframes scaleIn {
|
||||
0% {
|
||||
transform: scale3d(0, 0, 1);
|
||||
}
|
||||
100% {
|
||||
transform: scale3d(172, 172, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#voice-status,
|
||||
#voice-tips {
|
||||
color: var(--grey-color);
|
||||
text-align: center;
|
||||
}
|
||||
#voice-status {
|
||||
font-size: 17px;
|
||||
font-style: italic;
|
||||
}
|
||||
#voice-tips {
|
||||
margin-top: 32px;
|
||||
line-height: 18px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#voice-overlay-bg {
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 100;
|
||||
will-change: opacity;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
// backdrop-filter: saturate(140%) blur(5px);
|
||||
/*background-color: rgba(0, 0, 0, .9);*/
|
||||
background-color: var(--black-color);
|
||||
}
|
||||
@keyframes skipFadeIn {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#voice-container {
|
||||
opacity: 0;
|
||||
position: relative;
|
||||
top: 64px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 1024px;
|
||||
height: 756px;
|
||||
align-items: center;
|
||||
gap: 64px;
|
||||
animation: fadeIn 1s 3s both;
|
||||
}
|
||||
|
||||
#voice-energy-container {
|
||||
--neon-size: 228px;
|
||||
|
||||
// animation: fadeIn 1s 1.5s both;
|
||||
//opacity: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 400px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
p#voice-speech {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/**
|
||||
* Neons
|
||||
*/
|
||||
|
||||
.voice-neon {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
width: var(--neon-size);
|
||||
height: var(--neon-size);
|
||||
}
|
||||
|
||||
#purple-neon-blur {
|
||||
--neon-blur: calc(var(--neon-size) + 96px);
|
||||
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
opacity: 0.7;
|
||||
width: var(--neon-blur);
|
||||
height: var(--neon-blur);
|
||||
}
|
||||
|
||||
#blue-neon-1 {
|
||||
margin-top: -8px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
#blue-neon-2 {
|
||||
margin-top: 8px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Particles
|
||||
*/
|
||||
|
||||
.voice-particle {
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
opacity: 1;
|
||||
will-change: transform, opacity;
|
||||
animation-duration: 1s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
.voice-particle.blue {
|
||||
background-color: #c4e0ff;
|
||||
box-shadow: 0 0 2px 2px var(--blue-color);
|
||||
}
|
||||
.voice-particle.pink {
|
||||
background-color: #ffb9d7;
|
||||
box-shadow: 0 0 2px 2px var(--pink-color);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* IDLE status
|
||||
*/
|
||||
|
||||
#voice-energy-container.idle {
|
||||
.voice-particle {
|
||||
visibility: hidden;
|
||||
animation-play-state: paused;
|
||||
opacity: 0;
|
||||
}
|
||||
.voice-neon {
|
||||
margin: 0;
|
||||
}
|
||||
#purple-neon-blur {
|
||||
transform: scale(1);
|
||||
}
|
||||
#purple-neon-blur circle {
|
||||
filter: drop-shadow(0px 0px 64px mix(#ed297a, #1c75db));
|
||||
animation: idleNeonBlurBreath 2.2s infinite alternate;
|
||||
}
|
||||
#pink-neon-1 {
|
||||
transform: scale(1);
|
||||
animation: idleBouncePinkNeon1 1.8s 1s infinite alternate;
|
||||
}
|
||||
#blue-neon-1 {
|
||||
transform: scale(0.8);
|
||||
animation: idleMoveBlueNeon1 1.8s infinite alternate;
|
||||
}
|
||||
#blue-neon-2 {
|
||||
transform: scale(0.9);
|
||||
animation: idleMoveBlueNeon2 1.8s 0.5s infinite alternate;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes idleNeonBlurBreath {
|
||||
100% {
|
||||
filter: drop-shadow(0px 0px 0px mix(#ed297a, #1c75db));
|
||||
}
|
||||
}
|
||||
@keyframes idleBouncePurpleNeonBlur {
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes idleBouncePinkNeon1 {
|
||||
100% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
@keyframes idleMoveBlueNeon1 {
|
||||
100% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
@keyframes idleMoveBlueNeon2 {
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Listening status
|
||||
*/
|
||||
|
||||
#voice-energy-container.listening {
|
||||
.voice-particle {
|
||||
visibility: hidden;
|
||||
animation-play-state: paused;
|
||||
opacity: 0;
|
||||
}
|
||||
.voice-neon {
|
||||
margin: 0;
|
||||
}
|
||||
#purple-neon-blur {
|
||||
transform: scale(1);
|
||||
}
|
||||
#purple-neon-blur circle {
|
||||
filter: drop-shadow(0px 0px 64px mix(#ed297a, #1c75db));
|
||||
animation: listeningNeonBlurBreath 0.7s infinite alternate;
|
||||
}
|
||||
#pink-neon-1 {
|
||||
transform: scale(1);
|
||||
animation: listeningBouncePinkNeon1 0.3s 1s infinite alternate;
|
||||
}
|
||||
#blue-neon-1 {
|
||||
transform: scale(0.8);
|
||||
animation: listeningMoveBlueNeon1 0.3s infinite alternate;
|
||||
}
|
||||
#blue-neon-2 {
|
||||
transform: scale(0.9);
|
||||
animation: listeningMoveBlueNeon2 0.3s 0.5s infinite alternate;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes listeningNeonBlurBreath {
|
||||
100% {
|
||||
filter: drop-shadow(0px 0px 0px mix(#ed297a, #1c75db));
|
||||
}
|
||||
}
|
||||
@keyframes listeningBouncePurpleNeonBlur {
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes listeningBouncePinkNeon1 {
|
||||
100% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
@keyframes listeningMoveBlueNeon1 {
|
||||
100% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
@keyframes listeningMoveBlueNeon2 {
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@import 'base.scss';
|
||||
@import 'listening.scss';
|
||||
@import 'idle.scss';
|
||||
@import 'processing.scss';
|
||||
@import 'talking.scss';
|
||||
@@ -0,0 +1,88 @@
|
||||
@use 'sass:math';
|
||||
|
||||
/**
|
||||
* Processing status
|
||||
*/
|
||||
|
||||
#voice-energy-container.processing {
|
||||
#purple-neon-blur {
|
||||
animation: processingBouncePurpleNeonBlur 1s infinite alternate;
|
||||
}
|
||||
#pink-neon-1 {
|
||||
animation: processingBouncePinkNeon1 0.5s infinite alternate;
|
||||
}
|
||||
#blue-neon-1 {
|
||||
animation: processingMoveBlueNeon1 0.5s infinite alternate;
|
||||
}
|
||||
#blue-neon-2 {
|
||||
animation: processingMoveBlueNeon2 0.5s infinite alternate;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes processingBouncePurpleNeonBlur {
|
||||
50% {
|
||||
transform: scale(1.07);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes processingBouncePinkNeon1 {
|
||||
50% {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes processingMoveBlueNeon1 {
|
||||
0% {
|
||||
transform: translateX(0) translateY(0);
|
||||
}
|
||||
33% {
|
||||
transform: translateY(-3px) translateX(-2px);
|
||||
}
|
||||
66% {
|
||||
transform: translateY(-3px) translateX(3px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-3px) translateX(1px);
|
||||
}
|
||||
}
|
||||
@keyframes processingMoveBlueNeon2 {
|
||||
0% {
|
||||
transform: translateX(0) translateY(0);
|
||||
}
|
||||
33% {
|
||||
transform: translateY(3px) translateX(2px);
|
||||
}
|
||||
66% {
|
||||
transform: translateY(3px) translateX(3px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(3px) translateX(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 0 through 31 {
|
||||
.processing .voice-particle[data-particle='#{$i}'] {
|
||||
animation-delay: #{$i * 0.1}s;
|
||||
}
|
||||
|
||||
.processing .voice-particle[data-particle='#{$i}'] {
|
||||
animation-name: processingMoveParticle#{$i};
|
||||
}
|
||||
|
||||
#voice-energy-container.processing {
|
||||
@keyframes processingMoveParticle#{$i} {
|
||||
75% {
|
||||
opacity: 0.1;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(math.cos(11.25deg * $i) * 110px)
|
||||
translateY(math.sin(11.25deg * $i) * 110px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
@use 'sass:math';
|
||||
|
||||
/**
|
||||
* Talking status
|
||||
*/
|
||||
|
||||
#voice-energy-container.talking {
|
||||
.voice-neon {
|
||||
margin: 0;
|
||||
}
|
||||
#purple-neon-blur {
|
||||
animation: talkingBouncePurpleNeonBlur 1s infinite alternate;
|
||||
}
|
||||
#pink-neon-1 {
|
||||
transform: scale(1);
|
||||
animation: talkingBouncePinkNeon1 0.5s 1s infinite alternate;
|
||||
}
|
||||
#blue-neon-1 {
|
||||
transform: scale(0.8);
|
||||
animation: talkingMoveBlueNeon1 0.5s infinite alternate;
|
||||
}
|
||||
#blue-neon-2 {
|
||||
transform: scale(0.9);
|
||||
animation: talkingMoveBlueNeon2 0.5s 0.3s infinite alternate;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes talkingBouncePurpleNeonBlur {
|
||||
50% {
|
||||
transform: scale(1.07);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes talkingBouncePinkNeon1 {
|
||||
100% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
@keyframes talkingMoveBlueNeon1 {
|
||||
100% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
@keyframes talkingMoveBlueNeon2 {
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.talking .voice-particle {
|
||||
opacity: 0;
|
||||
animation-duration: 2s;
|
||||
}
|
||||
@for $i from 0 through 31 {
|
||||
.talking .voice-particle[data-particle='#{$i}'] {
|
||||
animation-delay: #{$i * 0.2}s;
|
||||
// animation-duration: #{$i * 0.5}s;
|
||||
}
|
||||
|
||||
.talking .voice-particle[data-particle='#{$i}'] {
|
||||
animation-name: talkingMoveParticle#{$i};
|
||||
}
|
||||
|
||||
#voice-energy-container.talking {
|
||||
@keyframes talkingMoveParticle#{$i} {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translate(0);
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(math.cos(math.random() * 360deg))
|
||||
translateY(math.sin(math.random() * 360deg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './timer'
|
||||
@@ -0,0 +1 @@
|
||||
export * from './timer'
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
import { CircularProgress, Flexbox, Text } from '@aurora'
|
||||
|
||||
interface TimerProps {
|
||||
initialTime: number
|
||||
interval: number
|
||||
totalTimeContent: string
|
||||
initialProgress?: number
|
||||
onEnd?: () => void
|
||||
}
|
||||
|
||||
function formatTime(seconds: number): string {
|
||||
const minutes = seconds >= 60 ? Math.floor(seconds / 60) : 0
|
||||
const remainingSeconds = seconds % 60
|
||||
const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes
|
||||
const formattedSeconds =
|
||||
remainingSeconds < 10 ? `0${remainingSeconds}` : remainingSeconds
|
||||
|
||||
return `${formattedMinutes}:${formattedSeconds}`
|
||||
}
|
||||
|
||||
export function Timer({
|
||||
initialTime,
|
||||
initialProgress,
|
||||
interval,
|
||||
totalTimeContent,
|
||||
onEnd
|
||||
}: TimerProps) {
|
||||
const [progress, setProgress] = useState(initialProgress || 0)
|
||||
const [timeLeft, setTimeLeft] = useState(initialTime)
|
||||
|
||||
useEffect(() => {
|
||||
setTimeLeft(initialTime)
|
||||
setProgress(progress)
|
||||
}, [initialTime])
|
||||
|
||||
useEffect(() => {
|
||||
if (timeLeft <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const timer = setInterval(() => {
|
||||
setTimeLeft((prevTime) => {
|
||||
const newTime = prevTime - 1
|
||||
|
||||
if (newTime <= 0 && onEnd) {
|
||||
onEnd()
|
||||
}
|
||||
|
||||
return newTime
|
||||
})
|
||||
setProgress((prevProgress) => prevProgress + 100 / initialTime)
|
||||
}, interval)
|
||||
|
||||
return () => clearInterval(timer)
|
||||
}, [initialTime, interval, timeLeft])
|
||||
|
||||
return (
|
||||
<CircularProgress value={progress} size="lg">
|
||||
<Flexbox gap="xs" alignItems="center" justifyContent="center">
|
||||
<Text fontSize="lg" fontWeight="semi-bold">
|
||||
{formatTime(timeLeft)}
|
||||
</Text>
|
||||
<Text fontSize="xs" secondary>
|
||||
{totalTimeContent}
|
||||
</Text>
|
||||
</Flexbox>
|
||||
</CircularProgress>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +1,322 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Leon</title>
|
||||
<style>
|
||||
@import './css/style.scss';
|
||||
</style>
|
||||
</head>
|
||||
<body class="settingup">
|
||||
<div id="init"></div>
|
||||
<div id="voice-overlay-bg">
|
||||
<div id="voice-container">
|
||||
<div>
|
||||
<div id="voice-energy-container">
|
||||
<svg
|
||||
class="voice-neon"
|
||||
id="pink-neon-1"
|
||||
viewBox="0 0 237 237"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter0_i_6_16)">
|
||||
<circle
|
||||
cx="118.5"
|
||||
cy="118.5"
|
||||
r="113.5"
|
||||
stroke="#FFF"
|
||||
stroke-width="10"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_i_6_16"
|
||||
x="0"
|
||||
y="0"
|
||||
width="237"
|
||||
height="237"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="erode"
|
||||
in="SourceAlpha"
|
||||
result="effect1_innerShadow_6_16"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="1" />
|
||||
<feComposite
|
||||
in2="hardAlpha"
|
||||
operator="arithmetic"
|
||||
k2="-1"
|
||||
k3="1"
|
||||
/>
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.929412 0 0 0 0 0.160784 0 0 0 0 0.478431 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="shape"
|
||||
result="effect1_innerShadow_6_16"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
class="voice-neon"
|
||||
id="blue-neon-1"
|
||||
viewBox="0 0 237 237"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter0_i_6_24)">
|
||||
<circle
|
||||
cx="118.5"
|
||||
cy="118.5"
|
||||
r="116.5"
|
||||
stroke="#FFF"
|
||||
stroke-width="4"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_i_6_24"
|
||||
x="0"
|
||||
y="0"
|
||||
width="237"
|
||||
height="237"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="erode"
|
||||
in="SourceAlpha"
|
||||
result="effect1_innerShadow_6_24"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="1" />
|
||||
<feComposite
|
||||
in2="hardAlpha"
|
||||
operator="arithmetic"
|
||||
k2="-1"
|
||||
k3="1"
|
||||
/>
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.109804 0 0 0 0 0.458824 0 0 0 0 0.858824 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="shape"
|
||||
result="effect1_innerShadow_6_24"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
class="voice-neon"
|
||||
id="blue-neon-2"
|
||||
viewBox="0 0 237 237"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter0_i_6_24)">
|
||||
<circle
|
||||
cx="118.5"
|
||||
cy="118.5"
|
||||
r="116.5"
|
||||
stroke="#FFF"
|
||||
stroke-width="4"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_i_6_24"
|
||||
x="0"
|
||||
y="0"
|
||||
width="237"
|
||||
height="237"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="5"
|
||||
operator="erode"
|
||||
in="SourceAlpha"
|
||||
result="effect1_innerShadow_6_24"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="1" />
|
||||
<feComposite
|
||||
in2="hardAlpha"
|
||||
operator="arithmetic"
|
||||
k2="-1"
|
||||
k3="1"
|
||||
/>
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.109804 0 0 0 0 0.458824 0 0 0 0 0.858824 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="shape"
|
||||
result="effect1_innerShadow_6_24"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
id="purple-neon-blur"
|
||||
viewBox="0 0 341 341"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g opacity="0.72" filter="url(#filter0_f_18_61)">
|
||||
<circle
|
||||
cx="170.5"
|
||||
cy="170.5"
|
||||
r="116.5"
|
||||
stroke="#ED297A"
|
||||
stroke-width="24"
|
||||
/>
|
||||
</g>
|
||||
<g opacity="0.72" filter="url(#filter1_f_18_61)">
|
||||
<circle
|
||||
cx="170.5"
|
||||
cy="170.5"
|
||||
r="116.5"
|
||||
stroke="#1C75DB"
|
||||
stroke-width="24"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_18_61"
|
||||
x="0"
|
||||
y="0"
|
||||
width="341"
|
||||
height="341"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="21"
|
||||
result="effect1_foregroundBlur_18_61"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_f_18_61"
|
||||
x="0"
|
||||
y="0"
|
||||
width="341"
|
||||
height="341"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="21"
|
||||
result="effect1_foregroundBlur_18_61"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div id="voice-status"></div>
|
||||
<div id="voice-tips">
|
||||
It is recommended to use a headset for a better voice experience.
|
||||
<br />
|
||||
Otherwise, if your microphone is too sensitive or speakers are too
|
||||
loud,
|
||||
<br />
|
||||
Leon may hear his own voice and get confused.
|
||||
</div>
|
||||
</div>
|
||||
<p id="voice-speech"></p>
|
||||
</div>
|
||||
</div>
|
||||
<aside id="sessions-panel" aria-label="Sessions">
|
||||
<div id="sessions-header">
|
||||
<div>
|
||||
<span id="sessions-eyebrow">Sessions</span>
|
||||
</div>
|
||||
<button id="new-session" type="button" aria-label="New session">
|
||||
<i class="ri-add-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="sessions-list"></div>
|
||||
</aside>
|
||||
<main>
|
||||
<div id="root"></div>
|
||||
<div id="top-container">
|
||||
<div id="mood"></div>
|
||||
<button id="info">Info</button>
|
||||
</div>
|
||||
<div id="feed">
|
||||
<p id="no-bubble" class="hide">
|
||||
You can start to interact with Leon, don't be shy.
|
||||
You can start to interact with me, don't be shy.
|
||||
</p>
|
||||
</div>
|
||||
<div id="suggestions-container"></div>
|
||||
<div id="is-typing">
|
||||
<div class="circle"></div>
|
||||
<div class="circle"></div>
|
||||
@@ -27,25 +328,37 @@
|
||||
<div id="sonar"></div>
|
||||
</div>
|
||||
<label for="utterance"></label>
|
||||
<input type="text" id="utterance" autocomplete="off" autofocus />
|
||||
<small>
|
||||
Use <kbd>↑</kbd> <kbd>↓</kbd> to browse history; <kbd>↵</kbd> to
|
||||
submit;
|
||||
<kbd>alt + c to listen.</kbd>
|
||||
</small>
|
||||
<textarea id="utterance" autocomplete="off" autofocus></textarea>
|
||||
<ul id="tip">
|
||||
<li><kbd>enter</kbd> to submit.</li>
|
||||
<li><kbd>shift</kbd> + <kbd>enter</kbd> for new line.</li>
|
||||
<li>
|
||||
<kbd>shift</kbd> + <kbd>↑</kbd> / <kbd>↓</kbd> to browse history.
|
||||
</li>
|
||||
<li><kbd>alt</kbd> / <kbd>cmd</kbd> + <kbd>c</kbd> to listen.</li>
|
||||
<li><kbd>/</kbd> for commands.</li>
|
||||
<li><kbd>@</kbd> for files.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<div id="logo"></div>
|
||||
<div id="version">
|
||||
<small>v</small>
|
||||
<footer id="footer">
|
||||
<div id="logo-container">
|
||||
<div id="logo"></div>
|
||||
<div>
|
||||
<small>Leon AI</small>
|
||||
<div id="version">
|
||||
<small>v</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="discord">
|
||||
<small class="italic">
|
||||
<a href="https://discord.gg/MNQqqKg" target="_blank"
|
||||
<div id="discord-container">
|
||||
<div>
|
||||
<small>
|
||||
<a href="https://discord.gg/MNQqqKg" target="_blank"
|
||||
>Join us on Discord</a
|
||||
>
|
||||
</small>
|
||||
>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script type="module" src="/js/main.js"></script>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<p>Hello from React</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -1,9 +0,0 @@
|
||||
import type React from 'react'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function Button({ children }: Props) {
|
||||
return <button>{children}</button>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import axios from 'axios'
|
||||
|
||||
export async function requestBuiltInCommand(serverUrl, payload) {
|
||||
const response = await axios.post(`${serverUrl}/api/v1/command`, payload)
|
||||
|
||||
return response.data
|
||||
}
|
||||
@@ -0,0 +1,750 @@
|
||||
import { createRef } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { requestBuiltInCommand } from './api'
|
||||
import { BuiltInCommandsModal } from './modal'
|
||||
import FileSystemAutocomplete from '../file-system-autocomplete'
|
||||
|
||||
const AUTOCOMPLETE_DELAY_MS = 90
|
||||
const CLOSE_ANIMATION_DURATION_MS = 180
|
||||
const ROOT_COMMAND_INPUT = '/'
|
||||
const EDITABLE_ACTION_KEYS = new Set(['Backspace', 'Delete'])
|
||||
const NAVIGATION_DIRECTIONS = {
|
||||
next: 1,
|
||||
previous: -1
|
||||
}
|
||||
|
||||
function isEditableElement(element) {
|
||||
if (!element) {
|
||||
return false
|
||||
}
|
||||
|
||||
const tagName = element.tagName?.toLowerCase()
|
||||
|
||||
return (
|
||||
tagName === 'input' ||
|
||||
tagName === 'textarea' ||
|
||||
element.isContentEditable === true
|
||||
)
|
||||
}
|
||||
|
||||
export default class BuiltInCommands {
|
||||
constructor({
|
||||
serverUrl,
|
||||
input,
|
||||
onSubmitToChat,
|
||||
getActiveSessionId,
|
||||
onCommandExecuted
|
||||
}) {
|
||||
this.serverUrl = serverUrl
|
||||
this.input = input
|
||||
this.onSubmitToChat = onSubmitToChat
|
||||
this.getActiveSessionId = getActiveSessionId
|
||||
this.onCommandExecuted = onCommandExecuted
|
||||
this.sessionId = null
|
||||
this.origin = 'shortcut'
|
||||
this.commandValue = ''
|
||||
this.selectedSuggestionIndex = -1
|
||||
this.suggestions = []
|
||||
this.recentSuggestions = []
|
||||
this.result = null
|
||||
this.loadingMessage = null
|
||||
this.pendingInput = null
|
||||
this.isOpen = false
|
||||
this.isClosing = false
|
||||
this.isLoading = false
|
||||
this.hasSubmitted = false
|
||||
this.autocompleteTimeout = null
|
||||
this.closeTimeout = null
|
||||
this.shouldFocusInput = false
|
||||
this.modalInputRef = createRef()
|
||||
this.fileSystemAutocomplete = new FileSystemAutocomplete({
|
||||
serverUrl,
|
||||
onValueChange: (value) => {
|
||||
this.handleCommandChange(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
init() {
|
||||
this.mount()
|
||||
this.attachEvents()
|
||||
this.render()
|
||||
}
|
||||
|
||||
mount() {
|
||||
this.container = document.createElement('div')
|
||||
document.body.appendChild(this.container)
|
||||
this.root = createRoot(this.container)
|
||||
}
|
||||
|
||||
attachEvents() {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
this.handleDocumentKeyDown(event)
|
||||
})
|
||||
|
||||
this.input.addEventListener('input', () => {
|
||||
this.handleMainInput()
|
||||
})
|
||||
|
||||
this.input.addEventListener('focus', () => {
|
||||
this.handleMainInput()
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const focusedInputElement = this.modalInputRef.current
|
||||
const shouldRestoreCommandInputFocus =
|
||||
focusedInputElement && document.activeElement === focusedInputElement
|
||||
const selectionStart = shouldRestoreCommandInputFocus
|
||||
? focusedInputElement.selectionStart
|
||||
: null
|
||||
const selectionEnd = shouldRestoreCommandInputFocus
|
||||
? focusedInputElement.selectionEnd
|
||||
: null
|
||||
const { recentSelectedSuggestionIndex, suggestionSelectedSuggestionIndex } =
|
||||
this.getSelectedSuggestionIndices()
|
||||
|
||||
this.root.render(
|
||||
<BuiltInCommandsModal
|
||||
isOpen={this.isOpen}
|
||||
isVisible={this.isOpen || this.isClosing}
|
||||
isLoading={this.isLoading}
|
||||
loadingMessage={this.loadingMessage}
|
||||
commandValue={this.commandValue}
|
||||
suggestions={this.suggestions}
|
||||
recentSuggestions={this.recentSuggestions}
|
||||
recentSelectedSuggestionIndex={recentSelectedSuggestionIndex}
|
||||
suggestionSelectedSuggestionIndex={suggestionSelectedSuggestionIndex}
|
||||
result={this.result}
|
||||
pendingInput={this.pendingInput}
|
||||
hasSubmitted={this.hasSubmitted}
|
||||
inputRef={this.modalInputRef}
|
||||
onCommandChange={(value) => {
|
||||
this.handleCommandChange(value)
|
||||
}}
|
||||
onMaskClick={() => {
|
||||
this.close()
|
||||
}}
|
||||
onSuggestionSelect={(suggestion) => {
|
||||
this.handleSuggestionSelect(suggestion)
|
||||
}}
|
||||
onReturn={() => {
|
||||
this.returnToSuggestions()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
if (this.shouldFocusInput) {
|
||||
this.shouldFocusInput = false
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
this.focusCommandInput()
|
||||
})
|
||||
} else if (shouldRestoreCommandInputFocus) {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.restoreCommandInputFocus(selectionStart, selectionEnd)
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
this.isOpen &&
|
||||
!this.hasSubmitted &&
|
||||
!this.result &&
|
||||
this.selectedSuggestionIndex >= 0
|
||||
) {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.scrollSelectedSuggestionIntoView()
|
||||
})
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
this.attachFileSystemAutocomplete()
|
||||
})
|
||||
}
|
||||
|
||||
attachFileSystemAutocomplete() {
|
||||
const inputElement = this.modalInputRef.current
|
||||
|
||||
if (!inputElement) {
|
||||
return
|
||||
}
|
||||
|
||||
this.fileSystemAutocomplete.attach(inputElement)
|
||||
}
|
||||
|
||||
handleDocumentKeyDown(event) {
|
||||
if (this.isOpen) {
|
||||
if (event.metaKey || event.ctrlKey || event.altKey) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
this.blurCommandInput()
|
||||
this.moveSelection(NAVIGATION_DIRECTIONS.next)
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
this.blurCommandInput()
|
||||
this.moveSelection(NAVIGATION_DIRECTIONS.previous)
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'Tab') {
|
||||
const suggestion =
|
||||
this.selectedSuggestionIndex >= 0
|
||||
? this.getVisibleSuggestions()[this.selectedSuggestionIndex]
|
||||
: null
|
||||
|
||||
if (suggestion) {
|
||||
event.preventDefault()
|
||||
this.applySuggestion(suggestion, {
|
||||
appendTrailingSpace: true
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
|
||||
if (this.selectedSuggestionIndex >= 0) {
|
||||
const suggestion =
|
||||
this.getVisibleSuggestions()[this.selectedSuggestionIndex]
|
||||
|
||||
if (suggestion) {
|
||||
this.applySuggestion(suggestion)
|
||||
void this.submit()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.submit()
|
||||
return
|
||||
}
|
||||
|
||||
if (this.shouldHandleDetachedInputKey(event)) {
|
||||
event.preventDefault()
|
||||
this.handleDetachedInputKey(event)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
event.key !== '/' ||
|
||||
event.metaKey ||
|
||||
event.ctrlKey ||
|
||||
event.altKey ||
|
||||
event.shiftKey
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const activeElement = document.activeElement
|
||||
|
||||
if (
|
||||
activeElement === this.input ||
|
||||
(activeElement && isEditableElement(activeElement))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
this.open({
|
||||
origin: 'shortcut',
|
||||
rawInput: '/'
|
||||
})
|
||||
}
|
||||
|
||||
handleMainInput() {
|
||||
if (document.activeElement !== this.input) {
|
||||
return
|
||||
}
|
||||
|
||||
const rawValue = this.input.value || ''
|
||||
|
||||
if (!rawValue.startsWith('/')) {
|
||||
return
|
||||
}
|
||||
|
||||
this.open({
|
||||
origin: 'input',
|
||||
rawInput: rawValue
|
||||
})
|
||||
}
|
||||
|
||||
handleCommandChange(value) {
|
||||
this.commandValue = value
|
||||
this.hasSubmitted = false
|
||||
if (!this.pendingInput) {
|
||||
this.result = null
|
||||
this.loadingMessage = null
|
||||
this.queueAutocomplete()
|
||||
}
|
||||
this.render()
|
||||
}
|
||||
|
||||
open({ origin, rawInput }) {
|
||||
window.clearTimeout(this.closeTimeout)
|
||||
|
||||
if (this.isOpen) {
|
||||
this.origin = origin
|
||||
this.commandValue = this.normalizeCommandValue(rawInput)
|
||||
this.result = null
|
||||
this.loadingMessage = null
|
||||
this.pendingInput = null
|
||||
this.hasSubmitted = false
|
||||
this.shouldFocusInput = true
|
||||
this.input.value = ''
|
||||
this.queueAutocomplete()
|
||||
this.render()
|
||||
return
|
||||
}
|
||||
|
||||
this.origin = origin
|
||||
this.sessionId = null
|
||||
this.commandValue = this.normalizeCommandValue(rawInput)
|
||||
this.suggestions = []
|
||||
this.result = null
|
||||
this.loadingMessage = null
|
||||
this.pendingInput = null
|
||||
this.selectedSuggestionIndex = -1
|
||||
this.hasSubmitted = false
|
||||
this.isOpen = true
|
||||
this.isClosing = false
|
||||
this.shouldFocusInput = true
|
||||
this.input.value = ''
|
||||
|
||||
if (this.origin === 'input') {
|
||||
this.input.blur()
|
||||
}
|
||||
|
||||
this.queueAutocomplete()
|
||||
this.render()
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this.isOpen || this.isClosing) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isClosing = true
|
||||
this.isOpen = false
|
||||
this.fileSystemAutocomplete.close()
|
||||
this.render()
|
||||
|
||||
this.closeTimeout = window.setTimeout(() => {
|
||||
this.resetState()
|
||||
this.render()
|
||||
this.focusMainInput()
|
||||
}, CLOSE_ANIMATION_DURATION_MS)
|
||||
}
|
||||
|
||||
queueAutocomplete() {
|
||||
window.clearTimeout(this.autocompleteTimeout)
|
||||
this.isLoading = true
|
||||
|
||||
this.autocompleteTimeout = window.setTimeout(() => {
|
||||
void this.fetchAutocomplete()
|
||||
}, AUTOCOMPLETE_DELAY_MS)
|
||||
}
|
||||
|
||||
async fetchAutocomplete() {
|
||||
try {
|
||||
const data = await requestBuiltInCommand(this.serverUrl, {
|
||||
mode: 'autocomplete',
|
||||
input: this.buildCommandInput(),
|
||||
session_id: this.sessionId,
|
||||
conversation_session_id: this.getActiveSessionId?.()
|
||||
})
|
||||
|
||||
if (!this.isOpen || this.hasSubmitted) {
|
||||
return
|
||||
}
|
||||
|
||||
this.sessionId = data.session.id
|
||||
this.suggestions = data.suggestions || []
|
||||
this.recentSuggestions = data.recent_suggestions || []
|
||||
this.loadingMessage = data.session.loading_message || null
|
||||
this.pendingInput = data.session.pending_input || null
|
||||
this.selectedSuggestionIndex =
|
||||
this.getVisibleSuggestions().length > 0 ? 0 : -1
|
||||
this.isLoading = false
|
||||
this.render()
|
||||
} catch (error) {
|
||||
if (!this.isOpen || this.hasSubmitted) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isLoading = false
|
||||
this.result = {
|
||||
title: 'Command Error',
|
||||
tone: 'error',
|
||||
blocks: [
|
||||
{
|
||||
type: 'list',
|
||||
items: [
|
||||
{
|
||||
label: error.message || 'Failed to load built-in commands.',
|
||||
tone: 'error'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
this.loadingMessage = null
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const commandInput = this.buildCommandInput()
|
||||
|
||||
if (commandInput === ROOT_COMMAND_INPUT) {
|
||||
return
|
||||
}
|
||||
|
||||
window.clearTimeout(this.autocompleteTimeout)
|
||||
this.autocompleteTimeout = null
|
||||
|
||||
await this.resolveLoadingMessageForSubmission(commandInput)
|
||||
|
||||
this.isLoading = true
|
||||
this.hasSubmitted = true
|
||||
this.result = null
|
||||
this.commandValue = ''
|
||||
this.suggestions = []
|
||||
this.selectedSuggestionIndex = -1
|
||||
this.render()
|
||||
|
||||
try {
|
||||
const data = await requestBuiltInCommand(this.serverUrl, {
|
||||
mode: 'execute',
|
||||
input: commandInput,
|
||||
session_id: this.sessionId,
|
||||
conversation_session_id: this.getActiveSessionId?.()
|
||||
})
|
||||
|
||||
if (data.client_action?.type === 'submit_to_chat') {
|
||||
const wasSubmitted = this.onSubmitToChat?.(data.client_action)
|
||||
|
||||
if (!wasSubmitted) {
|
||||
throw new Error('Failed to submit the built-in command to chat.')
|
||||
}
|
||||
|
||||
this.isLoading = false
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
|
||||
this.sessionId = data.session.id
|
||||
this.isLoading = false
|
||||
this.result = data.result
|
||||
this.onCommandExecuted?.(commandInput, data)
|
||||
this.loadingMessage = data.session.loading_message || null
|
||||
this.recentSuggestions = data.recent_suggestions || []
|
||||
this.pendingInput = data.session.pending_input || null
|
||||
this.commandValue = ''
|
||||
this.render()
|
||||
} catch (error) {
|
||||
this.isLoading = false
|
||||
this.result = {
|
||||
title: 'Command Error',
|
||||
tone: 'error',
|
||||
blocks: [
|
||||
{
|
||||
type: 'list',
|
||||
items: [
|
||||
{
|
||||
label:
|
||||
error.message || 'Failed to execute the built-in command.',
|
||||
tone: 'error'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
this.loadingMessage = null
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
|
||||
async resolveLoadingMessageForSubmission(commandInput) {
|
||||
try {
|
||||
const data = await requestBuiltInCommand(this.serverUrl, {
|
||||
mode: 'autocomplete',
|
||||
input: commandInput,
|
||||
session_id: this.sessionId,
|
||||
conversation_session_id: this.getActiveSessionId?.()
|
||||
})
|
||||
|
||||
this.sessionId = data.session.id
|
||||
this.loadingMessage = data.session.loading_message || null
|
||||
} catch {
|
||||
this.loadingMessage = null
|
||||
}
|
||||
}
|
||||
|
||||
moveSelection(direction) {
|
||||
if (this.hasSubmitted || this.result) {
|
||||
return
|
||||
}
|
||||
|
||||
const visibleSuggestions = this.getVisibleSuggestions()
|
||||
|
||||
if (visibleSuggestions.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.selectedSuggestionIndex === -1) {
|
||||
this.selectedSuggestionIndex = 0
|
||||
} else {
|
||||
this.selectedSuggestionIndex =
|
||||
(this.selectedSuggestionIndex +
|
||||
direction +
|
||||
visibleSuggestions.length) %
|
||||
visibleSuggestions.length
|
||||
}
|
||||
|
||||
this.render()
|
||||
}
|
||||
|
||||
handleSuggestionSelect(suggestion) {
|
||||
const normalizedSuggestionValue = this.normalizeCommandValue(suggestion.value)
|
||||
|
||||
if (normalizedSuggestionValue.trim() === this.commandValue.trim()) {
|
||||
void this.submit()
|
||||
return
|
||||
}
|
||||
|
||||
this.applySuggestion(suggestion, {
|
||||
appendTrailingSpace: true
|
||||
})
|
||||
}
|
||||
|
||||
applySuggestion(suggestion, options = {}) {
|
||||
this.commandValue = `${this.normalizeCommandValue(suggestion.value)}${
|
||||
options.appendTrailingSpace ? ' ' : ''
|
||||
}`
|
||||
this.loadingMessage = null
|
||||
this.queueAutocomplete()
|
||||
this.shouldFocusInput = true
|
||||
this.render()
|
||||
}
|
||||
|
||||
returnToSuggestions() {
|
||||
this.sessionId = null
|
||||
this.hasSubmitted = false
|
||||
this.result = null
|
||||
this.loadingMessage = null
|
||||
this.pendingInput = null
|
||||
this.commandValue = ''
|
||||
this.suggestions = []
|
||||
this.selectedSuggestionIndex = -1
|
||||
this.shouldFocusInput = true
|
||||
this.queueAutocomplete()
|
||||
this.render()
|
||||
}
|
||||
|
||||
focusCommandInput() {
|
||||
const inputElement = this.modalInputRef.current
|
||||
|
||||
if (!inputElement) {
|
||||
return
|
||||
}
|
||||
|
||||
inputElement.focus()
|
||||
|
||||
if (typeof inputElement.setSelectionRange === 'function') {
|
||||
inputElement.setSelectionRange(
|
||||
inputElement.value.length,
|
||||
inputElement.value.length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
restoreCommandInputFocus(selectionStart, selectionEnd) {
|
||||
const inputElement = this.modalInputRef.current
|
||||
|
||||
if (!inputElement) {
|
||||
return
|
||||
}
|
||||
|
||||
if (document.activeElement !== inputElement) {
|
||||
try {
|
||||
inputElement.focus({ preventScroll: true })
|
||||
} catch {
|
||||
inputElement.focus()
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof inputElement.setSelectionRange === 'function' &&
|
||||
selectionStart !== null &&
|
||||
selectionEnd !== null
|
||||
) {
|
||||
inputElement.setSelectionRange(selectionStart, selectionEnd)
|
||||
}
|
||||
}
|
||||
|
||||
blurCommandInput() {
|
||||
const inputElement = this.modalInputRef.current
|
||||
|
||||
if (!inputElement || document.activeElement !== inputElement) {
|
||||
return
|
||||
}
|
||||
|
||||
inputElement.blur()
|
||||
}
|
||||
|
||||
focusMainInput() {
|
||||
if (!this.input) {
|
||||
return
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
try {
|
||||
this.input.focus({ preventScroll: true })
|
||||
} catch {
|
||||
this.input.focus()
|
||||
}
|
||||
|
||||
if (typeof this.input.setSelectionRange === 'function') {
|
||||
this.input.setSelectionRange(
|
||||
this.input.value.length,
|
||||
this.input.value.length
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
scrollSelectedSuggestionIntoView() {
|
||||
const selectedSuggestionElement = this.container?.querySelector(
|
||||
'.aurora-list-item--selected'
|
||||
)
|
||||
|
||||
if (!selectedSuggestionElement) {
|
||||
return
|
||||
}
|
||||
|
||||
selectedSuggestionElement.scrollIntoView({
|
||||
block: 'nearest'
|
||||
})
|
||||
}
|
||||
|
||||
shouldHandleDetachedInputKey(event) {
|
||||
const inputElement = this.modalInputRef.current
|
||||
|
||||
if (!inputElement || document.activeElement === inputElement) {
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
event.key.length === 1 ||
|
||||
EDITABLE_ACTION_KEYS.has(event.key)
|
||||
)
|
||||
}
|
||||
|
||||
handleDetachedInputKey(event) {
|
||||
if (event.key.length === 1) {
|
||||
this.commandValue += event.key
|
||||
} else if (event.key === 'Backspace') {
|
||||
this.commandValue = this.commandValue.slice(0, -1)
|
||||
} else if (event.key === 'Delete') {
|
||||
this.commandValue = ''
|
||||
}
|
||||
|
||||
this.hasSubmitted = false
|
||||
this.result = null
|
||||
this.loadingMessage = null
|
||||
this.queueAutocomplete()
|
||||
this.shouldFocusInput = true
|
||||
this.render()
|
||||
}
|
||||
|
||||
normalizeCommandValue(rawInput) {
|
||||
const normalizedValue = String(rawInput || '').trim()
|
||||
|
||||
if (!normalizedValue.startsWith('/')) {
|
||||
return normalizedValue
|
||||
}
|
||||
|
||||
return normalizedValue.slice(1)
|
||||
}
|
||||
|
||||
buildCommandInput() {
|
||||
if (this.pendingInput) {
|
||||
return this.commandValue
|
||||
}
|
||||
|
||||
return `/${this.commandValue}`.trim()
|
||||
}
|
||||
|
||||
getVisibleSuggestions() {
|
||||
if (this.commandValue.trim() === '') {
|
||||
return [...this.recentSuggestions, ...this.suggestions]
|
||||
}
|
||||
|
||||
return this.suggestions
|
||||
}
|
||||
|
||||
getSelectedSuggestionIndices() {
|
||||
if (this.commandValue.trim() !== '') {
|
||||
return {
|
||||
recentSelectedSuggestionIndex: -1,
|
||||
suggestionSelectedSuggestionIndex: this.selectedSuggestionIndex
|
||||
}
|
||||
}
|
||||
|
||||
const recentSuggestionsLength = this.recentSuggestions.length
|
||||
|
||||
if (this.selectedSuggestionIndex < 0) {
|
||||
return {
|
||||
recentSelectedSuggestionIndex: -1,
|
||||
suggestionSelectedSuggestionIndex: -1
|
||||
}
|
||||
}
|
||||
|
||||
if (this.selectedSuggestionIndex < recentSuggestionsLength) {
|
||||
return {
|
||||
recentSelectedSuggestionIndex: this.selectedSuggestionIndex,
|
||||
suggestionSelectedSuggestionIndex: -1
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
recentSelectedSuggestionIndex: -1,
|
||||
suggestionSelectedSuggestionIndex:
|
||||
this.selectedSuggestionIndex - recentSuggestionsLength
|
||||
}
|
||||
}
|
||||
|
||||
resetState() {
|
||||
window.clearTimeout(this.autocompleteTimeout)
|
||||
this.isClosing = false
|
||||
this.isLoading = false
|
||||
this.sessionId = null
|
||||
this.origin = 'shortcut'
|
||||
this.commandValue = ''
|
||||
this.suggestions = []
|
||||
this.result = null
|
||||
this.loadingMessage = null
|
||||
this.pendingInput = null
|
||||
this.selectedSuggestionIndex = -1
|
||||
this.hasSubmitted = false
|
||||
this.shouldFocusInput = false
|
||||
this.autocompleteTimeout = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import '../../css/built-in-commands.scss'
|
||||
|
||||
export { default as BuiltInCommands } from './built-in-commands.jsx'
|
||||
@@ -0,0 +1,263 @@
|
||||
import {
|
||||
Button,
|
||||
Flexbox,
|
||||
Icon,
|
||||
Input,
|
||||
List,
|
||||
ListHeader,
|
||||
ListItem,
|
||||
Loader,
|
||||
ScrollContainer,
|
||||
Text
|
||||
} from '@aurora'
|
||||
|
||||
import { BuiltInCommandResultRenderer } from './result-renderer'
|
||||
|
||||
const SLASH_COMMAND_ICON_SVG = (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M9.78839 21H7.66003L14.2115 3H16.3398L9.78839 21Z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
function parseRemixIcon(rawIconName, rawIconType) {
|
||||
const fallbackIcon = {
|
||||
iconName: 'terminal-box',
|
||||
type: 'line'
|
||||
}
|
||||
const normalizedIconName = String(rawIconName || '').trim()
|
||||
const normalizedIconType = ['line', 'fill', 'notype'].includes(rawIconType)
|
||||
? rawIconType
|
||||
: null
|
||||
|
||||
if (!normalizedIconName) {
|
||||
return fallbackIcon
|
||||
}
|
||||
|
||||
const iconWithoutPrefix = normalizedIconName.replace(/^ri-/, '')
|
||||
|
||||
if (iconWithoutPrefix.endsWith('-line')) {
|
||||
return {
|
||||
iconName: iconWithoutPrefix.slice(0, -5),
|
||||
type: 'line'
|
||||
}
|
||||
}
|
||||
|
||||
if (iconWithoutPrefix.endsWith('-fill')) {
|
||||
return {
|
||||
iconName: iconWithoutPrefix.slice(0, -5),
|
||||
type: 'fill'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
iconName: iconWithoutPrefix,
|
||||
type: normalizedIconType || 'notype'
|
||||
}
|
||||
}
|
||||
|
||||
function SuggestionList({
|
||||
headerTitle,
|
||||
suggestions,
|
||||
selectedSuggestionIndex,
|
||||
onSuggestionSelect
|
||||
}) {
|
||||
return (
|
||||
<List>
|
||||
<ListHeader>{headerTitle}</ListHeader>
|
||||
{suggestions.map((suggestion, index) => {
|
||||
const parsedIcon = parseRemixIcon(
|
||||
suggestion.icon_name,
|
||||
suggestion.icon_type
|
||||
)
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={`suggestion-${suggestion.value}-${index}`}
|
||||
name="built-in-command-suggestion"
|
||||
value={suggestion.value}
|
||||
selected={index === selectedSuggestionIndex}
|
||||
onClick={() => {
|
||||
onSuggestionSelect(suggestion)
|
||||
}}
|
||||
>
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="md">
|
||||
<Icon
|
||||
iconName={parsedIcon.iconName}
|
||||
type={parsedIcon.type}
|
||||
size="md"
|
||||
bgShape="square"
|
||||
color="blue"
|
||||
bgColor="transparent-blue"
|
||||
/>
|
||||
<div className="built-in-commands-modal__suggestion-copy">
|
||||
<Text fontWeight="semi-bold">{suggestion.usage}</Text>
|
||||
<Text secondary>{suggestion.description}</Text>
|
||||
</div>
|
||||
</Flexbox>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
||||
function LoadingState({ loadingMessage }) {
|
||||
return (
|
||||
<List>
|
||||
<ListHeader>Working</ListHeader>
|
||||
<ListItem>
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="sm">
|
||||
<Loader size="sm" />
|
||||
<Text>{loadingMessage || 'Working on it...'}</Text>
|
||||
</Flexbox>
|
||||
</ListItem>
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
||||
function EmptyState() {
|
||||
return (
|
||||
<List>
|
||||
<ListHeader>Suggestions</ListHeader>
|
||||
<ListItem>
|
||||
<Text secondary>No matching built-in command.</Text>
|
||||
</ListItem>
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
||||
export function BuiltInCommandsModal({
|
||||
isOpen,
|
||||
isVisible,
|
||||
isLoading,
|
||||
loadingMessage,
|
||||
commandValue,
|
||||
suggestions,
|
||||
recentSuggestions,
|
||||
recentSelectedSuggestionIndex,
|
||||
suggestionSelectedSuggestionIndex,
|
||||
result,
|
||||
pendingInput,
|
||||
hasSubmitted,
|
||||
inputRef,
|
||||
onCommandChange,
|
||||
onMaskClick,
|
||||
onSuggestionSelect,
|
||||
onReturn
|
||||
}) {
|
||||
const isCommandInputEmpty = commandValue.trim() === ''
|
||||
const pendingInputIcon = pendingInput?.icon_name
|
||||
? parseRemixIcon(pendingInput.icon_name, pendingInput.icon_type)
|
||||
: null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`built-in-commands-modal ${
|
||||
isOpen
|
||||
? 'built-in-commands-modal--open'
|
||||
: isVisible
|
||||
? ''
|
||||
: 'built-in-commands-modal--hidden'
|
||||
}`}
|
||||
aria-hidden={!isVisible}
|
||||
>
|
||||
<div className="built-in-commands-modal__mask" onClick={onMaskClick} />
|
||||
<div
|
||||
className="built-in-commands-modal__panel"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Built-in commands"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<div className="built-in-commands-modal__section built-in-commands-modal__section--top">
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="md">
|
||||
{hasSubmitted && result ? (
|
||||
<div className="built-in-commands-modal__return-button">
|
||||
<Button
|
||||
light
|
||||
iconName="arrow-left-double"
|
||||
iconSize="lg"
|
||||
onClick={() => {
|
||||
onReturn()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="built-in-commands-modal__input-field">
|
||||
<Input
|
||||
name="built-in-commands"
|
||||
type={pendingInput?.type || 'text'}
|
||||
placeholder={
|
||||
pendingInput?.placeholder || 'Type a built-in command'
|
||||
}
|
||||
maxLength={2_048}
|
||||
iconName={pendingInputIcon?.iconName}
|
||||
iconType={pendingInputIcon ? pendingInputIcon.type : undefined}
|
||||
iconSVG={pendingInput ? undefined : SLASH_COMMAND_ICON_SVG}
|
||||
iconSize="lg"
|
||||
value={commandValue}
|
||||
autofocus={isOpen}
|
||||
inputRef={inputRef}
|
||||
onChange={onCommandChange}
|
||||
/>
|
||||
</div>
|
||||
</Flexbox>
|
||||
</div>
|
||||
<div className="built-in-commands-modal__section built-in-commands-modal__section--middle">
|
||||
<div className="built-in-commands-modal__scroll-area">
|
||||
<ScrollContainer orientation="vertical" height="100%">
|
||||
{isLoading ? (
|
||||
<LoadingState loadingMessage={loadingMessage} />
|
||||
) : result ? (
|
||||
<BuiltInCommandResultRenderer result={result} />
|
||||
) : suggestions.length > 0 ? (
|
||||
<Flexbox gap="md">
|
||||
{isCommandInputEmpty && recentSuggestions.length > 0 ? (
|
||||
<SuggestionList
|
||||
headerTitle="Recently Used Commands"
|
||||
suggestions={recentSuggestions}
|
||||
selectedSuggestionIndex={recentSelectedSuggestionIndex}
|
||||
onSuggestionSelect={onSuggestionSelect}
|
||||
/>
|
||||
) : null}
|
||||
<SuggestionList
|
||||
headerTitle={
|
||||
isCommandInputEmpty ? 'All Commands' : 'Suggested Commands'
|
||||
}
|
||||
suggestions={suggestions}
|
||||
selectedSuggestionIndex={suggestionSelectedSuggestionIndex}
|
||||
onSuggestionSelect={onSuggestionSelect}
|
||||
/>
|
||||
</Flexbox>
|
||||
) : (
|
||||
<EmptyState />
|
||||
)}
|
||||
</ScrollContainer>
|
||||
</div>
|
||||
</div>
|
||||
<div className="built-in-commands-modal__section built-in-commands-modal__section--bottom">
|
||||
<Flexbox flexDirection="row" gap="md">
|
||||
<Text fontSize="xs" secondary>
|
||||
<kbd>↑</kbd> <kbd>↓</kbd> navigate
|
||||
</Text>
|
||||
<Text fontSize="xs" secondary>
|
||||
<kbd>tab</kbd> complete
|
||||
</Text>
|
||||
<Text fontSize="xs" secondary>
|
||||
<kbd>enter</kbd> submit
|
||||
</Text>
|
||||
<Text fontSize="xs" secondary>
|
||||
<kbd>esc</kbd> close
|
||||
</Text>
|
||||
</Flexbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import {
|
||||
Flexbox,
|
||||
Icon,
|
||||
Link,
|
||||
List,
|
||||
ListHeader,
|
||||
ListItem,
|
||||
Text
|
||||
} from '@aurora'
|
||||
|
||||
function InlineLabel({ item }) {
|
||||
if (item.inline_link_label && item.inline_link_href) {
|
||||
return (
|
||||
<Text>
|
||||
{item.label}{' '}
|
||||
<Link href={item.inline_link_href} fontSize="md">
|
||||
{item.inline_link_label}
|
||||
</Link>.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
return <Text>{item.label}</Text>
|
||||
}
|
||||
|
||||
function ToneListItem({ item }) {
|
||||
if (item.tone === 'success') {
|
||||
return (
|
||||
<ListItem>
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="sm">
|
||||
<Icon
|
||||
iconName="check"
|
||||
size="sm"
|
||||
type="fill"
|
||||
bgShape="circle"
|
||||
color="green"
|
||||
bgColor="transparent-green"
|
||||
/>
|
||||
<InlineLabel item={item} />
|
||||
</Flexbox>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.tone === 'warning') {
|
||||
return (
|
||||
<ListItem>
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="sm">
|
||||
<Icon
|
||||
iconName="alert"
|
||||
size="sm"
|
||||
type="fill"
|
||||
bgShape="circle"
|
||||
color="yellow"
|
||||
bgColor="transparent-yellow"
|
||||
/>
|
||||
<InlineLabel item={item} />
|
||||
</Flexbox>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.tone === 'error') {
|
||||
return (
|
||||
<ListItem>
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="sm">
|
||||
<Icon
|
||||
iconName="close"
|
||||
size="sm"
|
||||
type="fill"
|
||||
bgShape="circle"
|
||||
color="red"
|
||||
bgColor="transparent-red"
|
||||
/>
|
||||
<InlineLabel item={item} />
|
||||
</Flexbox>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.value || item.description) {
|
||||
return (
|
||||
<ListItem>
|
||||
<div className="built-in-commands-modal__result-item">
|
||||
<div className="built-in-commands-modal__result-copy">
|
||||
<Text fontWeight="semi-bold">{item.label}</Text>
|
||||
{item.description ? <Text secondary>{item.description}</Text> : null}
|
||||
</div>
|
||||
{item.value ? (
|
||||
<div className="built-in-commands-modal__result-value">
|
||||
{item.href ? (
|
||||
<Link href={item.href} fontSize="md">
|
||||
{item.value}
|
||||
</Link>
|
||||
) : (
|
||||
<Text secondary textAlign="right">
|
||||
{item.value}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.inline_link_label && item.inline_link_href) {
|
||||
return (
|
||||
<ListItem>
|
||||
<InlineLabel item={item} />
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
<InlineLabel item={item} />
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
export function BuiltInCommandResultRenderer({ result }) {
|
||||
const blocks = Array.isArray(result?.blocks) ? result.blocks : []
|
||||
|
||||
return (
|
||||
<Flexbox flexDirection="column" gap="sm">
|
||||
{blocks.map((block, index) => {
|
||||
if (block.type !== 'list') {
|
||||
return null
|
||||
}
|
||||
|
||||
const header = block.header || (index === 0 ? result.title : '')
|
||||
|
||||
return (
|
||||
<List key={`result-block-${index}`}>
|
||||
{header ? <ListHeader>{header}</ListHeader> : null}
|
||||
{block.items.map((item, itemIndex) => (
|
||||
<ToneListItem item={item} key={`result-item-${itemIndex}`} />
|
||||
))}
|
||||
</List>
|
||||
)
|
||||
})}
|
||||
</Flexbox>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,50 @@
|
||||
const MAXIMUM_HEIGHT_TO_SHOW_SEE_MORE = 340
|
||||
import { createElement } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import axios from 'axios'
|
||||
|
||||
import { WidgetWrapper, Flexbox, Loader, Text } from '@aurora'
|
||||
|
||||
import renderAuroraComponent from './render-aurora-component'
|
||||
import ToolUIHandler from './tool-ui-handler'
|
||||
|
||||
const WIDGETS_TO_FETCH = []
|
||||
const WIDGETS_FETCH_CACHE = new Map()
|
||||
const REPLACED_MESSAGES = new Set()
|
||||
const AUTO_SCROLL_BOTTOM_THRESHOLD_PX = 24
|
||||
const MAXIMUM_BUBBLES_IN_MEMORY = 62
|
||||
const MAXIMUM_WIDGET_FETCH_CONCURRENCY = 4
|
||||
const SECONDS_PER_MINUTE = 60
|
||||
|
||||
function escapeHTML(value) {
|
||||
return String(value || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
export default class Chatbot {
|
||||
constructor() {
|
||||
constructor(socket, serverURL, sessionId = null) {
|
||||
this.socket = socket
|
||||
this.serverURL = serverURL
|
||||
this.sessionId = sessionId
|
||||
this.et = new EventTarget()
|
||||
this.feed = document.querySelector('#feed')
|
||||
this.typing = document.querySelector('#is-typing')
|
||||
this.noBubbleMessage = document.querySelector('#no-bubble')
|
||||
this.bubbles = localStorage.getItem('bubbles')
|
||||
this.parsedBubbles = JSON.parse(this.bubbles)
|
||||
this.parsedBubbles = []
|
||||
this.reasoningBlocks = new Map()
|
||||
this.feedAutoScrollEnabled = true
|
||||
this.isProgrammaticFeedScroll = false
|
||||
this.widgetHydrationPromise = null
|
||||
|
||||
// Initialize tool UI handler
|
||||
this.toolUIHandler = new ToolUIHandler(
|
||||
this.feed,
|
||||
this.scrollDown.bind(this),
|
||||
this.formatMessage.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -15,17 +52,49 @@ export default class Chatbot {
|
||||
this.scrollDown()
|
||||
|
||||
this.et.addEventListener('to-leon', (event) => {
|
||||
this.createBubble('me', event.detail)
|
||||
this.createBubble({
|
||||
who: 'me',
|
||||
string: event.detail.string,
|
||||
sentAt: event.detail.sentAt
|
||||
})
|
||||
})
|
||||
|
||||
this.et.addEventListener('me-received', (event) => {
|
||||
this.createBubble('leon', event.detail)
|
||||
this.createBubble({
|
||||
who: 'leon',
|
||||
string: event.detail
|
||||
})
|
||||
})
|
||||
|
||||
// Add event delegation for clickable paths
|
||||
this.feed.addEventListener('click', (event) => {
|
||||
if (event.target.classList.contains('clickable-path')) {
|
||||
const path = event.target.getAttribute('data-path')
|
||||
if (path) {
|
||||
this.openPath(path)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.feed.addEventListener(
|
||||
'scroll',
|
||||
() => {
|
||||
this.handleFeedScroll()
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
}
|
||||
|
||||
sendTo(who, string) {
|
||||
sendTo(who, string, sentAt = Date.now()) {
|
||||
if (who === 'leon') {
|
||||
this.et.dispatchEvent(new CustomEvent('to-leon', { detail: string }))
|
||||
this.et.dispatchEvent(
|
||||
new CustomEvent('to-leon', {
|
||||
detail: {
|
||||
string,
|
||||
sentAt
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,79 +126,832 @@ export default class Chatbot {
|
||||
}
|
||||
}
|
||||
|
||||
scrollDown() {
|
||||
this.feed.scrollTo(0, this.feed.scrollHeight)
|
||||
isElementNearBottom(element) {
|
||||
if (!element) {
|
||||
return true
|
||||
}
|
||||
|
||||
const remainingScrollableDistance =
|
||||
element.scrollHeight - (element.scrollTop + element.clientHeight)
|
||||
|
||||
return remainingScrollableDistance <= AUTO_SCROLL_BOTTOM_THRESHOLD_PX
|
||||
}
|
||||
|
||||
loadFeed() {
|
||||
/**
|
||||
* TODO: widget: load widget from local storage
|
||||
*/
|
||||
return new Promise((resolve) => {
|
||||
if (this.parsedBubbles === null || this.parsedBubbles.length === 0) {
|
||||
this.noBubbleMessage.classList.remove('hide')
|
||||
localStorage.setItem('bubbles', JSON.stringify([]))
|
||||
this.parsedBubbles = []
|
||||
resolve()
|
||||
} else {
|
||||
for (let i = 0; i < this.parsedBubbles.length; i += 1) {
|
||||
const bubble = this.parsedBubbles[i]
|
||||
handleFeedScroll() {
|
||||
if (!this.feed || this.isProgrammaticFeedScroll) {
|
||||
return
|
||||
}
|
||||
|
||||
this.createBubble(bubble.who, bubble.string, false)
|
||||
this.feedAutoScrollEnabled = this.isElementNearBottom(this.feed)
|
||||
}
|
||||
|
||||
if (i + 1 === this.parsedBubbles.length) {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
scrollDown(options = {}) {
|
||||
if (!this.feed) {
|
||||
return
|
||||
}
|
||||
|
||||
const { force = false } = options
|
||||
|
||||
if (!force && !this.feedAutoScrollEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isProgrammaticFeedScroll = true
|
||||
this.feed.scrollTo(0, this.feed.scrollHeight)
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.isProgrammaticFeedScroll = false
|
||||
this.feedAutoScrollEnabled = this.isElementNearBottom(this.feed)
|
||||
})
|
||||
}
|
||||
|
||||
createBubble(who, string, save = true) {
|
||||
const container = document.createElement('div')
|
||||
const bubble = document.createElement('p')
|
||||
|
||||
container.className = `bubble-container ${who}`
|
||||
bubble.className = 'bubble'
|
||||
bubble.innerHTML = string
|
||||
|
||||
this.feed.appendChild(container).appendChild(bubble)
|
||||
|
||||
if (container.clientHeight > MAXIMUM_HEIGHT_TO_SHOW_SEE_MORE) {
|
||||
bubble.style.maxHeight = `${MAXIMUM_HEIGHT_TO_SHOW_SEE_MORE}px`
|
||||
const showMore = document.createElement('p')
|
||||
const showMoreText = 'Show more'
|
||||
|
||||
showMore.className = 'show-more'
|
||||
showMore.innerHTML = showMoreText
|
||||
|
||||
container.appendChild(showMore)
|
||||
|
||||
showMore.addEventListener('click', () => {
|
||||
bubble.classList.toggle('show-all')
|
||||
showMore.innerHTML =
|
||||
showMore.innerHTML === showMoreText ? 'Show less' : showMoreText
|
||||
})
|
||||
scrollReasoningContentToBottom(reasoningBlock) {
|
||||
if (!reasoningBlock?.content || !reasoningBlock.isAutoScrollEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (save) {
|
||||
this.saveBubble(who, string)
|
||||
reasoningBlock.isProgrammaticScroll = true
|
||||
reasoningBlock.content.scrollTop = reasoningBlock.content.scrollHeight
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
reasoningBlock.isProgrammaticScroll = false
|
||||
reasoningBlock.isAutoScrollEnabled = this.isElementNearBottom(
|
||||
reasoningBlock.content
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
getWidgetPayload(formattedString) {
|
||||
if (
|
||||
typeof formattedString !== 'string' ||
|
||||
!formattedString.includes('"component":"WidgetWrapper"')
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(formattedString)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
saveBubble(who, string) {
|
||||
getPlanWidgetInsertionPoint(widgetPayload) {
|
||||
if (!widgetPayload || widgetPayload.widget !== 'PlanWidget') {
|
||||
return null
|
||||
}
|
||||
|
||||
// Always append new plan widgets as new bubbles.
|
||||
// Widget updates are handled via replaceMessageId targeting the same
|
||||
// messageId, so insertion-point heuristics are unnecessary and can cause
|
||||
// visual reuse across turns.
|
||||
return null
|
||||
}
|
||||
|
||||
isPlanWidgetData(data) {
|
||||
return Boolean(data && typeof data === 'object' && data.widget === 'PlanWidget')
|
||||
}
|
||||
|
||||
isSystemWidgetData(data) {
|
||||
return Boolean(
|
||||
data &&
|
||||
typeof data === 'object' &&
|
||||
data.historyMode &&
|
||||
data.historyMode === 'system_widget'
|
||||
)
|
||||
}
|
||||
|
||||
getTimelineItemWeight(item) {
|
||||
if (item.who === 'owner') {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (item.source === 'system_widget') {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 2
|
||||
}
|
||||
|
||||
async hydrateFetchedWidgets() {
|
||||
if (this.widgetHydrationPromise) {
|
||||
return this.widgetHydrationPromise
|
||||
}
|
||||
|
||||
const widgetContainers = [...WIDGETS_TO_FETCH].reverse()
|
||||
WIDGETS_TO_FETCH.length = 0
|
||||
|
||||
if (widgetContainers.length === 0) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const hydrateWidgetContainer = async (widgetContainer) => {
|
||||
const hasWidgetBeenFetched = WIDGETS_FETCH_CACHE.has(
|
||||
widgetContainer.widgetId
|
||||
)
|
||||
|
||||
if (hasWidgetBeenFetched) {
|
||||
const fetchedWidget = WIDGETS_FETCH_CACHE.get(widgetContainer.widgetId)
|
||||
widgetContainer.reactRootNode.render(fetchedWidget.reactNode)
|
||||
|
||||
setTimeout(() => {
|
||||
this.scrollDown()
|
||||
}, 100)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const data = await axios.get(
|
||||
`${this.serverURL}/api/v1/fetch-widget?skill_action=${widgetContainer.onFetch.actionName}&widget_id=${widgetContainer.widgetId}${this.sessionId ? `&session_id=${encodeURIComponent(this.sessionId)}` : ''}`
|
||||
)
|
||||
const fetchedWidget = data.data.widget
|
||||
const reactNode = fetchedWidget
|
||||
? renderAuroraComponent(
|
||||
this.socket,
|
||||
fetchedWidget.componentTree,
|
||||
fetchedWidget.supportedEvents
|
||||
)
|
||||
: createElement(WidgetWrapper, {
|
||||
children: createElement(Flexbox, {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
children: createElement(Text, {
|
||||
secondary: true,
|
||||
children: 'This widget has been deleted.'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
widgetContainer.reactRootNode.render(reactNode)
|
||||
WIDGETS_FETCH_CACHE.set(widgetContainer.widgetId, {
|
||||
...fetchedWidget,
|
||||
reactNode
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.scrollDown()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const workerCount = Math.min(
|
||||
MAXIMUM_WIDGET_FETCH_CONCURRENCY,
|
||||
widgetContainers.length
|
||||
)
|
||||
let currentIndex = 0
|
||||
|
||||
this.widgetHydrationPromise = Promise.all(
|
||||
Array.from({ length: workerCount }, async () => {
|
||||
while (currentIndex < widgetContainers.length) {
|
||||
const widgetContainer = widgetContainers[currentIndex]
|
||||
currentIndex += 1
|
||||
|
||||
if (!widgetContainer) {
|
||||
continue
|
||||
}
|
||||
|
||||
await hydrateWidgetContainer(widgetContainer)
|
||||
}
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
console.error('Failed to hydrate fetched widgets:', error)
|
||||
})
|
||||
.finally(() => {
|
||||
this.widgetHydrationPromise = null
|
||||
})
|
||||
|
||||
return this.widgetHydrationPromise
|
||||
}
|
||||
|
||||
setSessionId(sessionId) {
|
||||
this.sessionId = sessionId
|
||||
}
|
||||
|
||||
resetFeed() {
|
||||
WIDGETS_TO_FETCH.length = 0
|
||||
this.feed.innerHTML = ''
|
||||
this.noBubbleMessage = document.createElement('p')
|
||||
this.noBubbleMessage.id = 'no-bubble'
|
||||
this.noBubbleMessage.className = 'hide'
|
||||
this.noBubbleMessage.textContent =
|
||||
'You can start to interact with me, don\'t be shy.'
|
||||
this.feed.appendChild(this.noBubbleMessage)
|
||||
this.parsedBubbles = []
|
||||
this.reasoningBlocks.clear()
|
||||
this.feedAutoScrollEnabled = true
|
||||
}
|
||||
|
||||
async loadFeed() {
|
||||
WIDGETS_TO_FETCH.length = 0
|
||||
this.resetFeed()
|
||||
const sessionQuery = this.sessionId
|
||||
? `&session_id=${encodeURIComponent(this.sessionId)}`
|
||||
: ''
|
||||
|
||||
const [historyResponse, systemWidgetsResponse] = await Promise.all([
|
||||
axios.get(
|
||||
`${this.serverURL}/api/v1/conversation-history?supports_widgets=true${sessionQuery}`
|
||||
),
|
||||
axios.get(
|
||||
`${this.serverURL}/api/v1/system-widgets?supports_widgets=true${sessionQuery}`
|
||||
)
|
||||
])
|
||||
const history = Array.isArray(historyResponse.data?.history)
|
||||
? historyResponse.data.history
|
||||
: []
|
||||
const systemWidgets = Array.isArray(systemWidgetsResponse.data?.widgets)
|
||||
? systemWidgetsResponse.data.widgets
|
||||
: []
|
||||
|
||||
const timelineItems = [...history, ...systemWidgets]
|
||||
.map((item, index) => ({
|
||||
...item,
|
||||
sortIndex: index
|
||||
}))
|
||||
.sort((left, right) => {
|
||||
if (left.sentAt !== right.sentAt) {
|
||||
return left.sentAt - right.sentAt
|
||||
}
|
||||
|
||||
const leftWeight = this.getTimelineItemWeight(left)
|
||||
const rightWeight = this.getTimelineItemWeight(right)
|
||||
|
||||
if (leftWeight !== rightWeight) {
|
||||
return leftWeight - rightWeight
|
||||
}
|
||||
|
||||
return left.sortIndex - right.sortIndex
|
||||
})
|
||||
|
||||
this.parsedBubbles = history
|
||||
|
||||
if (timelineItems.length === 0) {
|
||||
this.noBubbleMessage.classList.remove('hide')
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < timelineItems.length; i += 1) {
|
||||
const bubble = timelineItems[i]
|
||||
|
||||
if (
|
||||
bubble.originalString &&
|
||||
ToolUIHandler.isToolOutputMarker(bubble.originalString)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
this.createBubble({
|
||||
who: bubble.who === 'owner' ? 'me' : bubble.who,
|
||||
string: bubble.originalString ? bubble.originalString : bubble.string,
|
||||
metrics: bubble.llmMetrics || null,
|
||||
sentAt: bubble.sentAt,
|
||||
save: false,
|
||||
isCreatingFromLoadingFeed: true,
|
||||
messageId: bubble.messageId
|
||||
})
|
||||
}
|
||||
|
||||
void this.hydrateFetchedWidgets()
|
||||
}
|
||||
|
||||
createBubble(params) {
|
||||
const {
|
||||
who,
|
||||
string,
|
||||
metrics = null,
|
||||
save = true,
|
||||
bubbleId,
|
||||
isCreatingFromLoadingFeed = false,
|
||||
messageId,
|
||||
sentAt = null,
|
||||
beforeElement = null
|
||||
} = params
|
||||
const container = document.createElement('div')
|
||||
const bubble = document.createElement('p')
|
||||
|
||||
if (!this.noBubbleMessage.classList.contains('hide')) {
|
||||
this.noBubbleMessage.classList.add('hide')
|
||||
}
|
||||
|
||||
if (this.parsedBubbles.length === 62) {
|
||||
container.className = `bubble-container ${who}`
|
||||
bubble.className = 'bubble'
|
||||
|
||||
if (messageId) {
|
||||
container.setAttribute('data-message-id', messageId)
|
||||
}
|
||||
|
||||
// Store original string before formatting
|
||||
const originalString = string
|
||||
const formattedString = this.formatMessage(string)
|
||||
const widgetPayload = this.getWidgetPayload(formattedString)
|
||||
const autoPlanInsertionPoint = this.getPlanWidgetInsertionPoint(widgetPayload)
|
||||
const resolvedBeforeElement = beforeElement || autoPlanInsertionPoint
|
||||
|
||||
bubble.innerHTML = formattedString
|
||||
|
||||
if (bubbleId) {
|
||||
container.classList.add(bubbleId)
|
||||
}
|
||||
|
||||
if (resolvedBeforeElement && resolvedBeforeElement.parentNode === this.feed) {
|
||||
this.feed.insertBefore(container, resolvedBeforeElement)
|
||||
} else {
|
||||
this.feed.appendChild(container)
|
||||
}
|
||||
container.appendChild(bubble)
|
||||
|
||||
if (who === 'leon' && metrics) {
|
||||
container.appendChild(this.createMetricsElement(metrics, sentAt))
|
||||
} else if (who === 'me') {
|
||||
const timestampElement = this.createTimestampElement(sentAt)
|
||||
|
||||
if (timestampElement) {
|
||||
container.appendChild(timestampElement)
|
||||
}
|
||||
}
|
||||
|
||||
let widgetComponentTree = null
|
||||
let widgetSupportedEvents = null
|
||||
|
||||
/**
|
||||
* Widget rendering
|
||||
*/
|
||||
if (
|
||||
formattedString.includes &&
|
||||
formattedString.includes('"component":"WidgetWrapper"')
|
||||
) {
|
||||
const parsedWidget = widgetPayload || JSON.parse(formattedString)
|
||||
container.setAttribute('data-widget-id', parsedWidget.id)
|
||||
|
||||
/**
|
||||
* On widget fetching, render the loader
|
||||
*/
|
||||
if (isCreatingFromLoadingFeed && parsedWidget.onFetch) {
|
||||
const root = createRoot(container)
|
||||
|
||||
root.render(
|
||||
createElement(WidgetWrapper, {
|
||||
children: createElement(Flexbox, {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
children: createElement(Loader)
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
WIDGETS_TO_FETCH.push({
|
||||
reactRootNode: root,
|
||||
widgetId: parsedWidget.id,
|
||||
onFetch: parsedWidget.onFetch
|
||||
})
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
widgetComponentTree = parsedWidget.componentTree
|
||||
widgetSupportedEvents = parsedWidget.supportedEvents
|
||||
|
||||
/**
|
||||
* On widget creation
|
||||
*/
|
||||
const root = createRoot(container)
|
||||
|
||||
const reactNode = renderAuroraComponent(
|
||||
this.socket,
|
||||
widgetComponentTree,
|
||||
widgetSupportedEvents
|
||||
)
|
||||
|
||||
root.render(reactNode)
|
||||
}
|
||||
|
||||
if (save) {
|
||||
this.saveBubble(who, originalString, formattedString, messageId, metrics, sentAt)
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
formatReasoningPhaseTitle(phase) {
|
||||
const normalizedPhase =
|
||||
typeof phase === 'string' && phase.trim()
|
||||
? phase.replaceAll('_', ' ').toUpperCase()
|
||||
: 'EXECUTION'
|
||||
|
||||
return `REASONING - ${normalizedPhase}`
|
||||
}
|
||||
|
||||
createOrUpdateReasoningBlock(generationId, token, phase) {
|
||||
if (!generationId || !token) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!this.noBubbleMessage.classList.contains('hide')) {
|
||||
this.noBubbleMessage.classList.add('hide')
|
||||
}
|
||||
|
||||
let reasoningBlock = this.reasoningBlocks.get(generationId)
|
||||
|
||||
if (!reasoningBlock) {
|
||||
const container = document.createElement('div')
|
||||
const block = document.createElement('div')
|
||||
const header = document.createElement('div')
|
||||
const icon = document.createElement('i')
|
||||
const title = document.createElement('span')
|
||||
const content = document.createElement('div')
|
||||
|
||||
container.className = 'reasoning-block-container leon'
|
||||
container.setAttribute('data-reasoning-id', generationId)
|
||||
block.className = 'reasoning-block'
|
||||
header.className = 'reasoning-header'
|
||||
icon.className = 'ri-brain-ai-3-line reasoning-icon'
|
||||
title.className = 'reasoning-title'
|
||||
title.textContent = this.formatReasoningPhaseTitle(phase)
|
||||
content.className = 'reasoning-content'
|
||||
|
||||
header.appendChild(icon)
|
||||
header.appendChild(title)
|
||||
block.appendChild(header)
|
||||
block.appendChild(content)
|
||||
container.appendChild(block)
|
||||
this.feed.appendChild(container)
|
||||
|
||||
reasoningBlock = {
|
||||
container,
|
||||
content,
|
||||
text: '',
|
||||
isAutoScrollEnabled: true,
|
||||
isProgrammaticScroll: false
|
||||
}
|
||||
content.addEventListener(
|
||||
'scroll',
|
||||
() => {
|
||||
if (reasoningBlock.isProgrammaticScroll) {
|
||||
return
|
||||
}
|
||||
|
||||
reasoningBlock.isAutoScrollEnabled = this.isElementNearBottom(content)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
this.reasoningBlocks.set(generationId, reasoningBlock)
|
||||
}
|
||||
|
||||
reasoningBlock.text += token
|
||||
reasoningBlock.content.textContent = reasoningBlock.text
|
||||
this.scrollReasoningContentToBottom(reasoningBlock)
|
||||
|
||||
return reasoningBlock.container
|
||||
}
|
||||
|
||||
handleToolOutput(data) {
|
||||
const result = this.toolUIHandler.handleToolOutput(data)
|
||||
|
||||
// Save in memory if it's a new group
|
||||
if (result && result.isNewGroup) {
|
||||
const { toolkitName, toolName, answer } = data
|
||||
const toolInfo = this.toolUIHandler.getToolGroupInfo(
|
||||
result.groupId,
|
||||
toolkitName,
|
||||
toolName,
|
||||
answer
|
||||
)
|
||||
|
||||
this.saveBubble(
|
||||
'leon',
|
||||
toolInfo.originalString,
|
||||
toolInfo.formattedMessage,
|
||||
toolInfo.messageId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
saveBubble(
|
||||
who,
|
||||
originalString,
|
||||
string,
|
||||
messageId,
|
||||
metrics = null,
|
||||
sentAt = null
|
||||
) {
|
||||
if (!this.noBubbleMessage.classList.contains('hide')) {
|
||||
this.noBubbleMessage.classList.add('hide')
|
||||
}
|
||||
|
||||
if (this.parsedBubbles.length === MAXIMUM_BUBBLES_IN_MEMORY) {
|
||||
this.parsedBubbles.shift()
|
||||
}
|
||||
|
||||
this.parsedBubbles.push({ who, string })
|
||||
localStorage.setItem('bubbles', JSON.stringify(this.parsedBubbles))
|
||||
// Store both original and formatted strings
|
||||
this.parsedBubbles.push({
|
||||
who,
|
||||
sentAt,
|
||||
string,
|
||||
originalString,
|
||||
messageId,
|
||||
llmMetrics: metrics
|
||||
})
|
||||
this.scrollDown()
|
||||
}
|
||||
|
||||
formatMessage(message) {
|
||||
const isWidget =
|
||||
message.includes && message.includes('"component":"WidgetWrapper"')
|
||||
|
||||
if (typeof message === 'string' && !isWidget) {
|
||||
message = escapeHTML(message)
|
||||
message = message.replace(/\n/g, '<br />')
|
||||
|
||||
// Handle HTTP/HTTPS URLs with simple regex
|
||||
message = message.replace(/https?:\/\/[^\s<>"{}|\\^`[\]]+/gi, (match) => {
|
||||
return `<a href="${match}" target="_blank" rel="noopener noreferrer" class="clickable-url" title="Open URL in browser">${match}</a>`
|
||||
})
|
||||
|
||||
// Handle file paths with delimiters for exact matching
|
||||
message = message.replace(
|
||||
/\[FILE_PATH\](.*?)\[\/FILE_PATH\]/g,
|
||||
(match, filePath) => {
|
||||
return `<span class="clickable-path" data-path="${filePath}" title="Open in file explorer">${filePath}</span>`
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
formatMetricTimestamp(sentAt) {
|
||||
if (typeof sentAt !== 'number' || !Number.isFinite(sentAt)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const date = new Date(sentAt)
|
||||
const now = new Date()
|
||||
|
||||
if (Number.isNaN(date.getTime()) || Number.isNaN(now.getTime())) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const timeFormatter = new Intl.DateTimeFormat(undefined, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
const formattedTime = timeFormatter.format(date)
|
||||
const startOfToday = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate()
|
||||
)
|
||||
const startOfTargetDay = new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate()
|
||||
)
|
||||
const dayDifference = Math.round(
|
||||
(startOfToday.getTime() - startOfTargetDay.getTime()) / 86_400_000
|
||||
)
|
||||
|
||||
if (dayDifference === 0) {
|
||||
return `Today, ${formattedTime}`
|
||||
}
|
||||
|
||||
if (dayDifference === 1) {
|
||||
return `Yesterday, ${formattedTime}`
|
||||
}
|
||||
|
||||
if (dayDifference > 1 && dayDifference < 7) {
|
||||
const weekdayFormatter = new Intl.DateTimeFormat(undefined, {
|
||||
weekday: 'long'
|
||||
})
|
||||
|
||||
return `${weekdayFormatter.format(date)}, ${formattedTime}`
|
||||
}
|
||||
|
||||
const monthDayFormatter = new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
})
|
||||
|
||||
return `${monthDayFormatter.format(date)}, ${formattedTime}`
|
||||
}
|
||||
|
||||
formatFullMetricTimestamp(sentAt) {
|
||||
if (typeof sentAt !== 'number' || !Number.isFinite(sentAt)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const date = new Date(sentAt)
|
||||
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
formatTimestampMarkup(sentAt) {
|
||||
const formattedTimestamp = this.formatMetricTimestamp(sentAt)
|
||||
const fullFormattedTimestamp = this.formatFullMetricTimestamp(sentAt)
|
||||
|
||||
if (!formattedTimestamp) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return `
|
||||
<span class="bubble-metric-item">
|
||||
<i class="ri-time-line" aria-hidden="true"></i>
|
||||
<span title="${fullFormattedTimestamp || formattedTimestamp}">${formattedTimestamp}</span>
|
||||
</span>
|
||||
`.trim()
|
||||
}
|
||||
|
||||
formatTurnDuration(durationMs) {
|
||||
const durationSeconds = Number(durationMs || 0) / 1_000
|
||||
const roundedDurationSeconds = Math.max(0, Number(durationSeconds.toFixed(1)))
|
||||
|
||||
if (roundedDurationSeconds < SECONDS_PER_MINUTE) {
|
||||
return `${roundedDurationSeconds.toFixed(1)}s`
|
||||
}
|
||||
|
||||
const minutes = Math.floor(roundedDurationSeconds / SECONDS_PER_MINUTE)
|
||||
const seconds = roundedDurationSeconds % SECONDS_PER_MINUTE
|
||||
|
||||
return `${minutes}m${seconds.toFixed(1)}s`
|
||||
}
|
||||
|
||||
formatMetrics(metrics, sentAt = null) {
|
||||
if (!metrics) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const inputTokens = Number(metrics.inputTokens || 0)
|
||||
const outputTokens = Number(metrics.outputTokens || 0)
|
||||
const totalTokens = Number(metrics.totalTokens || inputTokens + outputTokens)
|
||||
const turnDuration = this.formatTurnDuration(metrics.durationMs)
|
||||
const tokensPerSecond = Number(
|
||||
metrics.tokensPerSecond || metrics.averagedPhaseTokensPerSecond || 0
|
||||
)
|
||||
const tokenFormatter = new Intl.NumberFormat()
|
||||
const timestampMarkup = this.formatTimestampMarkup(sentAt)
|
||||
|
||||
return `
|
||||
<span class="bubble-metric-item">
|
||||
<i class="ri-copper-coin-line" aria-hidden="true"></i>
|
||||
<span>${tokenFormatter.format(totalTokens)} (i:${tokenFormatter.format(inputTokens)}/o:${tokenFormatter.format(outputTokens)}) tok</span>
|
||||
</span>
|
||||
<span class="bubble-metric-item">
|
||||
<i class="ri-timer-flash-line" aria-hidden="true"></i>
|
||||
<span>${turnDuration}</span>
|
||||
</span>
|
||||
<span class="bubble-metric-item">
|
||||
<i class="ri-flashlight-line" aria-hidden="true"></i>
|
||||
<span>${tokensPerSecond.toFixed(2)} t/s</span>
|
||||
</span>
|
||||
${timestampMarkup}
|
||||
`.trim()
|
||||
}
|
||||
|
||||
createMetricsElement(metrics, sentAt = null) {
|
||||
const metricsElement = document.createElement('div')
|
||||
|
||||
metricsElement.className = 'bubble-metrics'
|
||||
metricsElement.innerHTML = this.formatMetrics(metrics, sentAt)
|
||||
|
||||
return metricsElement
|
||||
}
|
||||
|
||||
createTimestampElement(sentAt) {
|
||||
const timestampMarkup = this.formatTimestampMarkup(sentAt)
|
||||
|
||||
if (!timestampMarkup) {
|
||||
return null
|
||||
}
|
||||
|
||||
const timestampElement = document.createElement('div')
|
||||
|
||||
timestampElement.className = 'bubble-metrics'
|
||||
timestampElement.innerHTML = timestampMarkup
|
||||
|
||||
return timestampElement
|
||||
}
|
||||
|
||||
updateBubbleMetrics(container, metrics, sentAt = null) {
|
||||
if (!container) {
|
||||
return
|
||||
}
|
||||
|
||||
const existingMetricsElement = container.querySelector('.bubble-metrics')
|
||||
|
||||
if (!metrics) {
|
||||
if (existingMetricsElement) {
|
||||
existingMetricsElement.remove()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (existingMetricsElement) {
|
||||
existingMetricsElement.innerHTML = this.formatMetrics(metrics, sentAt)
|
||||
return
|
||||
}
|
||||
|
||||
container.appendChild(this.createMetricsElement(metrics, sentAt))
|
||||
}
|
||||
|
||||
getLatestReasoningContainer() {
|
||||
const reasoningContainers = this.feed.querySelectorAll(
|
||||
'.reasoning-block-container'
|
||||
)
|
||||
|
||||
if (reasoningContainers.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return reasoningContainers[reasoningContainers.length - 1] || null
|
||||
}
|
||||
|
||||
replaceMessage(replaceMessageId, newData) {
|
||||
const existingBubble = document.querySelector(
|
||||
`[data-message-id="${replaceMessageId}"]`
|
||||
)
|
||||
const isPlanWidget = this.isPlanWidgetData(newData)
|
||||
const nextSibling = existingBubble ? existingBubble.nextSibling : null
|
||||
|
||||
if (existingBubble) {
|
||||
existingBubble.remove()
|
||||
|
||||
const bubbleIndex = this.parsedBubbles.findIndex(
|
||||
(bubble) => bubble.messageId === replaceMessageId
|
||||
)
|
||||
if (bubbleIndex !== -1) {
|
||||
this.parsedBubbles.splice(bubbleIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const isTextAnswerPayload = Boolean(
|
||||
newData &&
|
||||
typeof newData === 'object' &&
|
||||
typeof newData.answer === 'string' &&
|
||||
!newData.widget &&
|
||||
!newData.componentTree
|
||||
)
|
||||
const bubbleString = isTextAnswerPayload
|
||||
? newData.answer
|
||||
: typeof newData === 'string'
|
||||
? newData
|
||||
: JSON.stringify(newData)
|
||||
const metrics =
|
||||
isTextAnswerPayload && newData.llmMetrics ? newData.llmMetrics : null
|
||||
|
||||
const shouldSaveMessage = !this.isSystemWidgetData(newData)
|
||||
const beforeElement = isPlanWidget ? null : nextSibling
|
||||
|
||||
this.createBubble({
|
||||
who: 'leon',
|
||||
string: bubbleString,
|
||||
save: shouldSaveMessage,
|
||||
messageId: replaceMessageId,
|
||||
beforeElement,
|
||||
metrics,
|
||||
sentAt:
|
||||
newData && typeof newData === 'object' && typeof newData.sentAt === 'number'
|
||||
? newData.sentAt
|
||||
: Date.now()
|
||||
})
|
||||
|
||||
/**
|
||||
* Only scroll down on the first replacement of this message
|
||||
* to avoid repeating scrolling for every message replacement
|
||||
*/
|
||||
if (!REPLACED_MESSAGES.has(replaceMessageId)) {
|
||||
REPLACED_MESSAGES.add(replaceMessageId)
|
||||
this.scrollDown()
|
||||
}
|
||||
}
|
||||
|
||||
openPath(filePath) {
|
||||
// Send request to server to open the file path in system file explorer
|
||||
fetch(`${this.serverURL}/api/v1/open-path`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ path: filePath })
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (!data.success) {
|
||||
console.error('Failed to open path:', data.error)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error opening path:', error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
import { io } from 'socket.io-client'
|
||||
import React from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { Button } from './aurora/button'
|
||||
import Chatbot from './chatbot'
|
||||
import { INIT_MESSAGES } from './constants'
|
||||
import VoiceEnergy from './voice-energy'
|
||||
import { ASR_DISABLED_MESSAGE, INIT_MESSAGES } from './constants'
|
||||
import handleSuggestions from './suggestion-handler.js'
|
||||
|
||||
export default class Client {
|
||||
constructor(client, serverUrl, input, res) {
|
||||
constructor(client, serverUrl, input, options = {}) {
|
||||
this.client = client
|
||||
this._input = input
|
||||
this._suggestionContainer = document.querySelector('#suggestions-container')
|
||||
this.voiceSpeechElement = document.querySelector('#voice-speech')
|
||||
this.serverUrl = serverUrl
|
||||
this.socket = io(this.serverUrl)
|
||||
this.activeSessionId = options.activeSessionId || null
|
||||
this.history = localStorage.getItem('history')
|
||||
this.parsedHistory = []
|
||||
this.info = res
|
||||
this.chatbot = new Chatbot()
|
||||
this.chatbot = new Chatbot(this.socket, this.serverUrl, this.activeSessionId)
|
||||
this.voiceEnergy = new VoiceEnergy(this)
|
||||
this._recorder = {}
|
||||
this._suggestions = []
|
||||
this._answerGenerationId = 'xxx'
|
||||
this._activeStreamGenerationId = null
|
||||
this._ttsAudioContext = null
|
||||
this._isLeonGeneratingAnswer = false
|
||||
this._isVoiceModeEnabled = false
|
||||
this._hasSentInitMessages = false
|
||||
this._chatbotInitPromise = null
|
||||
// this._ttsAudioContextes = {}
|
||||
}
|
||||
|
||||
set input(newInput) {
|
||||
@@ -27,6 +35,10 @@ export default class Client {
|
||||
}
|
||||
}
|
||||
|
||||
get input() {
|
||||
return this._input
|
||||
}
|
||||
|
||||
set recorder(recorder) {
|
||||
this._recorder = recorder
|
||||
}
|
||||
@@ -35,6 +47,33 @@ export default class Client {
|
||||
return this._recorder
|
||||
}
|
||||
|
||||
updateMood(mood) {
|
||||
const moodContainer = document.querySelector('#mood')
|
||||
|
||||
if (!moodContainer || !mood?.emoji || !mood?.type) {
|
||||
return
|
||||
}
|
||||
|
||||
moodContainer.textContent = `Leon's mood: ${mood.emoji}`
|
||||
moodContainer.setAttribute('title', mood.type)
|
||||
}
|
||||
|
||||
setSessionPanel(sessionPanel) {
|
||||
this.sessionPanel = sessionPanel
|
||||
}
|
||||
|
||||
async setActiveSession(sessionId) {
|
||||
if (!sessionId || sessionId === this.activeSessionId) {
|
||||
return
|
||||
}
|
||||
|
||||
this.activeSessionId = sessionId
|
||||
this.chatbot.setSessionId(sessionId)
|
||||
this.socket.emit('session-change', sessionId)
|
||||
await this.chatbot.loadFeed()
|
||||
this.chatbot.scrollDown({ force: true })
|
||||
}
|
||||
|
||||
async sendInitMessages() {
|
||||
for (let i = 0; i < INIT_MESSAGES.length; i++) {
|
||||
const messages = INIT_MESSAGES[i]
|
||||
@@ -53,35 +92,250 @@ export default class Client {
|
||||
}
|
||||
}
|
||||
|
||||
init(loader) {
|
||||
this.chatbot.init()
|
||||
setInitStatus(statusName, statusType) {
|
||||
window.leonInitStatusEvent.dispatchEvent(
|
||||
new CustomEvent('initStatusChange', {
|
||||
detail: {
|
||||
statusName,
|
||||
statusType
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
waitForInitUICompletion() {
|
||||
if (this._hasSentInitMessages || this.chatbot.parsedBubbles?.length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const trySendInitMessages = () => {
|
||||
const initializedInitElement = document.querySelector('#init .initialized')
|
||||
|
||||
if (!initializedInitElement) {
|
||||
return false
|
||||
}
|
||||
|
||||
this._hasSentInitMessages = true
|
||||
this.sendInitMessages()
|
||||
return true
|
||||
}
|
||||
|
||||
if (trySendInitMessages()) {
|
||||
return
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (trySendInitMessages()) {
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
asrStartRecording() {
|
||||
if (!window.leonConfigInfo.asr.enabled) {
|
||||
alert(ASR_DISABLED_MESSAGE)
|
||||
return
|
||||
}
|
||||
|
||||
if (!this._isVoiceModeEnabled) {
|
||||
this.enableVoiceMode()
|
||||
|
||||
this.voiceEnergy.status = 'listening'
|
||||
|
||||
this.socket.emit('asr-start-record')
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this._chatbotInitPromise = this.chatbot.init()
|
||||
this.voiceEnergy.init()
|
||||
|
||||
if (window.leonConfigInfo?.tcpServer?.enabled === false) {
|
||||
this.setInitStatus('tcpServerBoot', 'success')
|
||||
}
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
this.socket.emit('init', this.client)
|
||||
this.socket.emit('init', {
|
||||
client: this.client,
|
||||
sessionId: this.activeSessionId,
|
||||
capabilities: {
|
||||
supportsWidgets: true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Init status listeners
|
||||
*/
|
||||
this.socket.on('init-client-core-server-handshake', (status) => {
|
||||
this.setInitStatus('clientCoreServerHandshake', status)
|
||||
})
|
||||
this.socket.on('init-tcp-server-boot', (status) => {
|
||||
this.setInitStatus('tcpServerBoot', status)
|
||||
})
|
||||
this.socket.on('init-llama-server-boot', (status) => {
|
||||
this.setInitStatus('llamaServerBoot', status)
|
||||
})
|
||||
|
||||
this.socket.on('ready', () => {
|
||||
loader.stop()
|
||||
void this._chatbotInitPromise?.then(() => {
|
||||
setTimeout(() => {
|
||||
const body = document.querySelector('body')
|
||||
body.classList.remove('settingup')
|
||||
}, 250)
|
||||
|
||||
if (this.chatbot.parsedBubbles?.length === 0) {
|
||||
this.sendInitMessages()
|
||||
}
|
||||
this.waitForInitUICompletion()
|
||||
})
|
||||
})
|
||||
|
||||
this.socket.on('answer', (data) => {
|
||||
this.chatbot.receivedFrom('leon', data)
|
||||
/*if (this._isVoiceModeEnabled) {
|
||||
this.voiceEnergy.status = 'listening'
|
||||
}*/
|
||||
|
||||
// Leon has finished to answer
|
||||
this._isLeonGeneratingAnswer = false
|
||||
|
||||
const isPlanWidget =
|
||||
data && typeof data === 'object' && data.widget === 'PlanWidget'
|
||||
|
||||
if (isPlanWidget) {
|
||||
this.chatbot.isTyping('leon', false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle message replacement if replaceMessageId is provided
|
||||
*/
|
||||
if (data.replaceMessageId) {
|
||||
this.chatbot.replaceMessage(data.replaceMessageId, data)
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tool output messages
|
||||
*/
|
||||
if (data.isToolOutput) {
|
||||
this.chatbot.handleToolOutput(data)
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle widget data directly
|
||||
*/
|
||||
if (data.widget || data.componentTree) {
|
||||
const isSystemWidget = this.chatbot.isSystemWidgetData(data)
|
||||
// Pass the entire widget data as JSON string for chatbot.js to handle
|
||||
const widgetString =
|
||||
typeof data === 'string' ? data : JSON.stringify(data)
|
||||
|
||||
this.chatbot.createBubble({
|
||||
who: 'leon',
|
||||
string: widgetString,
|
||||
save: !isSystemWidget,
|
||||
messageId: data.widget?.id || data.id || `msg-${Date.now()}`
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const answerText = typeof data === 'string' ? data : data.answer
|
||||
const llmMetrics =
|
||||
data && typeof data === 'object' && data.llmMetrics
|
||||
? data.llmMetrics
|
||||
: null
|
||||
|
||||
/**
|
||||
* Just save the bubble if the newest bubble is from the streaming.
|
||||
* Otherwise, create a new bubble
|
||||
*/
|
||||
const streamGenerationId =
|
||||
this._activeStreamGenerationId || this._answerGenerationId
|
||||
const streamedBubbleContainerElement = streamGenerationId
|
||||
? document.querySelector(
|
||||
`.bubble-container.leon.${streamGenerationId}`
|
||||
)
|
||||
: null
|
||||
const isBubbleFromStreaming = Boolean(streamedBubbleContainerElement)
|
||||
|
||||
if (isBubbleFromStreaming && streamedBubbleContainerElement) {
|
||||
this.chatbot.saveBubble(
|
||||
'leon',
|
||||
answerText,
|
||||
answerText,
|
||||
null,
|
||||
llmMetrics,
|
||||
data && typeof data === 'object' && typeof data.sentAt === 'number'
|
||||
? data.sentAt
|
||||
: Date.now()
|
||||
)
|
||||
|
||||
// Slightly delay the update to avoid the stream animation to be interrupted
|
||||
setTimeout(() => {
|
||||
// Update the text of the bubble (quick emoji fix)
|
||||
streamedBubbleContainerElement.querySelector('p.bubble').innerHTML =
|
||||
this.chatbot.formatMessage(answerText)
|
||||
this.chatbot.updateBubbleMetrics(
|
||||
streamedBubbleContainerElement,
|
||||
llmMetrics,
|
||||
data && typeof data === 'object' && typeof data.sentAt === 'number'
|
||||
? data.sentAt
|
||||
: Date.now()
|
||||
)
|
||||
}, 2_500)
|
||||
} else {
|
||||
this.chatbot.createBubble({
|
||||
who: 'leon',
|
||||
string: answerText,
|
||||
save:
|
||||
!(
|
||||
data &&
|
||||
typeof data === 'object' &&
|
||||
data.historyMode === 'system_widget'
|
||||
),
|
||||
metrics: llmMetrics,
|
||||
messageId: data && typeof data === 'object' ? data.messageId : null,
|
||||
sentAt:
|
||||
data && typeof data === 'object' && typeof data.sentAt === 'number'
|
||||
? data.sentAt
|
||||
: Date.now()
|
||||
})
|
||||
}
|
||||
this.chatbot.scrollDown({ force: true })
|
||||
|
||||
this._activeStreamGenerationId = null
|
||||
this._answerGenerationId = 'xxx'
|
||||
void this.sessionPanel?.refresh()
|
||||
})
|
||||
|
||||
this.socket.on('suggest', (data) => {
|
||||
data?.forEach((suggestionText) => {
|
||||
setTimeout(() => {
|
||||
handleSuggestions(data, this.chatbot, this)
|
||||
}, 400)
|
||||
setTimeout(() => {
|
||||
this.chatbot.scrollDown()
|
||||
}, 450)
|
||||
/*data?.forEach((suggestionText) => {
|
||||
this.addSuggestion(suggestionText)
|
||||
})
|
||||
})*/
|
||||
})
|
||||
|
||||
this.socket.on('is-typing', (data) => {
|
||||
this.chatbot.isTyping('leon', data)
|
||||
})
|
||||
|
||||
this.socket.on('owner-utterance', (data) => {
|
||||
if (!data?.utterance) {
|
||||
return
|
||||
}
|
||||
|
||||
this.chatbot.createBubble({
|
||||
who: 'me',
|
||||
string: data.utterance,
|
||||
messageId: data.messageId,
|
||||
sentAt: typeof data.sentAt === 'number' ? data.sentAt : Date.now()
|
||||
})
|
||||
})
|
||||
|
||||
this.socket.on('recognized', (data, cb) => {
|
||||
this._input.value = data
|
||||
this.send('utterance')
|
||||
@@ -89,28 +343,138 @@ export default class Client {
|
||||
cb('string-received')
|
||||
})
|
||||
|
||||
this.socket.on('widget', (data) => {
|
||||
/**
|
||||
* TODO: widget: widget handler to core/skill; dynamic component rendering
|
||||
*/
|
||||
console.log('data', data)
|
||||
this.socket.on('widget-send-utterance', (utterance) => {
|
||||
this._input.value = utterance
|
||||
this.send('utterance')
|
||||
})
|
||||
|
||||
const container = document.createElement('div')
|
||||
container.className = 'widget'
|
||||
this.chatbot.feed.appendChild(container)
|
||||
this.socket.on('new-mood', (mood) => {
|
||||
this.updateMood(mood)
|
||||
})
|
||||
|
||||
const root = createRoot(container)
|
||||
|
||||
// TODO: widget: pass props and dynamic component loading according to type
|
||||
const widgets = {
|
||||
Button: (options) => {
|
||||
return Button({
|
||||
children: options.text
|
||||
})
|
||||
}
|
||||
this.socket.on('llm-token', (data) => {
|
||||
if (this._isVoiceModeEnabled) {
|
||||
this.voiceEnergy.status = 'processing'
|
||||
}
|
||||
|
||||
root.render(widgets[data.type](data.options))
|
||||
this._isLeonGeneratingAnswer = true
|
||||
const previousGenerationId = this._answerGenerationId
|
||||
const newGenerationId = data.generationId
|
||||
this._answerGenerationId = newGenerationId
|
||||
this._activeStreamGenerationId = newGenerationId
|
||||
const isSameGeneration = previousGenerationId === newGenerationId
|
||||
let bubbleContainerElement = null
|
||||
|
||||
if (!isSameGeneration) {
|
||||
bubbleContainerElement = this.chatbot.createBubble({
|
||||
who: 'leon',
|
||||
string: data.token,
|
||||
save: false,
|
||||
bubbleId: newGenerationId
|
||||
})
|
||||
} else {
|
||||
bubbleContainerElement = document.querySelector(
|
||||
`.${previousGenerationId}`
|
||||
)
|
||||
}
|
||||
|
||||
const bubbleElement = bubbleContainerElement.querySelector('p.bubble')
|
||||
|
||||
// Token is already appened when it's a new generation
|
||||
if (isSameGeneration) {
|
||||
// bubbleElement.textContent += data.token
|
||||
|
||||
const tokenSpan = document.createElement('span')
|
||||
tokenSpan.className = 'llm-token fade-in'
|
||||
tokenSpan.textContent = data.token
|
||||
|
||||
bubbleElement.appendChild(tokenSpan)
|
||||
}
|
||||
|
||||
this.chatbot.scrollDown()
|
||||
})
|
||||
|
||||
this.socket.on('llm-reasoning-token', (data) => {
|
||||
if (!data?.generationId || !data?.token) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._isVoiceModeEnabled) {
|
||||
this.voiceEnergy.status = 'processing'
|
||||
}
|
||||
|
||||
this._isLeonGeneratingAnswer = true
|
||||
this.chatbot.createOrUpdateReasoningBlock(
|
||||
data.generationId,
|
||||
data.token,
|
||||
data.phase
|
||||
)
|
||||
this.chatbot.scrollDown()
|
||||
})
|
||||
|
||||
this.socket.on('asr-speech', (text) => {
|
||||
if (!this._isVoiceModeEnabled) {
|
||||
this.enableVoiceMode()
|
||||
}
|
||||
|
||||
this.voiceEnergy.status = 'listening'
|
||||
this._input.value = text
|
||||
|
||||
if (this.voiceSpeechElement) {
|
||||
this.voiceSpeechElement.textContent = text
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.on('asr-end-of-owner-speech', () => {
|
||||
this.voiceEnergy.status = 'processing'
|
||||
|
||||
setTimeout(() => {
|
||||
this.send('utterance')
|
||||
}, 200)
|
||||
})
|
||||
|
||||
this.socket.on('asr-active-listening-disabled', () => {
|
||||
this.voiceEnergy.status = 'idle'
|
||||
})
|
||||
|
||||
/**
|
||||
* Only used for "local" TTS provider as a PoC for now.
|
||||
* Target to do a better implementation in the future
|
||||
* with streaming support
|
||||
*/
|
||||
this.socket.on('tts-stream', (data) => {
|
||||
this.voiceEnergy.status = 'talking'
|
||||
|
||||
// const { audioId, chunk } = data
|
||||
const { chunk } = data
|
||||
this._ttsAudioContext = new AudioContext()
|
||||
// this._ttsAudioContextes[audioId] = ctx
|
||||
|
||||
const source = this._ttsAudioContext.createBufferSource()
|
||||
this._ttsAudioContext.decodeAudioData(chunk, (buffer) => {
|
||||
source.buffer = buffer
|
||||
|
||||
source.connect(this._ttsAudioContext.destination)
|
||||
source.start(0)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* When Leon got interrupted by the owner voice
|
||||
* while he is speaking
|
||||
*/
|
||||
this.socket.on('tts-interruption', async () => {
|
||||
if (this._ttsAudioContext) {
|
||||
await this._ttsAudioContext.close()
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.on('tts-end-of-speech', async () => {
|
||||
this.voiceEnergy.status = 'listening'
|
||||
|
||||
if (window.leonConfigInfo?.asr?.enabled) {
|
||||
this.socket.emit('asr-start-record')
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.on('audio-forwarded', (data, cb) => {
|
||||
@@ -127,7 +491,7 @@ export default class Client {
|
||||
* When the after speech option is enabled and
|
||||
* the answer is a final one
|
||||
*/
|
||||
if (this.info.after_speech && data.is_final_answer) {
|
||||
if (window.leonConfigInfo.after_speech && data.is_final_answer) {
|
||||
// Enable recording after the speech + 500ms
|
||||
setTimeout(() => {
|
||||
this._recorder.start()
|
||||
@@ -164,29 +528,46 @@ export default class Client {
|
||||
}
|
||||
|
||||
send(keyword) {
|
||||
if (this._input.value !== '') {
|
||||
this.socket.emit(keyword, {
|
||||
client: this.client,
|
||||
value: this._input.value.trim()
|
||||
})
|
||||
this.chatbot.sendTo('leon', this._input.value)
|
||||
|
||||
this._suggestions.forEach((suggestion) => {
|
||||
// Remove all event listeners of the suggestion
|
||||
suggestion.replaceWith(suggestion.cloneNode(true))
|
||||
this._suggestionContainer.replaceChildren()
|
||||
})
|
||||
|
||||
this.save()
|
||||
|
||||
return true
|
||||
if (keyword === 'utterance') {
|
||||
return this.sendUtterance(this._input.value)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
save() {
|
||||
let val = this._input.value
|
||||
sendUtterance(value, options = {}) {
|
||||
if (this._isLeonGeneratingAnswer) {
|
||||
return false
|
||||
}
|
||||
|
||||
const trimmedValue = String(value || '').trim()
|
||||
|
||||
if (trimmedValue === '') {
|
||||
return false
|
||||
}
|
||||
|
||||
const sentAt =
|
||||
typeof options.sentAt === 'number' ? options.sentAt : Date.now()
|
||||
|
||||
this.socket.emit('utterance', {
|
||||
client: this.client,
|
||||
value: trimmedValue,
|
||||
sentAt,
|
||||
sessionId: this.activeSessionId,
|
||||
...(options.commandContext
|
||||
? { commandContext: options.commandContext }
|
||||
: {})
|
||||
})
|
||||
this.chatbot.sendTo('leon', trimmedValue, sentAt)
|
||||
this.chatbot.scrollDown({ force: true })
|
||||
|
||||
this.save(trimmedValue)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
save(value = this._input.value) {
|
||||
let val = value
|
||||
|
||||
if (localStorage.getItem('history') === null) {
|
||||
localStorage.setItem('history', JSON.stringify([]))
|
||||
@@ -205,21 +586,45 @@ export default class Client {
|
||||
}
|
||||
|
||||
this._input.value = ''
|
||||
setTimeout(() => {
|
||||
// Remove the last character to avoid the space
|
||||
this._input.value = this._input.value.slice(0, -1)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
addSuggestion(text) {
|
||||
const newSuggestion = document.createElement('button')
|
||||
newSuggestion.classList.add('suggestion')
|
||||
newSuggestion.textContent = text
|
||||
enableVoiceMode() {
|
||||
if (!this._isVoiceModeEnabled) {
|
||||
this._isVoiceModeEnabled = true
|
||||
|
||||
this._suggestionContainer.appendChild(newSuggestion)
|
||||
const body = document.querySelector('body')
|
||||
if (!body.classList.contains('voice-mode-enabled')) {
|
||||
body.classList.add('voice-mode-enabled')
|
||||
|
||||
newSuggestion.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
this.input = e.target.textContent
|
||||
this.send('utterance')
|
||||
})
|
||||
const voiceOverlayTransitor = document.createElement('div')
|
||||
voiceOverlayTransitor.id = 'voice-overlay-transitor'
|
||||
body.appendChild(voiceOverlayTransitor)
|
||||
voiceOverlayTransitor.addEventListener('animationend', () => {
|
||||
voiceOverlayTransitor.removeEventListener('animationend', () => {})
|
||||
voiceOverlayTransitor.remove()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
disableVoiceMode() {
|
||||
if (this._isVoiceModeEnabled) {
|
||||
this._isVoiceModeEnabled = false
|
||||
|
||||
this._suggestions.push(newSuggestion)
|
||||
const body = document.querySelector('body')
|
||||
|
||||
const voiceContainer = document.querySelector('#voice-container')
|
||||
if (voiceContainer) {
|
||||
voiceContainer.style.animation = 'none'
|
||||
voiceContainer.style.animation = null
|
||||
}
|
||||
|
||||
if (body.classList.contains('voice-mode-enabled')) {
|
||||
body.classList.remove('voice-mode-enabled')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
export const INIT_MESSAGES = [
|
||||
[
|
||||
`Hello there! Glad to e-meet meet you, I'm Leon, your open-source personal assistant. While I'm still learning and improving, I promise to do my best to be helpful.`
|
||||
'Hi, I\'m Leon, your open-source personal assistant. I\'m still evolving, but I\'m here to help.'
|
||||
],
|
||||
[
|
||||
`<ul>
|
||||
<li>We've got lots in the works. Check out <a href="http://roadmap.getleon.ai/" target="_blank">our roadmap</a>.</li>
|
||||
<li>Stay updated on our progress by checking out our <a href="https://blog.getleon.ai/" target="_blank">blog</a>.</li>
|
||||
<li>You can also get updates straight to your inbox <a href="https://newsletter.getleon.ai/subscription/form" target="_blank">here</a>.</li>
|
||||
`
|
||||
`Want to follow the project?
|
||||
|
||||
- Regular progress updates: https://x.com/grenlouis
|
||||
- Blog posts: https://blog.getleon.ai/
|
||||
- Email updates: https://leonai.substack.com/subscribe`
|
||||
],
|
||||
[
|
||||
`Come hang out with us <a href="https://discord.gg/MNQqqKg" target="_blank">on Discord</a>! Once we release our official version, our community will be working together to build new skills for me. You won't want to miss out on the fun!`
|
||||
`Come hang out on Discord: https://discord.gg/MNQqqKg
|
||||
That's where people will help shape the ecosystem and build what comes next.`
|
||||
],
|
||||
[
|
||||
`At the moment, I'm not using a large language model, but we're planning to incorporate some in the future to improve my abilities. You can <a href="https://github.com/leon-ai/leon#how-about-large-language-models-and-leon" target="_blank">learn more about our plans here</a>.`
|
||||
],
|
||||
[
|
||||
`Just so you know, my creator is working tirelessly to improve my skills and features, dedicating 75% of his free time to the project on top of his full-time job. If you'd like to help speed up my development, you can sponsor his work by clicking on this link: <strong><a href='http://sponsor.getleon.ai/' target='_blank'>sponsor.getleon.ai</a></strong>. Your support would mean a lot to us. Thank you for choosing me as your assistant!`
|
||||
'Leon is built independently in spare time. If you want to help accelerate development, you can support the project here: http://sponsor.getleon.ai/'
|
||||
]
|
||||
]
|
||||
|
||||
export const ASR_DISABLED_MESSAGE =
|
||||
'Voice mode is not enabled yet. Run /voice setup from the chat, wait for the resources to install, then restart Leon by using /restart.'
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const AUTOCOMPLETE_DELAY_MS = 80
|
||||
const CLOSE_DELAY_MS = 120
|
||||
const DROPDOWN_MARGIN_PX = 8
|
||||
const MINIMUM_DROPDOWN_HEIGHT_PX = 196
|
||||
const MAXIMUM_DROPDOWN_HEIGHT_PX = 280
|
||||
const FILE_SYSTEM_TRIGGER = '@'
|
||||
const FOLDER_TYPE = 'folder'
|
||||
const DEFAULT_FILE_ICON_NAME = 'file-line'
|
||||
const DEFAULT_FOLDER_ICON_NAME = 'folder-3-line'
|
||||
const NAVIGATION_DIRECTIONS = {
|
||||
next: 1,
|
||||
previous: -1
|
||||
}
|
||||
|
||||
function isWhitespace(character) {
|
||||
return character.trim() === ''
|
||||
}
|
||||
|
||||
function findPathToken(input) {
|
||||
const selectionStart = input.selectionStart ?? input.value.length
|
||||
let tokenStart = selectionStart
|
||||
|
||||
while (
|
||||
tokenStart > 0 &&
|
||||
!isWhitespace(input.value.charAt(tokenStart - 1))
|
||||
) {
|
||||
tokenStart -= 1
|
||||
}
|
||||
|
||||
if (input.value.charAt(tokenStart) !== FILE_SYSTEM_TRIGGER) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
start: tokenStart,
|
||||
end: selectionStart,
|
||||
value: input.value.slice(tokenStart, selectionStart)
|
||||
}
|
||||
}
|
||||
|
||||
export default class FileSystemAutocomplete {
|
||||
constructor({ serverUrl, input = null, onValueChange = null }) {
|
||||
this.serverUrl = serverUrl
|
||||
this.input = null
|
||||
this.onValueChange = onValueChange
|
||||
this.dropdown = document.createElement('div')
|
||||
this.dropdown.className = 'file-system-autocomplete file-system-autocomplete--hidden'
|
||||
this.entries = []
|
||||
this.selectedEntryIndex = -1
|
||||
this.token = null
|
||||
this.isOpen = false
|
||||
this.requestCounter = 0
|
||||
this.autocompleteTimeout = null
|
||||
this.closeTimeout = null
|
||||
this.shouldScrollSelectedEntry = false
|
||||
|
||||
this.handleInput = this.handleInput.bind(this)
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this)
|
||||
this.handleFocus = this.handleFocus.bind(this)
|
||||
this.handleClick = this.handleClick.bind(this)
|
||||
this.handleBlur = this.handleBlur.bind(this)
|
||||
this.handleWindowChange = this.handleWindowChange.bind(this)
|
||||
|
||||
document.body.appendChild(this.dropdown)
|
||||
|
||||
if (input) {
|
||||
this.attach(input)
|
||||
}
|
||||
}
|
||||
|
||||
attach(input) {
|
||||
if (this.input === input) {
|
||||
return
|
||||
}
|
||||
|
||||
window.clearTimeout(this.closeTimeout)
|
||||
|
||||
if (this.input) {
|
||||
this.removeInputListeners()
|
||||
}
|
||||
|
||||
this.input = input
|
||||
this.input.addEventListener('input', this.handleInput)
|
||||
this.input.addEventListener('keydown', this.handleKeyDown)
|
||||
this.input.addEventListener('focus', this.handleFocus)
|
||||
this.input.addEventListener('click', this.handleClick)
|
||||
this.input.addEventListener('blur', this.handleBlur)
|
||||
window.addEventListener('resize', this.handleWindowChange)
|
||||
window.addEventListener('scroll', this.handleWindowChange, true)
|
||||
}
|
||||
|
||||
removeInputListeners() {
|
||||
if (!this.input) {
|
||||
return
|
||||
}
|
||||
|
||||
this.input.removeEventListener('input', this.handleInput)
|
||||
this.input.removeEventListener('keydown', this.handleKeyDown)
|
||||
this.input.removeEventListener('focus', this.handleFocus)
|
||||
this.input.removeEventListener('click', this.handleClick)
|
||||
this.input.removeEventListener('blur', this.handleBlur)
|
||||
window.removeEventListener('resize', this.handleWindowChange)
|
||||
window.removeEventListener('scroll', this.handleWindowChange, true)
|
||||
this.input = null
|
||||
}
|
||||
|
||||
detach() {
|
||||
this.removeInputListeners()
|
||||
this.close()
|
||||
}
|
||||
|
||||
handleInput() {
|
||||
window.clearTimeout(this.closeTimeout)
|
||||
this.queueAutocomplete()
|
||||
}
|
||||
|
||||
handleFocus() {
|
||||
window.clearTimeout(this.closeTimeout)
|
||||
this.queueAutocomplete()
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
this.queueAutocomplete()
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
this.queueClose()
|
||||
}
|
||||
|
||||
queueClose() {
|
||||
window.clearTimeout(this.closeTimeout)
|
||||
this.closeTimeout = window.setTimeout(() => {
|
||||
this.close()
|
||||
}, CLOSE_DELAY_MS)
|
||||
}
|
||||
|
||||
handleWindowChange() {
|
||||
if (this.isOpen) {
|
||||
this.positionDropdown()
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(event) {
|
||||
if (!this.isOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
this.moveSelection(NAVIGATION_DIRECTIONS.next)
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
this.moveSelection(NAVIGATION_DIRECTIONS.previous)
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'Tab' || event.key === 'Enter') {
|
||||
const selectedEntry = this.entries[this.selectedEntryIndex]
|
||||
|
||||
if (selectedEntry) {
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
this.applyEntry(selectedEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queueAutocomplete() {
|
||||
window.clearTimeout(this.autocompleteTimeout)
|
||||
this.autocompleteTimeout = window.setTimeout(() => {
|
||||
void this.fetchAutocomplete()
|
||||
}, AUTOCOMPLETE_DELAY_MS)
|
||||
}
|
||||
|
||||
async fetchAutocomplete() {
|
||||
if (!this.input) {
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
|
||||
if (document.activeElement !== this.input) {
|
||||
this.queueClose()
|
||||
return
|
||||
}
|
||||
|
||||
const token = findPathToken(this.input)
|
||||
|
||||
if (!token) {
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
|
||||
const requestId = this.requestCounter + 1
|
||||
this.requestCounter = requestId
|
||||
this.token = token
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${this.serverUrl}/api/v1/file-system/list`,
|
||||
{
|
||||
value: token.value
|
||||
}
|
||||
)
|
||||
|
||||
if (requestId !== this.requestCounter) {
|
||||
return
|
||||
}
|
||||
|
||||
this.entries = response.data.entries || []
|
||||
this.selectedEntryIndex = this.entries.length > 0 ? 0 : -1
|
||||
this.render()
|
||||
} catch {
|
||||
if (requestId !== this.requestCounter) {
|
||||
return
|
||||
}
|
||||
|
||||
this.entries = []
|
||||
this.selectedEntryIndex = -1
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.input || !this.token) {
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
|
||||
this.dropdown.innerHTML = ''
|
||||
|
||||
if (this.entries.length === 0) {
|
||||
const emptyElement = document.createElement('div')
|
||||
emptyElement.className = 'file-system-autocomplete__empty'
|
||||
emptyElement.textContent = 'No matching file system entry.'
|
||||
this.dropdown.appendChild(emptyElement)
|
||||
} else {
|
||||
this.entries.forEach((entry, index) => {
|
||||
const item = document.createElement('button')
|
||||
const icon = document.createElement('i')
|
||||
const label = document.createElement('span')
|
||||
|
||||
item.type = 'button'
|
||||
item.className = `file-system-autocomplete__item${
|
||||
index === this.selectedEntryIndex
|
||||
? ' file-system-autocomplete__item--selected'
|
||||
: ''
|
||||
}`
|
||||
icon.className = `ri-${
|
||||
entry.iconName ||
|
||||
(entry.type === FOLDER_TYPE
|
||||
? DEFAULT_FOLDER_ICON_NAME
|
||||
: DEFAULT_FILE_ICON_NAME)
|
||||
}`
|
||||
label.className = 'file-system-autocomplete__label'
|
||||
label.textContent = entry.value
|
||||
|
||||
item.addEventListener('mousedown', (event) => {
|
||||
event.preventDefault()
|
||||
})
|
||||
item.addEventListener('click', () => {
|
||||
this.applyEntry(entry)
|
||||
})
|
||||
|
||||
item.appendChild(icon)
|
||||
item.appendChild(label)
|
||||
this.dropdown.appendChild(item)
|
||||
})
|
||||
}
|
||||
|
||||
this.isOpen = true
|
||||
this.dropdown.classList.remove('file-system-autocomplete--hidden')
|
||||
this.positionDropdown()
|
||||
|
||||
if (this.shouldScrollSelectedEntry) {
|
||||
this.shouldScrollSelectedEntry = false
|
||||
this.scrollSelectedEntryIntoView()
|
||||
}
|
||||
}
|
||||
|
||||
positionDropdown() {
|
||||
const inputRect = this.input.getBoundingClientRect()
|
||||
const spaceBelow = window.innerHeight - inputRect.bottom
|
||||
const spaceAbove = inputRect.top
|
||||
const shouldOpenAbove =
|
||||
spaceBelow < MINIMUM_DROPDOWN_HEIGHT_PX && spaceAbove > spaceBelow
|
||||
const availableHeight =
|
||||
(shouldOpenAbove ? spaceAbove : spaceBelow) - DROPDOWN_MARGIN_PX * 2
|
||||
const maxHeight = Math.min(
|
||||
MAXIMUM_DROPDOWN_HEIGHT_PX,
|
||||
Math.max(MINIMUM_DROPDOWN_HEIGHT_PX, availableHeight)
|
||||
)
|
||||
|
||||
this.dropdown.style.left = `${inputRect.left}px`
|
||||
this.dropdown.style.width = `${inputRect.width}px`
|
||||
this.dropdown.style.maxHeight = `${maxHeight}px`
|
||||
|
||||
if (shouldOpenAbove) {
|
||||
this.dropdown.style.top = ''
|
||||
this.dropdown.style.bottom = `${
|
||||
window.innerHeight - inputRect.top + DROPDOWN_MARGIN_PX
|
||||
}px`
|
||||
} else {
|
||||
this.dropdown.style.top = `${inputRect.bottom + DROPDOWN_MARGIN_PX}px`
|
||||
this.dropdown.style.bottom = ''
|
||||
}
|
||||
}
|
||||
|
||||
moveSelection(direction) {
|
||||
if (this.entries.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.selectedEntryIndex =
|
||||
(this.selectedEntryIndex + direction + this.entries.length) %
|
||||
this.entries.length
|
||||
this.shouldScrollSelectedEntry = true
|
||||
this.render()
|
||||
}
|
||||
|
||||
scrollSelectedEntryIntoView() {
|
||||
const selectedEntryElement = this.dropdown.querySelector(
|
||||
'.file-system-autocomplete__item--selected'
|
||||
)
|
||||
|
||||
if (!selectedEntryElement) {
|
||||
return
|
||||
}
|
||||
|
||||
selectedEntryElement.scrollIntoView({
|
||||
block: 'nearest'
|
||||
})
|
||||
}
|
||||
|
||||
applyEntry(entry) {
|
||||
if (!this.input || !this.token) {
|
||||
return
|
||||
}
|
||||
|
||||
const completedValue = entry.absolutePath || entry.value
|
||||
const nextCharacter = this.input.value.charAt(this.token.end)
|
||||
const shouldAddTrailingWhitespace =
|
||||
nextCharacter === '' || !isWhitespace(nextCharacter)
|
||||
const insertedValue = `${completedValue}${
|
||||
shouldAddTrailingWhitespace ? ' ' : ''
|
||||
}`
|
||||
const nextValue = `${this.input.value.slice(0, this.token.start)}${
|
||||
insertedValue
|
||||
}${this.input.value.slice(this.token.end)}`
|
||||
const nextCursorPosition = this.token.start + insertedValue.length
|
||||
|
||||
this.input.value = nextValue
|
||||
this.input.setSelectionRange(nextCursorPosition, nextCursorPosition)
|
||||
this.input.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
|
||||
if (this.onValueChange) {
|
||||
this.onValueChange(nextValue)
|
||||
window.requestAnimationFrame(() => {
|
||||
this.input?.setSelectionRange(nextCursorPosition, nextCursorPosition)
|
||||
})
|
||||
}
|
||||
|
||||
this.close()
|
||||
}
|
||||
|
||||
close() {
|
||||
this.isOpen = false
|
||||
this.entries = []
|
||||
this.selectedEntryIndex = -1
|
||||
this.token = null
|
||||
this.dropdown.classList.add('file-system-autocomplete--hidden')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import {
|
||||
WidgetWrapper,
|
||||
Text,
|
||||
Icon,
|
||||
Flexbox,
|
||||
List,
|
||||
ListHeader,
|
||||
ListItem,
|
||||
Loader
|
||||
} from '@aurora'
|
||||
|
||||
const container = document.querySelector('#init')
|
||||
const root = createRoot(container)
|
||||
const LLAMA_SERVER_BOOT_STATUS = 'llamaServerBoot'
|
||||
const INIT_ERROR_STATUS = 'error'
|
||||
const INIT_ERROR_DISMISS_SECONDS = 10
|
||||
const INIT_ERROR_DISMISS_INTERVAL_MS = 1_000
|
||||
|
||||
function Item({ children, status }) {
|
||||
if (status === 'error') {
|
||||
return <ErrorListItem>{children}</ErrorListItem>
|
||||
}
|
||||
if (status === 'warning') {
|
||||
return <WarningListItem>{children}</WarningListItem>
|
||||
}
|
||||
if (status === 'success') {
|
||||
return <SuccessListItem>{children}</SuccessListItem>
|
||||
}
|
||||
if (status === 'loading') {
|
||||
return <LoadingListItem>{children}</LoadingListItem>
|
||||
}
|
||||
|
||||
return <ListItem>{children}</ListItem>
|
||||
}
|
||||
|
||||
function LoadingListItem({ children }) {
|
||||
return (
|
||||
<ListItem>
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="sm">
|
||||
<Loader size="sm" />
|
||||
<Text>{children}</Text>
|
||||
</Flexbox>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
function ErrorListItem({ children }) {
|
||||
return (
|
||||
<ListItem>
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="sm">
|
||||
<Icon
|
||||
iconName="close"
|
||||
size="sm"
|
||||
type="fill"
|
||||
bgShape="circle"
|
||||
color="red"
|
||||
bgColor="transparent-red"
|
||||
/>
|
||||
<Text>{children}</Text>
|
||||
</Flexbox>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
function WarningListItem({ children }) {
|
||||
return (
|
||||
<ListItem>
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="sm">
|
||||
<Icon
|
||||
iconName="alert"
|
||||
size="sm"
|
||||
type="fill"
|
||||
bgShape="circle"
|
||||
color="yellow"
|
||||
bgColor="transparent-yellow"
|
||||
/>
|
||||
<Text>{children}</Text>
|
||||
</Flexbox>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
function SuccessListItem({ children }) {
|
||||
return (
|
||||
<ListItem>
|
||||
<Flexbox flexDirection="row" alignItems="center" gap="sm">
|
||||
<Icon
|
||||
iconName="check"
|
||||
size="sm"
|
||||
type="fill"
|
||||
bgShape="circle"
|
||||
color="green"
|
||||
bgColor="transparent-green"
|
||||
/>
|
||||
<Text>{children}</Text>
|
||||
</Flexbox>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
function Init() {
|
||||
const parentRef = useRef(null)
|
||||
const [config, setConfig] = useState(() => ({ ...window.leonConfigInfo }))
|
||||
const usesLlamaCPP =
|
||||
config.llm?.workflowProvider === 'llamacpp' ||
|
||||
config.llm?.agentProvider === 'llamacpp'
|
||||
const [initErrorCountdown, setInitErrorCountdown] = useState(null)
|
||||
const [areInitErrorsDismissed, setAreInitErrorsDismissed] = useState(false)
|
||||
const [statusMap, setStatusMap] = useState({
|
||||
clientCoreServerHandshake: 'loading',
|
||||
tcpServerBoot:
|
||||
window.leonConfigInfo?.tcpServer?.enabled === false ? 'success' : 'loading',
|
||||
[LLAMA_SERVER_BOOT_STATUS]:
|
||||
window.leonConfigInfo?.llm?.workflowProvider === 'llamacpp' ||
|
||||
window.leonConfigInfo?.llm?.agentProvider === 'llamacpp'
|
||||
? 'loading'
|
||||
: 'success'
|
||||
})
|
||||
const hasInitError = Object.values(statusMap).some(
|
||||
(status) => status === INIT_ERROR_STATUS
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (parentRef.current) {
|
||||
parentRef.current.classList.remove('not-initialized')
|
||||
}
|
||||
}, 250)
|
||||
|
||||
function handleStatusChange(event) {
|
||||
const { statusName, statusType } = event.detail
|
||||
|
||||
if (statusType === INIT_ERROR_STATUS) {
|
||||
setAreInitErrorsDismissed(false)
|
||||
}
|
||||
|
||||
setStatusMap((prev) => ({ ...prev, [statusName]: statusType }))
|
||||
}
|
||||
|
||||
window.leonInitStatusEvent.addEventListener(
|
||||
'initStatusChange',
|
||||
handleStatusChange
|
||||
)
|
||||
return () =>
|
||||
window.leonInitStatusEvent.removeEventListener(
|
||||
'initStatusChange',
|
||||
handleStatusChange
|
||||
)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasInitError || areInitErrorsDismissed) {
|
||||
setInitErrorCountdown(null)
|
||||
return
|
||||
}
|
||||
|
||||
let secondsLeft = INIT_ERROR_DISMISS_SECONDS
|
||||
setInitErrorCountdown(secondsLeft)
|
||||
|
||||
const interval = setInterval(() => {
|
||||
secondsLeft -= 1
|
||||
setInitErrorCountdown(secondsLeft)
|
||||
|
||||
if (secondsLeft <= 0) {
|
||||
clearInterval(interval)
|
||||
setAreInitErrorsDismissed(true)
|
||||
}
|
||||
}, INIT_ERROR_DISMISS_INTERVAL_MS)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [hasInitError, areInitErrorsDismissed])
|
||||
|
||||
const statuses = []
|
||||
for (let key of Object.keys(statusMap)) {
|
||||
if (key === 'tcpServerBoot' && config.tcpServer?.enabled === false) {
|
||||
statuses.push('success')
|
||||
} else if (statusMap[key] === INIT_ERROR_STATUS && areInitErrorsDismissed) {
|
||||
statuses.push('success')
|
||||
} else if (
|
||||
key === LLAMA_SERVER_BOOT_STATUS &&
|
||||
!usesLlamaCPP
|
||||
) {
|
||||
statuses.push('success')
|
||||
} else {
|
||||
statuses.push(statusMap[key])
|
||||
}
|
||||
}
|
||||
|
||||
const areAllStatusesSuccess = statuses.every((status) => status === 'success')
|
||||
const getInitMessage = (status, defaultMessage) => {
|
||||
if (status === INIT_ERROR_STATUS && initErrorCountdown !== null) {
|
||||
return `An error occurred during the initialization. This message will disappear in ${initErrorCountdown} seconds`
|
||||
}
|
||||
|
||||
return defaultMessage
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (window.leonConfigInfo) {
|
||||
setConfig({ ...window.leonConfigInfo })
|
||||
}
|
||||
}, [window.leonConfigInfo])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
zIndex: 9999,
|
||||
backgroundColor: 'var(--black-color)'
|
||||
}}
|
||||
ref={parentRef}
|
||||
className={areAllStatusesSuccess ? 'initialized' : 'not-initialized'}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '33%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}}
|
||||
>
|
||||
<WidgetWrapper noPadding>
|
||||
<List>
|
||||
<ListHeader>Leon is getting ready...</ListHeader>
|
||||
<Item status={statusMap.clientCoreServerHandshake}>
|
||||
{getInitMessage(
|
||||
statusMap.clientCoreServerHandshake,
|
||||
'Client and core server handshaked'
|
||||
)}
|
||||
</Item>
|
||||
{config.tcpServer?.enabled !== false && (
|
||||
<Item status={statusMap.tcpServerBoot}>
|
||||
{getInitMessage(statusMap.tcpServerBoot, 'TCP server booted')}
|
||||
</Item>
|
||||
)}
|
||||
{usesLlamaCPP && (
|
||||
<Item status={statusMap[LLAMA_SERVER_BOOT_STATUS]}>
|
||||
{getInitMessage(
|
||||
statusMap[LLAMA_SERVER_BOOT_STATUS],
|
||||
'llama-server booted'
|
||||
)}
|
||||
</Item>
|
||||
)}
|
||||
</List>
|
||||
</WidgetWrapper>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
root.render(<Init />)
|
||||
@@ -1,22 +0,0 @@
|
||||
export default class Loader {
|
||||
constructor() {
|
||||
this.et = new EventTarget()
|
||||
this.body = document.querySelector('body')
|
||||
|
||||
this.et.addEventListener('settingup', (event) => {
|
||||
if (event.detail) {
|
||||
this.body.classList.add('settingup')
|
||||
} else {
|
||||
this.body.classList.remove('settingup')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
start() {
|
||||
this.et.dispatchEvent(new CustomEvent('settingup', { detail: true }))
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.et.dispatchEvent(new CustomEvent('settingup', { detail: false }))
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
import axios from 'axios'
|
||||
import '@aurora/style.css'
|
||||
|
||||
import Loader from './loader'
|
||||
window.leonInitStatusEvent = new EventTarget()
|
||||
|
||||
import './init'
|
||||
import Client from './client'
|
||||
import Recorder from './recorder'
|
||||
import listener from './listener'
|
||||
import { onkeydowndocument, onkeydowninput } from './onkeydown'
|
||||
import { BuiltInCommands } from './built-in-commands'
|
||||
import FileSystemAutocomplete from './file-system-autocomplete'
|
||||
import SessionsPanel from './sessions'
|
||||
// import Recorder from './recorder'
|
||||
// import listener from './listener'
|
||||
import { onkeydownstartrecording, onkeydowninput } from './onkeydown'
|
||||
|
||||
const config = {
|
||||
app: 'webapp',
|
||||
@@ -19,29 +25,90 @@ const serverUrl =
|
||||
: `${config.server_host}:${config.server_port}`
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const loader = new Loader()
|
||||
|
||||
loader.start()
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${serverUrl}/api/v1/info`)
|
||||
const [response, sessionsResponse] = await Promise.all([
|
||||
axios.get(`${serverUrl}/api/v1/info`),
|
||||
axios.get(`${serverUrl}/api/v1/sessions`)
|
||||
])
|
||||
const input = document.querySelector('#utterance')
|
||||
const mic = document.querySelector('#mic-button')
|
||||
const v = document.querySelector('#version small')
|
||||
const client = new Client(config.app, serverUrl, input, response.data)
|
||||
let rec = {}
|
||||
let chunks = []
|
||||
const infoButton = document.querySelector('#info')
|
||||
const client = new Client(config.app, serverUrl, input, {
|
||||
activeSessionId: sessionsResponse.data.active_session_id
|
||||
})
|
||||
const sessionsPanel = new SessionsPanel({
|
||||
serverUrl,
|
||||
socket: client.socket,
|
||||
activeSessionId: sessionsResponse.data.active_session_id,
|
||||
initialPayload: sessionsResponse.data,
|
||||
onSelect: (sessionId) => client.setActiveSession(sessionId)
|
||||
})
|
||||
const fileSystemAutocomplete = new FileSystemAutocomplete({
|
||||
serverUrl,
|
||||
input
|
||||
})
|
||||
const builtInCommands = new BuiltInCommands({
|
||||
serverUrl,
|
||||
input,
|
||||
getActiveSessionId: () => sessionsPanel.getActiveSessionId(),
|
||||
onCommandExecuted: (commandInput) => {
|
||||
if (commandInput.trim().startsWith('/session')) {
|
||||
void sessionsPanel.refresh()
|
||||
}
|
||||
},
|
||||
onSubmitToChat: (clientAction) => {
|
||||
return client.sendUtterance(clientAction.utterance, {
|
||||
commandContext: {
|
||||
forcedRoutingMode: clientAction.command_context?.forced_routing_mode,
|
||||
forcedSkillName: clientAction.command_context?.forced_skill_name,
|
||||
forcedToolName: clientAction.command_context?.forced_tool_name
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
// let rec = {}
|
||||
// let chunks = []
|
||||
|
||||
v.innerHTML += client.info.version
|
||||
window.leonConfigInfo = response.data
|
||||
const infoKeys = [
|
||||
'timeZone',
|
||||
'telemetry',
|
||||
'gpu',
|
||||
'graphicsComputeAPI',
|
||||
'totalVRAM',
|
||||
'freeVRAM',
|
||||
'usedVRAM',
|
||||
'llm',
|
||||
'asr',
|
||||
'tts',
|
||||
'mood',
|
||||
'version'
|
||||
]
|
||||
const infoToDisplay = {}
|
||||
infoKeys.forEach((key) => {
|
||||
infoToDisplay[key] = window.leonConfigInfo[key]
|
||||
})
|
||||
|
||||
client.init(loader)
|
||||
v.textContent += window.leonConfigInfo.version
|
||||
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
client.updateMood(window.leonConfigInfo.mood)
|
||||
client.setSessionPanel(sessionsPanel)
|
||||
sessionsPanel.init()
|
||||
client.init()
|
||||
fileSystemAutocomplete.attach(input)
|
||||
builtInCommands.init()
|
||||
|
||||
infoButton.addEventListener('click', () => {
|
||||
alert(JSON.stringify(infoToDisplay, null, 2))
|
||||
})
|
||||
|
||||
/*if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
.then((stream) => {
|
||||
if (MediaRecorder) {
|
||||
rec = new Recorder(stream, mic, client.info)
|
||||
rec = new Recorder(stream, mic, window.leonConfigInfo)
|
||||
client.recorder = rec
|
||||
|
||||
rec.ondataavailable((e) => {
|
||||
@@ -49,7 +116,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
})
|
||||
|
||||
rec.onstart(() => {
|
||||
/* */
|
||||
/!* *!/
|
||||
})
|
||||
|
||||
rec.onstop(() => {
|
||||
@@ -106,18 +173,19 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.error(
|
||||
'MediaDevices.getUserMedia() is not supported on your browser.'
|
||||
)
|
||||
}
|
||||
}*/
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
onkeydowndocument(e, () => {
|
||||
if (rec.enabled === false) {
|
||||
onkeydownstartrecording(e, () => {
|
||||
client.asrStartRecording()
|
||||
/*if (rec.enabled === false) {
|
||||
input.value = ''
|
||||
rec.start()
|
||||
rec.enabled = true
|
||||
} else {
|
||||
rec.stop()
|
||||
rec.enabled = false
|
||||
}
|
||||
}*/
|
||||
})
|
||||
})
|
||||
|
||||
@@ -128,13 +196,15 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
mic.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (rec.enabled === false) {
|
||||
client.asrStartRecording()
|
||||
|
||||
/*if (rec.enabled === false) {
|
||||
rec.start()
|
||||
rec.enabled = true
|
||||
} else {
|
||||
rec.stop()
|
||||
rec.enabled = false
|
||||
}
|
||||
}*/
|
||||
})
|
||||
} catch (e) {
|
||||
alert(`Error: ${e.message}; ${JSON.stringify(e.response?.data)}`)
|
||||
|
||||
@@ -8,29 +8,31 @@ const onkeydowninput = (e, client) => {
|
||||
parsedHistory = JSON.parse(localStorage.getItem('history')).reverse()
|
||||
}
|
||||
|
||||
if (key === 13) {
|
||||
if (key === 13 && !e.shiftKey) {
|
||||
if (client.send('utterance')) {
|
||||
parsedHistory = JSON.parse(localStorage.getItem('history')).reverse()
|
||||
index = -1
|
||||
}
|
||||
} else if (localStorage.getItem('history') !== null) {
|
||||
if (key === 38 && index < parsedHistory.length - 1) {
|
||||
index += 1
|
||||
client.input = parsedHistory[index]
|
||||
} else if (key === 40 && index - 1 >= 0) {
|
||||
index -= 1
|
||||
client.input = parsedHistory[index]
|
||||
} else if (key === 40 && index - 1 < 0) {
|
||||
client.input = ''
|
||||
index = -1
|
||||
if (e.shiftKey) {
|
||||
if (key === 38 && index < parsedHistory.length - 1) {
|
||||
index += 1
|
||||
client.input = parsedHistory[index]
|
||||
} else if (key === 40 && index - 1 >= 0) {
|
||||
index -= 1
|
||||
client.input = parsedHistory[index]
|
||||
} else if (key === 40 && index - 1 < 0) {
|
||||
client.input = ''
|
||||
index = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onkeydowndocument = (e, cb) => {
|
||||
if (e.altKey && e.key === 'c') {
|
||||
const onkeydownstartrecording = (e, cb) => {
|
||||
if ((e.metaKey || e.altKey) && e.key === 'c') {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
export { onkeydowninput, onkeydowndocument }
|
||||
export { onkeydowninput, onkeydownstartrecording }
|
||||
|
||||
@@ -16,8 +16,8 @@ export default class Recorder {
|
||||
}
|
||||
|
||||
start(playSound = true) {
|
||||
if (this.info.stt.enabled === false) {
|
||||
console.warn('Speech-to-text disabled')
|
||||
if (this.info.asr.enabled === false) {
|
||||
console.warn('ASR disabled')
|
||||
} else {
|
||||
this.playSound = playSound
|
||||
this.recorder.start(playSound)
|
||||
@@ -25,8 +25,8 @@ export default class Recorder {
|
||||
}
|
||||
|
||||
stop(playSound = true) {
|
||||
if (this.info.stt.enabled === false) {
|
||||
console.warn('Speech-to-text disabled')
|
||||
if (this.info.asr.enabled === false) {
|
||||
console.warn('ASR disabled')
|
||||
} else {
|
||||
this.playSound = playSound
|
||||
this.recorder.stop(playSound)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { createElement } from 'react'
|
||||
|
||||
import * as auroraComponents from '@aurora'
|
||||
|
||||
import * as customAuroraComponents from '../custom-aurora-components'
|
||||
|
||||
export default function renderAuroraComponent(
|
||||
socket,
|
||||
component,
|
||||
supportedEvents
|
||||
) {
|
||||
if (component) {
|
||||
// `import/namespace` cannot statically validate dynamic component lookups.
|
||||
// eslint-disable-next-line import/namespace
|
||||
let reactComponent = auroraComponents[component.component]
|
||||
/**
|
||||
* Find custom component if a former component is not found
|
||||
*/
|
||||
if (!reactComponent) {
|
||||
// eslint-disable-next-line import/namespace
|
||||
reactComponent = customAuroraComponents[component.component]
|
||||
}
|
||||
|
||||
if (!reactComponent) {
|
||||
console.error(`Component ${component.component} not found`)
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if the browsed component has a supported event and bind it
|
||||
if (reactComponent && Array.isArray(component.events)) {
|
||||
component.events.forEach((event) => {
|
||||
if (supportedEvents.includes(event.type)) {
|
||||
component.props[event.type] = (data) => {
|
||||
const { method } = event
|
||||
|
||||
socket.emit('widget-event', { method, data })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// When children is a component, then wrap it in an array to render properly
|
||||
const isComponent = !!component.props?.children?.component
|
||||
if (isComponent) {
|
||||
component.props.children = [component.props.children]
|
||||
}
|
||||
|
||||
if (component.props?.children && Array.isArray(component.props.children)) {
|
||||
component.props.children = component.props.children.map((child) => {
|
||||
return renderAuroraComponent(socket, child, supportedEvents)
|
||||
})
|
||||
}
|
||||
|
||||
return createElement(reactComponent, component.props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1_000
|
||||
const GROUP_DATE_FORMATTER = new Intl.DateTimeFormat(undefined, {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
})
|
||||
|
||||
function createElement(tagName, className, textContent = '') {
|
||||
const element = document.createElement(tagName)
|
||||
|
||||
if (className) {
|
||||
element.className = className
|
||||
}
|
||||
|
||||
if (textContent) {
|
||||
element.textContent = textContent
|
||||
}
|
||||
|
||||
return element
|
||||
}
|
||||
|
||||
function getDayKey(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
|
||||
return new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate()
|
||||
).getTime()
|
||||
}
|
||||
|
||||
function getGroupLabel(timestamp) {
|
||||
const now = Date.now()
|
||||
const todayKey = getDayKey(now)
|
||||
const yesterdayKey = todayKey - DAY_MS
|
||||
const itemKey = getDayKey(timestamp)
|
||||
|
||||
if (itemKey === todayKey) {
|
||||
return 'Today'
|
||||
}
|
||||
|
||||
if (itemKey === yesterdayKey) {
|
||||
return 'Yesterday'
|
||||
}
|
||||
|
||||
return GROUP_DATE_FORMATTER.format(new Date(timestamp))
|
||||
}
|
||||
|
||||
export default class SessionsPanel {
|
||||
constructor({
|
||||
serverUrl,
|
||||
socket,
|
||||
activeSessionId,
|
||||
initialPayload,
|
||||
onSelect
|
||||
}) {
|
||||
this.serverUrl = serverUrl
|
||||
this.socket = socket
|
||||
this.activeSessionId = activeSessionId
|
||||
this.sessions = initialPayload?.sessions || []
|
||||
this.supportedProviders = initialPayload?.supported_providers || []
|
||||
this.currentModelTarget = initialPayload?.current_model_target || ''
|
||||
this.onSelect = onSelect
|
||||
this.container = document.querySelector('#sessions-panel')
|
||||
this.list = document.querySelector('#sessions-list')
|
||||
this.newButton = document.querySelector('#new-session')
|
||||
}
|
||||
|
||||
init() {
|
||||
this.newButton.addEventListener('click', () => {
|
||||
void this.createSession()
|
||||
})
|
||||
this.list.addEventListener('click', (event) => {
|
||||
void this.handleClick(event)
|
||||
})
|
||||
this.render()
|
||||
}
|
||||
|
||||
getActiveSessionId() {
|
||||
return this.activeSessionId
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
const previousActiveSessionId = this.activeSessionId
|
||||
const response = await axios.get(`${this.serverUrl}/api/v1/sessions`)
|
||||
|
||||
this.applyPayload(response.data)
|
||||
|
||||
if (
|
||||
response.data.active_session_id &&
|
||||
response.data.active_session_id !== previousActiveSessionId
|
||||
) {
|
||||
await this.onSelect?.(response.data.active_session_id)
|
||||
}
|
||||
}
|
||||
|
||||
async createSession() {
|
||||
const response = await axios.post(`${this.serverUrl}/api/v1/sessions`)
|
||||
|
||||
this.applyPayload(response.data)
|
||||
await this.selectSession(response.data.session.id)
|
||||
}
|
||||
|
||||
async selectSession(sessionId) {
|
||||
this.activeSessionId = sessionId
|
||||
this.socket.emit('session-change', sessionId)
|
||||
await this.onSelect?.(sessionId)
|
||||
await axios.patch(`${this.serverUrl}/api/v1/sessions/${sessionId}`, {
|
||||
is_active: true
|
||||
})
|
||||
await this.refresh()
|
||||
}
|
||||
|
||||
async updateSession(sessionId, payload) {
|
||||
const response = await axios.patch(
|
||||
`${this.serverUrl}/api/v1/sessions/${sessionId}`,
|
||||
payload
|
||||
)
|
||||
|
||||
this.applyPayload(response.data)
|
||||
}
|
||||
|
||||
async deleteSession(sessionId) {
|
||||
const wasActiveSession = this.activeSessionId === sessionId
|
||||
const response = await axios.delete(
|
||||
`${this.serverUrl}/api/v1/sessions/${sessionId}`
|
||||
)
|
||||
|
||||
this.applyPayload(response.data)
|
||||
|
||||
if (wasActiveSession) {
|
||||
await this.selectSession(response.data.active_session_id)
|
||||
}
|
||||
}
|
||||
|
||||
async handleClick(event) {
|
||||
const button = event.target.closest('button')
|
||||
const item = event.target.closest('.session-item')
|
||||
|
||||
if (!item) {
|
||||
return
|
||||
}
|
||||
|
||||
const sessionId = item.getAttribute('data-session-id')
|
||||
|
||||
if (!button) {
|
||||
await this.selectSession(sessionId)
|
||||
return
|
||||
}
|
||||
|
||||
const action = button.getAttribute('data-action')
|
||||
|
||||
if (action === 'rename') {
|
||||
const currentTitle = item.getAttribute('data-session-title') || ''
|
||||
const title = window.prompt('Session title', currentTitle)
|
||||
|
||||
if (title && title.trim()) {
|
||||
await this.updateSession(sessionId, { title: title.trim() })
|
||||
}
|
||||
} else if (action === 'pin') {
|
||||
await this.updateSession(sessionId, {
|
||||
is_pinned: button.getAttribute('aria-pressed') !== 'true'
|
||||
})
|
||||
} else if (action === 'delete') {
|
||||
await this.deleteSession(sessionId)
|
||||
} else if (action === 'model') {
|
||||
await this.updateSessionModel(sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
async updateSessionModel(sessionId) {
|
||||
const provider = window.prompt(
|
||||
`Provider (${this.supportedProviders.join(', ')})`
|
||||
)
|
||||
|
||||
if (!provider) {
|
||||
return
|
||||
}
|
||||
|
||||
const model = window.prompt('Model')
|
||||
|
||||
if (!model) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.updateSession(sessionId, {
|
||||
provider: provider.trim(),
|
||||
model: model.trim()
|
||||
})
|
||||
}
|
||||
|
||||
applyPayload(payload) {
|
||||
this.sessions = payload.sessions || []
|
||||
this.supportedProviders = payload.supported_providers || []
|
||||
this.currentModelTarget = payload.current_model_target || ''
|
||||
this.activeSessionId = payload.active_session_id || this.activeSessionId
|
||||
this.render()
|
||||
}
|
||||
|
||||
render() {
|
||||
this.list.innerHTML = ''
|
||||
|
||||
const pinnedSessions = this.sessions.filter((session) => session.isPinned)
|
||||
const unpinnedSessions = this.sessions.filter((session) => !session.isPinned)
|
||||
|
||||
if (pinnedSessions.length > 0) {
|
||||
this.renderGroup('Pinned', pinnedSessions)
|
||||
}
|
||||
|
||||
const groupedSessions = new Map()
|
||||
|
||||
for (const session of unpinnedSessions) {
|
||||
const timestamp = session.lastMessageAt || session.updatedAt
|
||||
const groupLabel = getGroupLabel(timestamp)
|
||||
const group = groupedSessions.get(groupLabel) || []
|
||||
|
||||
group.push(session)
|
||||
groupedSessions.set(groupLabel, group)
|
||||
}
|
||||
|
||||
for (const [label, sessions] of groupedSessions.entries()) {
|
||||
this.renderGroup(label, sessions)
|
||||
}
|
||||
}
|
||||
|
||||
renderGroup(label, sessions) {
|
||||
const group = createElement('section', 'session-group')
|
||||
const heading = createElement('h2', 'session-group-title', label)
|
||||
|
||||
group.appendChild(heading)
|
||||
|
||||
for (const session of sessions) {
|
||||
group.appendChild(this.createSessionItem(session))
|
||||
}
|
||||
|
||||
this.list.appendChild(group)
|
||||
}
|
||||
|
||||
createSessionItem(session) {
|
||||
const item = createElement('article', 'session-item')
|
||||
const title = createElement('div', 'session-title', session.title)
|
||||
const meta = createElement(
|
||||
'div',
|
||||
'session-meta',
|
||||
session.modelTarget || this.currentModelTarget || 'Default model'
|
||||
)
|
||||
const actions = createElement('div', 'session-actions')
|
||||
|
||||
item.setAttribute('data-session-id', session.id)
|
||||
item.setAttribute('data-session-title', session.title)
|
||||
|
||||
if (session.id === this.activeSessionId) {
|
||||
item.classList.add('session-item--active')
|
||||
}
|
||||
|
||||
actions.appendChild(
|
||||
this.createActionButton(
|
||||
'pin',
|
||||
session.isPinned ? 'ri-pushpin-fill' : 'ri-pushpin-line',
|
||||
session.isPinned
|
||||
)
|
||||
)
|
||||
actions.appendChild(this.createActionButton('rename', 'ri-edit-line'))
|
||||
actions.appendChild(this.createActionButton('model', 'ri-brain-line'))
|
||||
actions.appendChild(this.createActionButton('delete', 'ri-delete-bin-line'))
|
||||
|
||||
item.appendChild(title)
|
||||
item.appendChild(meta)
|
||||
item.appendChild(actions)
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
createActionButton(action, iconClassName, isPressed = false) {
|
||||
const button = createElement('button', 'session-action')
|
||||
const icon = createElement('i', iconClassName)
|
||||
|
||||
button.type = 'button'
|
||||
button.setAttribute('data-action', action)
|
||||
button.setAttribute('aria-label', action)
|
||||
button.setAttribute('aria-pressed', String(isPressed))
|
||||
button.appendChild(icon)
|
||||
|
||||
return button
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { createElement } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { WidgetWrapper, List, ListHeader, ListItem } from '@aurora'
|
||||
|
||||
export default function handleSuggestions(data, chatbot, client) {
|
||||
const container = document.createElement('div')
|
||||
container.className = 'bubble-container leon'
|
||||
|
||||
chatbot.feed.appendChild(container)
|
||||
|
||||
const root = createRoot(container)
|
||||
|
||||
root.render(
|
||||
createElement(WidgetWrapper, {
|
||||
noPadding: true,
|
||||
children: createElement(List, {
|
||||
children: [
|
||||
createElement(ListHeader, {
|
||||
children: 'Suggestions'
|
||||
}),
|
||||
...data.map((suggestionText) => {
|
||||
return createElement(ListItem, {
|
||||
children: suggestionText,
|
||||
name: 'suggestion',
|
||||
value: suggestionText,
|
||||
onClick: (suggestion) => {
|
||||
const parent = container.parentNode
|
||||
|
||||
if (parent) {
|
||||
parent.removeChild(container)
|
||||
}
|
||||
|
||||
client.input.value = suggestion.value
|
||||
client.send('utterance')
|
||||
}
|
||||
})
|
||||
})
|
||||
]
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
const STATUS = {
|
||||
listening: 'Listening...',
|
||||
processing: 'Processing...',
|
||||
talking: 'Talking...',
|
||||
idle: 'Idle'
|
||||
}
|
||||
|
||||
export default class VoiceEnergy {
|
||||
constructor(client) {
|
||||
this.client = client
|
||||
this.voiceEnergyContainerElement = document.querySelector(
|
||||
'#voice-energy-container'
|
||||
)
|
||||
this.voiceOverlayElement = document.querySelector('#voice-overlay-bg')
|
||||
this.statusElement = document.querySelector('#voice-status')
|
||||
this._status = 'idle'
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this._status
|
||||
}
|
||||
|
||||
set status(newStatus) {
|
||||
if (this._status !== newStatus) {
|
||||
this._status = newStatus
|
||||
|
||||
if (this.statusElement) {
|
||||
this.statusElement.textContent = STATUS[newStatus]
|
||||
}
|
||||
|
||||
// Clean up speech text when listening
|
||||
if (newStatus === 'listening' && this.client.voiceSpeechElement) {
|
||||
this.client.voiceSpeechElement.textContent = ''
|
||||
}
|
||||
|
||||
if (this.voiceEnergyContainerElement) {
|
||||
this.voiceEnergyContainerElement.className = ''
|
||||
this.voiceEnergyContainerElement.classList.add(newStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.voiceEnergyContainerElement) {
|
||||
if (this.voiceOverlayElement) {
|
||||
this.voiceOverlayElement.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
this.client.disableVoiceMode()
|
||||
})
|
||||
}
|
||||
|
||||
const particles = new Set()
|
||||
const particleColors = ['blue', 'pink']
|
||||
|
||||
for (let i = 0; i < 32; i += 1) {
|
||||
const particle = document.createElement('div')
|
||||
const randomColor = Math.floor(Math.random() * 2)
|
||||
let random = Math.floor(Math.random() * 32)
|
||||
|
||||
while (particles.has(random)) {
|
||||
random = Math.floor(Math.random() * 32)
|
||||
}
|
||||
|
||||
particles.add(random)
|
||||
particle.setAttribute('data-particle', String(random))
|
||||
particle.classList.add('voice-particle', particleColors[randomColor])
|
||||
particle.style.transform = `rotate(${
|
||||
i * 11.25
|
||||
}deg) translate(110px) rotate(-${i * 11.25}deg)`
|
||||
this.voiceEnergyContainerElement.appendChild(particle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"useDefineForClassFields": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["./src/**/*", "../aurora/src/**/*", "../aurora/global.d.ts"],
|
||||
"exclude": ["./dist", "../node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,26 +1,77 @@
|
||||
import dns from 'node:dns'
|
||||
import fs from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import dotenv from 'dotenv'
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import YAML from 'yaml'
|
||||
|
||||
dotenv.config()
|
||||
import {
|
||||
PROFILE_CONFIG_PATH,
|
||||
PROFILE_DOT_ENV_PATH
|
||||
} from '../server/src/leon-roots.ts'
|
||||
|
||||
const APP_DEV_SERVER_PORT = 5_173
|
||||
|
||||
dotenv.config({ path: PROFILE_DOT_ENV_PATH })
|
||||
|
||||
dns.setDefaultResultOrder('verbatim')
|
||||
|
||||
function readAppLeonConfig() {
|
||||
if (!fs.existsSync(PROFILE_CONFIG_PATH)) {
|
||||
throw new Error(`Profile config file not found at "${PROFILE_CONFIG_PATH}".`)
|
||||
}
|
||||
|
||||
const config = YAML.parse(fs.readFileSync(PROFILE_CONFIG_PATH, 'utf8'))
|
||||
const server = config?.server
|
||||
|
||||
if (
|
||||
!server ||
|
||||
typeof server.host !== 'string' ||
|
||||
!Number.isInteger(server.port)
|
||||
) {
|
||||
throw new Error(
|
||||
`Profile config file "${PROFILE_CONFIG_PATH}" must define server.host and server.port.`
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
host: server.host,
|
||||
port: server.port
|
||||
}
|
||||
}
|
||||
|
||||
// Map necessary Leon's env vars as Vite only expose VITE_*
|
||||
const leonConfig = readAppLeonConfig()
|
||||
process.env.VITE_LEON_NODE_ENV = process.env.LEON_NODE_ENV
|
||||
process.env.VITE_LEON_HOST = process.env.LEON_HOST
|
||||
process.env.VITE_LEON_PORT = process.env.LEON_PORT
|
||||
process.env.VITE_LEON_HOST = leonConfig.host
|
||||
process.env.VITE_LEON_PORT = String(leonConfig.port)
|
||||
|
||||
export default defineConfig({
|
||||
root: 'app/src',
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: '@aurora/style.css',
|
||||
replacement: fileURLToPath(
|
||||
new URL('../aurora/style.css', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: '@aurora',
|
||||
replacement: fileURLToPath(
|
||||
new URL('../aurora/src/index.ts', import.meta.url)
|
||||
)
|
||||
}
|
||||
]
|
||||
},
|
||||
build: {
|
||||
outDir: '../dist',
|
||||
emptyOutDir: true
|
||||
},
|
||||
server: {
|
||||
port: 3000
|
||||
port: APP_DEV_SERVER_PORT
|
||||
},
|
||||
plugins: [react()]
|
||||
})
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
declare module '*.css'
|
||||
declare module '*.scss'
|
||||
declare module '*.sass'
|
||||
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Aurora</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/main.tsx"></script>
|
||||
</body>
|
||||
<style>
|
||||
body {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
margin: 32px 0;
|
||||
font-family: var(--a-font-family);
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 24px 0;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
|
||||
import '@fontsource/source-sans-pro/200.css'
|
||||
import '@fontsource/source-sans-pro/300.css'
|
||||
import '@fontsource/source-sans-pro/400.css'
|
||||
import '@fontsource/source-sans-pro/600.css'
|
||||
import '@fontsource/source-sans-pro/700.css'
|
||||
import '@fontsource/source-sans-pro/900.css'
|
||||
import 'remixicon/fonts/remixicon.css'
|
||||
import '../src/styles/main.sass'
|
||||
import { App } from './App.tsx'
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
|
||||
if (rootElement != null) {
|
||||
ReactDOM.createRoot(rootElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
||||
} else {
|
||||
console.error('Error: Root element not found!')
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="800" height="192" viewBox="0 0 800 192" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M126.205 101.717C125.951 86.239 132.637 74.1902 146.246 65.5659C138.571 54.4049 127.251 48.1317 112.283 46.7293C106.959 46.2293 100.151 47.4878 91.8707 50.5366C82.9634 53.839 77.9488 55.4805 76.8366 55.4805C74.4854 55.4805 69.9707 54.0927 63.2805 51.2951C56.6049 48.5049 50.9098 47.1098 46.2073 47.1098C38.2902 47.239 30.9244 49.4268 24.122 53.6732C17.3171 57.9244 11.8756 63.7317 7.79512 71.0805C2.59268 80.2171 0 91.1268 0 103.807C0 115.222 1.91219 126.634 5.74634 138.056C9.33415 148.966 13.9195 158.541 19.4805 166.778C24.4244 174.263 28.7707 179.717 32.4707 183.146C37.7976 188.471 43.2341 191.012 48.8098 190.754C52.3951 190.622 57.0927 189.293 62.9098 186.749C68.3488 184.351 73.7976 183.144 79.2439 183.144C84.3146 183.144 89.5683 184.344 95.0195 186.749C101.078 189.293 106.032 190.559 109.866 190.559C115.559 190.31 120.876 187.834 125.832 183.146C127.556 181.617 129.446 179.527 131.485 176.863C133.839 173.761 136.1 170.589 138.263 167.351C140.066 164.559 141.769 161.704 143.368 158.79C145.084 155.653 146.602 152.412 147.915 149.085C148.527 147.69 149.083 146.266 149.583 144.807C150.076 143.349 150.571 141.863 151.066 140.339C146.488 138.315 142.341 135.449 138.632 131.768C130.476 123.663 126.329 113.639 126.205 101.717ZM260.783 135.634H259.883L222.193 49.5024H192.595V188.471H218.39V98.4683H219.588L251.085 169.937H269.176L300.573 98.4683H301.776V188.471H327.868V49.5024H298.473L260.783 135.634ZM102.815 31.322C109.376 23.0756 112.656 14.1976 112.656 4.68293V2.78293C112.656 2.14391 112.593 1.5122 112.471 0.878052C107.89 1.13171 103.037 2.59269 97.8976 5.25122C92.7659 7.91952 88.5293 11.2854 85.1829 15.3366C78.5049 23.3341 75.161 31.8902 75.161 41.0244V42.8268C75.161 43.4024 75.2244 44.0024 75.3463 44.6415C85.6195 45.6537 94.7756 41.2122 102.815 31.322ZM629.351 49.5024H658.344V188.473H629.351V49.5024ZM745.91 71.5927C763.105 71.5927 772.198 84.8293 772.198 99.0829H800V96.1293C800 69.2537 779.502 46.8585 745.707 46.8585C709.512 46.8585 687.92 70.6805 687.92 111.305V126.985C687.92 167.598 709.312 191.124 745.605 191.124C780.3 191.124 800 168.412 800 143.168V140.115H772.2C772.2 154.476 763.305 166.385 746.115 166.385C728.512 166.385 717.312 153.861 717.312 127.08V111.412C717.312 84.7244 728.615 71.5927 745.91 71.5927ZM438.756 137.259C438.756 155.895 427.859 166.173 411.863 166.173C395.766 166.173 384.871 155.993 384.871 137.259V49.5024H356.073V141.332C356.073 170.966 377.066 191.122 411.859 191.122C446.554 191.122 467.646 170.966 467.646 141.332V49.5024H438.756V137.259ZM563.571 107.534L542.878 102.856C530.683 100.302 524.283 94.9098 524.283 86.8634C524.283 76.1781 532.978 69.4561 546.983 69.4561C562.778 69.4561 570.676 78.5171 571.373 88.2927H598.368C598.068 63.7537 577.576 46.6512 547.078 46.6512C518.188 46.6512 495.39 61.8244 495.39 89.1073C495.39 112.727 511.39 123.715 530.585 127.9L551.078 132.68C564.876 135.837 571.873 140.422 571.873 149.883C571.873 160.683 563.273 168.015 547.18 168.015C529.883 168.015 520.783 158.849 519.988 148.563H492.19C492.598 168.215 505.593 191.122 545.785 191.122C579.576 191.122 600.476 174.324 600.476 147.029C600.466 121.78 583.568 111.905 563.571 107.534Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
@@ -0,0 +1,64 @@
|
||||
<svg width="103" height="21" viewBox="0 0 103 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_22_189)">
|
||||
<path d="M20.4846 6.48553H16.075V9.05876H20.4846V6.48553Z" fill="#29AB70"/>
|
||||
<path d="M20.4846 10.0692H16.075V12.6424H20.4846V10.0692Z" fill="url(#paint0_linear_22_189)"/>
|
||||
<path d="M20.4846 13.6511H16.075V16.2243H20.4846V13.6511Z" fill="url(#paint1_linear_22_189)"/>
|
||||
<path d="M4.5 17.2348H0.0905304V19.808H4.5V17.2348Z" fill="url(#paint2_linear_22_189)"/>
|
||||
<path d="M9.82757 17.2348H5.41811V19.808H9.82757V17.2348Z" fill="url(#paint3_linear_22_189)"/>
|
||||
<path d="M15.1569 17.2348H10.7474V19.808H15.1569V17.2348Z" fill="url(#paint4_linear_22_189)"/>
|
||||
<path d="M20.4846 17.2348H16.075V19.808H20.4846V17.2348Z" fill="url(#paint5_linear_22_189)"/>
|
||||
<path d="M15.1569 13.6511H10.7474V16.2243H15.1569V13.6511Z" fill="url(#paint6_linear_22_189)"/>
|
||||
<path d="M9.82757 13.6511H5.41811V16.2243H9.82757V13.6511Z" fill="url(#paint7_linear_22_189)"/>
|
||||
<path d="M9.82757 10.0692H5.41811V12.6424H9.82757V10.0692Z" fill="url(#paint8_linear_22_189)"/>
|
||||
<path d="M59.5733 17.011C57.6607 17.011 56.0872 15.8585 56.0588 13.834H65.1157C65.2344 13.2988 65.294 12.7521 65.2933 12.2038C65.2933 8.54729 62.7911 6.21381 59.108 6.21381C55.1709 6.21381 52.4716 9.27894 52.4716 13.074C52.4716 17.2916 55.3964 20.1046 59.5289 20.1046C62.6508 20.1046 64.8156 18.5294 65.7444 15.942L62.6668 14.7397C62.2442 16.2528 61.2035 17.011 59.5733 17.011ZM59.0956 9.11024C60.6938 9.11024 61.7949 9.98218 61.7949 11.3318L61.7664 11.4437H56.0588C56.3962 9.98219 57.634 9.11024 59.0956 9.11024Z" fill="white"/>
|
||||
<path d="M77.7348 9.44758V6.49432H66.1224V9.58788H73.2081L65.8969 16.7304V19.8222H77.9319V16.5883H70.3952L77.7348 9.44758Z" fill="white"/>
|
||||
<path d="M99.4234 10.7973C99.4245 11.0212 99.4096 11.245 99.379 11.4668H102.79C102.873 11.0331 102.913 10.5924 102.909 10.1509C102.909 7.87246 101.476 6.21381 99.086 6.21381C97.5392 6.21381 96.4435 6.97387 95.8806 8.20987V6.49617H92.2827V19.8151H95.8806V11.2466C95.8806 9.89694 96.6122 9.13865 97.7363 9.13865C98.7486 9.13865 99.4234 9.81348 99.4234 10.7973Z" fill="white"/>
|
||||
<path d="M34.6291 8.49219C33.8122 7.11412 32.2939 6.21376 30.4115 6.21376C26.755 6.21376 24.0841 9.02673 24.0841 13.216C24.0841 17.3484 26.7283 20.1046 30.4949 20.1046C32.3791 20.1046 33.8975 19.2877 34.7694 17.9114V19.8222H38.227V0.14032H34.6291V8.49219ZM31.2248 17.0483C29.2287 17.0483 27.6731 15.5868 27.6731 13.2249C27.6731 10.8346 29.2199 9.31797 31.2248 9.31797C33.1641 9.31797 34.7677 10.8363 34.7677 13.2249C34.7677 15.5779 33.1641 17.0483 31.2248 17.0483Z" fill="white"/>
|
||||
<path d="M85.2076 17.011C83.295 17.011 81.7216 15.8585 81.6932 13.834H90.7501C90.8689 13.2988 90.9284 12.7521 90.9277 12.2038C90.9277 8.54729 88.4255 6.21381 84.7424 6.21381C80.8053 6.21381 78.106 9.27894 78.106 13.074C78.106 17.2916 81.0308 20.1046 85.1632 20.1046C88.2852 20.1046 90.45 18.5294 91.3788 15.942L88.2994 14.7397C87.8785 16.2528 86.8397 17.011 85.2076 17.011ZM84.7299 9.11024C86.3282 9.11024 87.4292 9.98218 87.4292 11.3318L87.4008 11.4437H81.6897C82.0306 9.98219 83.2666 9.11024 84.7299 9.11024Z" fill="white"/>
|
||||
<path d="M46.2843 17.011C44.3717 17.011 42.7983 15.8585 42.7699 13.834H51.8268C51.9434 13.2986 51.9999 12.7518 51.9955 12.2038C51.9955 8.54729 49.4933 6.21381 45.8101 6.21381C41.873 6.21381 39.1737 9.27894 39.1737 13.074C39.1737 17.2916 42.0986 20.1046 46.231 20.1046C49.353 20.1046 51.5178 18.5294 52.4465 15.942L49.3778 14.7397C48.9552 16.2528 47.9145 17.011 46.2843 17.011ZM45.8066 9.11024C47.4049 9.11024 48.5059 9.98218 48.5059 11.3318L48.4775 11.4437H42.7699C43.1073 9.98219 44.345 9.11024 45.8066 9.11024Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_22_189" x1="18.5666" y1="12.9798" x2="17.993" y2="9.72996" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2C8C9D"/>
|
||||
<stop offset="0.04" stop-color="#298E9A"/>
|
||||
<stop offset="0.39" stop-color="#129C83"/>
|
||||
<stop offset="0.72" stop-color="#05A475"/>
|
||||
<stop offset="1" stop-color="#00A770"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_22_189" x1="16.0093" y1="16.0946" x2="20.5503" y2="13.7807" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2839BA"/>
|
||||
<stop offset="1" stop-color="#148CB3"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_22_189" x1="0.0905304" y1="18.5205" x2="4.5" y2="18.5205" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F6A500"/>
|
||||
<stop offset="1" stop-color="#F29100"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_22_189" x1="5.41811" y1="18.5205" x2="9.82757" y2="18.5205" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F29100"/>
|
||||
<stop offset="1" stop-color="#D12F5F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_22_189" x1="10.7474" y1="18.5205" x2="15.1569" y2="18.5205" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#B4197C"/>
|
||||
<stop offset="1" stop-color="#472EAD"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_22_189" x1="16.075" y1="18.5205" x2="20.4846" y2="18.5205" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2839BA"/>
|
||||
<stop offset="1" stop-color="#3072B7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_22_189" x1="10.5734" y1="15.6329" x2="15.3291" y2="14.2424" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#B4197C"/>
|
||||
<stop offset="1" stop-color="#373AAC"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_22_189" x1="5.26361" y1="14.079" x2="9.98385" y2="15.7963" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFCB00"/>
|
||||
<stop offset="1" stop-color="#D12F5F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_22_189" x1="5.79104" y1="9.66424" x2="9.45464" y2="13.0455" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFCF00"/>
|
||||
<stop offset="1" stop-color="#ED743B"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_22_189">
|
||||
<rect width="103" height="20.2448" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg width="102" height="31" viewBox="0 0 102 31" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.7443 13.5867C19.8876 10.7025 11.8767 10.4373 7.24037 11.8444C6.49582 12.0702 5.70854 11.6499 5.48295 10.9054C5.25735 10.1606 5.6772 9.37381 6.42224 9.14747C11.7444 7.53182 20.5916 7.84398 26.1827 11.1629C26.8525 11.5603 27.0722 12.4252 26.6751 13.0939C26.2779 13.7635 25.4123 13.9842 24.7443 13.5867ZM24.5852 17.8586C24.2446 18.4116 23.5217 18.5848 22.9696 18.2455C18.9205 15.7566 12.7463 15.0356 7.95602 16.4896C7.33483 16.6773 6.67869 16.3271 6.49006 15.7071C6.30283 15.0859 6.65325 14.4309 7.27333 14.242C12.7456 12.5815 19.5483 13.3858 24.1987 16.2437C24.7509 16.5837 24.9247 17.3071 24.5852 17.8586ZM22.7416 21.9613C22.471 22.4051 21.8928 22.5444 21.4505 22.2738C17.9124 20.1114 13.4591 19.6229 8.21451 20.821C7.70905 20.9369 7.20543 20.62 7.0901 20.1148C6.97436 19.6094 7.28985 19.1059 7.79642 18.9905C13.5357 17.6785 18.4589 18.2432 22.4302 20.6699C22.8729 20.9402 23.0124 21.5187 22.7416 21.9613ZM15.8319 0.229734C7.51087 0.229734 0.76506 6.97516 0.76506 15.2962C0.76506 23.618 7.51087 30.3632 15.8319 30.3632C24.1532 30.3632 30.8987 23.618 30.8987 15.2962C30.8987 6.97516 24.1532 0.229734 15.8319 0.229734Z" fill="#1ED760"/>
|
||||
<path d="M41.7566 14.1389C39.1552 13.5186 38.6919 13.0831 38.6919 12.1685C38.6919 11.304 39.5059 10.7225 40.716 10.7225C41.889 10.7225 43.052 11.1642 44.2717 12.0735C44.3086 12.101 44.3548 12.112 44.4003 12.1048C44.4458 12.0979 44.486 12.0731 44.5128 12.0353L45.783 10.2448C45.8351 10.1712 45.8209 10.0694 45.7506 10.0131C44.2992 8.84846 42.6648 8.28227 40.7545 8.28227C37.9456 8.28227 35.9836 9.96772 35.9836 12.3797C35.9836 14.9661 37.6761 15.8817 40.6008 16.5886C43.0902 17.162 43.5103 17.6423 43.5103 18.5012C43.5103 19.4527 42.6607 20.0443 41.2935 20.0443C39.7752 20.0443 38.5365 19.5328 37.1509 18.3329C37.1166 18.3034 37.0694 18.2898 37.0259 18.2923C36.9802 18.2961 36.9383 18.3173 36.9091 18.3523L35.4849 20.0473C35.4252 20.1177 35.4327 20.2228 35.5016 20.2836C37.1139 21.7227 39.0964 22.483 41.2359 22.483C44.2627 22.483 46.2185 20.8291 46.2185 18.2694C46.2185 16.1061 44.926 14.9098 41.7566 14.1389Z" fill="#1ED760"/>
|
||||
<path d="M55.2635 17.0382C55.2635 18.8649 54.1383 20.1396 52.5271 20.1396C50.9343 20.1396 49.7328 18.807 49.7328 17.0382C49.7328 15.2697 50.9343 13.9369 52.5271 13.9369C54.1124 13.9369 55.2635 15.2409 55.2635 17.0382ZM53.0663 11.5732C51.7544 11.5732 50.6783 12.09 49.791 13.1488V11.957C49.791 11.8629 49.7147 11.7863 49.6206 11.7863H47.2914C47.1973 11.7863 47.1212 11.8629 47.1212 11.957V25.1992C47.1212 25.2933 47.1973 25.3699 47.2914 25.3699H49.6206C49.7147 25.3699 49.791 25.2933 49.791 25.1992V21.0194C50.6785 22.0154 51.7548 22.5019 53.0663 22.5019C55.504 22.5019 57.9718 20.6253 57.9718 17.0382C57.9718 13.4503 55.504 11.5732 53.0663 11.5732Z" fill="#1ED760"/>
|
||||
<path d="M64.2972 20.1587C62.6274 20.1587 61.3686 18.817 61.3686 17.0382C61.3686 15.2519 62.5839 13.9556 64.2587 13.9556C65.9393 13.9556 67.2067 15.2972 67.2067 17.0772C67.2067 18.8633 65.9831 20.1587 64.2972 20.1587ZM64.2972 11.5733C61.1579 11.5733 58.6984 13.9907 58.6984 17.0772C58.6984 20.1302 61.141 22.5223 64.2587 22.5223C67.4093 22.5223 69.8763 20.113 69.8763 17.0382C69.8763 13.9737 67.4262 11.5733 64.2972 11.5733Z" fill="#1ED760"/>
|
||||
<path d="M76.5803 11.7863H74.0171V9.16561C74.0171 9.07152 73.9411 8.99505 73.847 8.99505H71.518C71.4237 8.99505 71.3471 9.07152 71.3471 9.16561V11.7863H70.2271C70.1332 11.7863 70.0574 11.8629 70.0574 11.957V13.9589C70.0574 14.0529 70.1332 14.1295 70.2271 14.1295H71.3471V19.3096C71.3471 21.403 72.389 22.4643 74.4438 22.4643C75.2793 22.4643 75.9726 22.2918 76.6258 21.9213C76.6789 21.8917 76.712 21.8344 76.712 21.7736V19.8673C76.712 19.8084 76.6812 19.753 76.6311 19.722C76.5803 19.6902 76.5172 19.6886 76.4652 19.7143C76.0165 19.94 75.5827 20.0443 75.0978 20.0443C74.3506 20.0443 74.0171 19.7051 74.0171 18.9446V14.1295H76.5803C76.6744 14.1295 76.7502 14.0529 76.7502 13.9589V11.957C76.7502 11.8629 76.6744 11.7863 76.5803 11.7863Z" fill="#1ED760"/>
|
||||
<path d="M85.5108 11.7966V11.4747C85.5108 10.5278 85.8739 10.1055 86.6882 10.1055C87.1738 10.1055 87.5639 10.202 88.0007 10.3477C88.0545 10.3646 88.1106 10.3561 88.1543 10.3239C88.1993 10.2917 88.2247 10.2401 88.2247 10.1854V8.22252C88.2247 8.14749 88.1765 8.0811 88.1043 8.05915C87.6428 7.92188 87.0524 7.78082 86.1682 7.78082C84.0166 7.78082 82.8796 8.99238 82.8796 11.2833V11.7762H81.7605C81.6666 11.7762 81.5895 11.8527 81.5895 11.9468V13.9589C81.5895 14.053 81.6666 14.1295 81.7605 14.1295H82.8796V22.1196C82.8796 22.2137 82.9555 22.2902 83.0494 22.2902H85.3786C85.4727 22.2902 85.5493 22.2137 85.5493 22.1196V14.1295H87.7242L91.0557 22.1175C90.6775 22.9568 90.3056 23.1238 89.7979 23.1238C89.3875 23.1238 88.9553 23.0012 88.5135 22.7594C88.4719 22.7365 88.4228 22.7328 88.3778 22.7466C88.3334 22.7625 88.2959 22.7954 88.277 22.8388L87.4876 24.5708C87.45 24.6526 87.482 24.7488 87.5608 24.7912C88.385 25.2376 89.1291 25.4281 90.0485 25.4281C91.7685 25.4281 92.7192 24.6268 93.5574 22.4714L97.5985 12.029C97.6187 11.9765 97.6127 11.9173 97.5805 11.8705C97.5485 11.8244 97.4969 11.7966 97.4404 11.7966H95.0154C94.9429 11.7966 94.8778 11.8426 94.8541 11.9108L92.37 19.0063L89.6491 11.9061C89.6243 11.8401 89.5607 11.7966 89.49 11.7966H85.5108Z" fill="#1ED760"/>
|
||||
<path d="M80.3336 11.7863H78.0042C77.9101 11.7863 77.8336 11.8629 77.8336 11.957V22.1196C77.8336 22.2137 77.9101 22.2902 78.0042 22.2902H80.3336C80.4275 22.2902 80.5041 22.2137 80.5041 22.1196V11.957C80.5041 11.8629 80.4275 11.7863 80.3336 11.7863Z" fill="#1ED760"/>
|
||||
<path d="M79.1806 7.15901C78.258 7.15901 77.509 7.90621 77.509 8.82882C77.509 9.75215 78.258 10.5001 79.1806 10.5001C80.1031 10.5001 80.8512 9.75215 80.8512 8.82882C80.8512 7.90621 80.1031 7.15901 79.1806 7.15901Z" fill="#1ED760"/>
|
||||
<path d="M99.6825 12.7972H99.2557V13.3415H99.6825C99.8955 13.3415 100.023 13.2372 100.023 13.0692C100.023 12.8922 99.8955 12.7972 99.6825 12.7972ZM99.9592 13.5736L100.423 14.2228H100.032L99.6145 13.6274H99.2557V14.2228H98.9285V12.502H99.6958C100.095 12.502 100.358 12.7066 100.358 13.0509C100.358 13.333 100.195 13.5054 99.9592 13.5736ZM99.5958 11.9295C98.7561 11.9295 98.1206 12.5969 98.1206 13.4143C98.1206 14.2313 98.7516 14.8898 99.5871 14.8898C100.427 14.8898 101.063 14.2228 101.063 13.405C101.063 12.588 100.431 11.9295 99.5958 11.9295ZM99.5871 15.0538C98.6652 15.0538 97.9479 14.3134 97.9479 13.4143C97.9479 12.5152 98.6746 11.7664 99.5958 11.7664C100.517 11.7664 101.235 12.5067 101.235 13.405C101.235 14.3042 100.509 15.0538 99.5871 15.0538Z" fill="#1ED760"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 57 KiB |
@@ -0,0 +1,25 @@
|
||||
<svg width="130" height="25" viewBox="0 0 130 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_22_225)">
|
||||
<path d="M94.1544 10.5232C93.6569 13.0503 93.2788 16.1346 93.0799 17.408H92.9405C92.7813 16.0948 92.4033 13.0303 91.8859 10.543L90.6125 4.35469H86.7323V21.0494H89.14V7.28786L89.3786 8.57318L91.8262 21.0494H94.2341L96.6416 8.57318L96.8999 7.28043V15.5532L96.9001 21.0494H99.3078V4.35469H95.388L94.1544 10.5232Z" fill="white"/>
|
||||
<path d="M105.815 18.5423C105.596 18.9999 105.119 19.3181 104.641 19.3181C104.084 19.3181 103.865 18.8805 103.865 17.8059V8.85182H101.119V17.9652C101.119 20.2136 101.855 21.2483 103.487 21.2483C104.601 21.2483 105.497 20.7508 106.114 19.557H106.173L106.412 21.0494H108.561V8.85182H105.815V18.5423Z" fill="white"/>
|
||||
<path d="M113.874 13.6672C112.978 13.0106 112.421 12.5728 112.421 11.6176C112.421 10.9411 112.74 10.563 113.496 10.563C114.272 10.563 114.531 11.1003 114.551 12.9309L116.859 12.8314C117.038 9.86658 116.063 8.6328 113.536 8.6328C111.188 8.6328 110.033 9.68756 110.033 11.8564C110.033 13.8263 110.989 14.7218 112.541 15.8958C113.874 16.9305 114.65 17.5076 114.65 18.3432C114.65 18.98 114.252 19.4177 113.555 19.4177C112.74 19.4177 112.262 18.6418 112.382 17.2888L110.054 17.3284C109.695 19.8555 110.71 21.3279 113.416 21.3279C115.784 21.3279 117.018 20.2337 117.018 18.0448C117.018 16.055 116.023 15.2591 113.874 13.6672Z" fill="white"/>
|
||||
<path d="M118.53 21.0494H121.157V8.8518H118.53V21.0494Z" fill="white"/>
|
||||
<path d="M119.863 4.03635C118.849 4.03635 118.372 4.41446 118.372 5.72779C118.372 7.08092 118.849 7.41913 119.863 7.41913C120.898 7.41913 121.356 7.08092 121.356 5.72779C121.356 4.47414 120.898 4.03635 119.863 4.03635Z" fill="white"/>
|
||||
<path d="M129.992 16.652L127.584 16.5325C127.584 18.6816 127.345 19.3779 126.53 19.3779C125.714 19.3779 125.574 18.602 125.574 16.0749V13.707C125.574 11.2594 125.734 10.4834 126.549 10.4834C127.306 10.4834 127.505 11.2196 127.505 13.4881L129.892 13.3289C130.052 11.4385 129.813 10.1451 129.077 9.40885C128.539 8.87158 127.724 8.61302 126.589 8.61302C123.923 8.61302 122.828 10.0457 122.828 14.0651V15.7763C122.828 19.9154 123.764 21.2483 126.49 21.2483C127.644 21.2483 128.44 21.0096 128.977 20.4923C129.753 19.7758 130.052 18.5423 129.992 16.652Z" fill="white"/>
|
||||
<path d="M39.3351 18.7816C39.4874 18.3835 39.5639 17.7337 39.5639 16.8312V13.03C39.5639 12.1543 39.4874 11.5144 39.3351 11.1095C39.1823 10.7048 38.9137 10.5024 38.5291 10.5024C38.1574 10.5024 37.8953 10.7048 37.743 11.1095C37.5903 11.5144 37.5142 12.1543 37.5142 13.03V16.8312C37.5142 17.7337 37.5868 18.3835 37.7329 18.7816C37.8788 19.1795 38.1441 19.3786 38.5291 19.3786C38.9137 19.3786 39.1823 19.1795 39.3351 18.7816ZM36.2504 20.692C35.6995 20.321 35.3081 19.7436 35.0763 18.9608C34.8438 18.1779 34.7279 17.1365 34.7279 15.836V14.0649C34.7279 12.7514 34.8603 11.6965 35.1258 10.9004C35.3912 10.1045 35.8056 9.52419 36.3697 9.15917C36.9334 8.79449 37.6732 8.61196 38.5886 8.61196C39.4908 8.61196 40.2139 8.79778 40.7581 9.16909C41.3019 9.54066 41.6997 10.1212 41.9521 10.9104C42.2041 11.6999 42.3303 12.7514 42.3303 14.0649V15.836C42.3303 17.1365 42.2075 18.1815 41.962 18.9706C41.7165 19.7602 41.3184 20.3373 40.768 20.7021C40.2171 21.0669 39.4709 21.2494 38.5291 21.2494C37.5605 21.2494 36.8007 21.0636 36.2504 20.692Z" fill="white"/>
|
||||
<path d="M78.8683 10.7304C78.7291 10.903 78.6361 11.1849 78.5898 11.5763C78.5431 11.9678 78.5202 12.5613 78.5202 13.3575V14.2332H80.5303V13.3575C80.5303 12.5748 80.5035 11.9811 80.4508 11.5763C80.3976 11.1717 80.3017 10.8865 80.162 10.7205C80.0227 10.5547 79.8069 10.4717 79.5151 10.4717C79.2233 10.4717 79.0078 10.5582 78.8683 10.7304ZM78.5202 15.8651V16.482C78.5202 17.2651 78.5431 17.8522 78.5898 18.2435C78.6361 18.6349 78.7321 18.92 78.8786 19.0993C79.0243 19.2784 79.2497 19.3678 79.555 19.3678C79.9661 19.3678 80.2481 19.2086 80.4009 18.8903C80.5532 18.5718 80.6363 18.0413 80.6496 17.2979L83.0179 17.4373C83.0312 17.5438 83.0378 17.6897 83.0378 17.8753C83.0378 19.0031 82.7293 19.8454 82.1125 20.4028C81.4954 20.9599 80.623 21.2386 79.4952 21.2386C78.142 21.2386 77.1931 20.8141 76.6491 19.9649C76.1052 19.116 75.8333 17.8026 75.8333 16.0244V13.8948C75.8333 12.0639 76.1152 10.7273 76.6791 9.88457C77.243 9.04213 78.2085 8.62074 79.5749 8.62074C80.517 8.62074 81.2398 8.79341 81.7442 9.1383C82.2483 9.48349 82.6031 10.0208 82.809 10.7502C83.0145 11.4801 83.1178 12.4887 83.1178 13.7754V15.8651H78.5202Z" fill="white"/>
|
||||
<path d="M29.5625 15.6366L26.4379 4.35238H29.1644L30.259 9.4671C30.5378 10.7277 30.7432 11.8024 30.8761 12.6912H30.9556C31.0483 12.0543 31.254 10.9865 31.5726 9.48705L32.7071 4.35238H35.4336L32.2691 15.6366V21.0503H29.5625V15.6366Z" fill="white"/>
|
||||
<path d="M51.1979 8.85064V21.0502H49.0483L48.8095 19.5576H48.7501C48.1658 20.6856 47.2902 21.2493 46.1228 21.2493C45.3136 21.2493 44.7162 20.984 44.3319 20.4532C43.9469 19.9226 43.7545 19.0935 43.7545 17.9655V8.85064H46.5009V17.8065C46.5009 18.3505 46.5607 18.7385 46.6803 18.9707C46.7996 19.2028 46.9984 19.319 47.2772 19.319C47.5161 19.319 47.7449 19.246 47.9638 19.0999C48.1828 18.9541 48.345 18.7685 48.4515 18.5425V8.85064H51.1979Z" fill="white"/>
|
||||
<path d="M65.2854 8.85064V21.0502H63.1358L62.8969 19.5576H62.8376C62.2533 20.6856 61.3777 21.2493 60.2103 21.2493C59.4011 21.2493 58.8037 20.984 58.4194 20.4532C58.0344 19.9226 57.842 19.0935 57.842 17.9655V8.85064H60.5884V17.8065C60.5884 18.3505 60.6482 18.7385 60.7675 18.9707C60.8871 19.2028 61.0859 19.319 61.3646 19.319C61.6035 19.319 61.8323 19.246 62.0513 19.0999C62.2703 18.9541 62.4324 18.7685 62.5389 18.5425V8.85064H65.2854Z" fill="white"/>
|
||||
<path d="M58.6608 6.56181H55.9343V21.0501H53.2476V6.56181H50.5211V4.35272H58.6608V6.56181Z" fill="white"/>
|
||||
<path d="M71.8573 15.7769C71.8573 16.6661 71.8207 17.3626 71.7478 17.8665C71.675 18.371 71.552 18.7293 71.3795 18.9413C71.2073 19.1538 70.9748 19.2598 70.683 19.2598C70.4574 19.2598 70.2485 19.2068 70.0564 19.1006C69.8637 18.9943 69.7082 18.8353 69.5887 18.6228V11.697C69.6812 11.3656 69.8406 11.0935 70.0662 10.8811C70.2916 10.669 70.5371 10.5627 70.8026 10.5627C71.0813 10.5627 71.2966 10.6722 71.4494 10.891C71.6017 11.1098 71.708 11.4782 71.7678 11.9956C71.8275 12.513 71.8573 13.2494 71.8573 14.2047V15.7769ZM74.3752 10.8006C74.2089 10.0315 73.9402 9.47416 73.569 9.12899C73.1974 8.7841 72.6865 8.61159 72.0367 8.61159C71.5321 8.61159 71.0614 8.7543 70.6235 9.03944C70.1858 9.32485 69.8472 9.69963 69.6086 10.164H69.5882L69.5887 3.73559H66.9416V21.0501H69.2105L69.4892 19.8957H69.5488C69.7609 20.3073 70.0793 20.632 70.5039 20.8709C70.9285 21.1098 71.3995 21.2292 71.9171 21.2292C72.8456 21.2292 73.5291 20.8014 73.9668 19.9456C74.4047 19.0899 74.6239 17.7533 74.6239 15.9353V14.0049C74.6239 12.6386 74.5406 11.5705 74.3752 10.8006Z" fill="white"/>
|
||||
<path d="M12.0901 24.6219C18.7674 24.6219 24.1803 19.209 24.1803 12.5318C24.1803 5.85459 18.7674 0.441692 12.0901 0.441692C5.41292 0.441692 0 5.85459 0 12.5318C0 19.209 5.41292 24.6219 12.0901 24.6219Z" fill="#ED1D24"/>
|
||||
<path d="M12.0901 5.18941C8.03538 5.18941 4.74754 8.47706 4.74754 12.5318C4.74754 16.5865 8.03538 19.8743 12.0901 19.8743C16.1448 19.8743 19.4326 16.5865 19.4326 12.5318C19.4326 8.47706 16.1448 5.18941 12.0901 5.18941ZM12.0901 6.16527C15.6009 6.16527 18.4567 9.02104 18.4567 12.5318C18.4567 16.0425 15.6009 18.8984 12.0901 18.8984C8.57936 18.8984 5.72349 16.0425 5.72349 12.5318C5.72349 9.02104 8.57936 6.16527 12.0901 6.16527Z" fill="white"/>
|
||||
<path d="M9.75453 16.1039L15.9369 12.5318L9.75453 8.95969V16.1039Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_22_225">
|
||||
<rect width="130" height="24.18" fill="white" transform="translate(0 0.442017)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,36 @@
|
||||
.aurora-preview-page {
|
||||
min-height: 100vh;
|
||||
padding: 32px;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(28, 117, 219, 0.16), transparent 30%),
|
||||
radial-gradient(circle at top right, rgba(237, 41, 122, 0.14), transparent 30%),
|
||||
var(--black-color);
|
||||
color: var(--white-color);
|
||||
}
|
||||
|
||||
.aurora-preview-shell {
|
||||
width: min(1_440px, 100%);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.aurora-preview-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.aurora-preview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.aurora-preview-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.aurora-preview-widget {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
const AURORA_PREVIEW_DEV_SERVER_PORT = 5_175
|
||||
|
||||
export default defineConfig({
|
||||
root: fileURLToPath(new URL('.', import.meta.url)),
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: '@aurora/style.css',
|
||||
replacement: fileURLToPath(new URL('../style.css', import.meta.url))
|
||||
},
|
||||
{
|
||||
find: '@aurora',
|
||||
replacement: fileURLToPath(new URL('../src/index.ts', import.meta.url))
|
||||
}
|
||||
]
|
||||
},
|
||||
server: {
|
||||
port: AURORA_PREVIEW_DEV_SERVER_PORT
|
||||
},
|
||||
plugins: [react()]
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
@import '../../styles/main.sass'
|
||||
|
||||
.aurora-button
|
||||
position: relative
|
||||
display: inline-block
|
||||
font-size: var(--a-font-size-md)
|
||||
line-height: var(--a-line-height-md)
|
||||
font-weight: var(--a-font-weight-semi-bold)
|
||||
border-radius: var(--a-border-radius-md)
|
||||
height: var(--a-button-size-md)
|
||||
border: none
|
||||
cursor: pointer
|
||||
padding: 0 var(--a-space-lg)
|
||||
box-shadow: 0 0 10px 2px transparent
|
||||
transition: opacity var(--a-transition-duration-main) var(--a-transition-timing-main), transform var(--a-transition-duration-main), background-color var(--a-transition-duration-main) var(--a-transition-timing-main), border-color var(--a-transition-duration-main) var(--a-transition-timing-main), color var(--a-transition-duration-main) var(--a-transition-timing-main), box-shadow var(--a-transition-duration-main) var(--a-transition-timing-main)
|
||||
&:hover:not(&--disabled, &--loading)
|
||||
transform: scale3d(1.01, 1.01, 1.01)
|
||||
&:active:not(&--disabled, &--loading)
|
||||
transform: scale3d(.96, .96, .96)
|
||||
|
||||
&--primary
|
||||
color: var(--a-color-text)
|
||||
background-color: var(--a-color-accent)
|
||||
&:hover:not(.aurora-button--disabled, .aurora-button--loading)
|
||||
background-color: var(--a-color-accent-hover)
|
||||
|
||||
&--secondary
|
||||
background-color: transparent
|
||||
color: var(--a-color-accent)
|
||||
border: 1px solid var(--a-color-accent)
|
||||
&:hover:not(.aurora-button--disabled, .aurora-button--loading), &:hover:not(.aurora-button--disabled, .aurora-button--loading) .aurora-icon
|
||||
border-color: var(--a-color-accent-hover)
|
||||
color: var(--a-color-accent-hover)
|
||||
.aurora-icon
|
||||
color: var(--a-color-accent)
|
||||
|
||||
&--danger
|
||||
background-color: var(--a-color-background-danger)
|
||||
color: var(--a-color-text-danger)
|
||||
&:hover:not(.aurora-button--disabled, .aurora-button--loading)
|
||||
background-color: var(--a-color-danger-hover)
|
||||
|
||||
&--light
|
||||
background-color: transparent
|
||||
color: var(--a-color-accent)
|
||||
&:hover:not(.aurora-button--disabled, .aurora-button--loading), &:hover:not(.aurora-button--disabled, .aurora-button--loading) .aurora-icon
|
||||
color: var(--a-color-accent-hover)
|
||||
.aurora-icon
|
||||
color: var(--a-color-accent)
|
||||
|
||||
&--disabled
|
||||
opacity: var(--a-opacity-disabled)
|
||||
cursor: not-allowed
|
||||
|
||||
&--loading
|
||||
opacity: var(--a-opacity-disabled)
|
||||
cursor: wait
|
||||
|
||||
.aurora-loader
|
||||
&::before, &::after
|
||||
background-color: var(--a-color-white)
|
||||
@@ -0,0 +1,114 @@
|
||||
import classNames from 'clsx'
|
||||
|
||||
import { generateKeyId } from '../../lib/utils'
|
||||
import { Flexbox, Icon, Loader } from '../..'
|
||||
|
||||
import './button.sass'
|
||||
|
||||
interface ButtonOnClickData {
|
||||
name: string | undefined
|
||||
value: string | number | undefined
|
||||
}
|
||||
|
||||
export interface ButtonProps {
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
children?: any
|
||||
// children?: React.ReactNode
|
||||
type?: 'button' | 'submit'
|
||||
iconName?: string
|
||||
iconPosition?: 'left' | 'right'
|
||||
secondary?: boolean
|
||||
danger?: boolean
|
||||
light?: boolean
|
||||
disabled?: boolean
|
||||
loading?: boolean
|
||||
name?: string
|
||||
value?: string | number | undefined
|
||||
onClick?: (data: ButtonOnClickData) => void
|
||||
}
|
||||
|
||||
export function Button({
|
||||
children,
|
||||
type = 'button',
|
||||
iconName,
|
||||
iconPosition = 'left',
|
||||
secondary,
|
||||
danger,
|
||||
light,
|
||||
disabled,
|
||||
loading,
|
||||
name,
|
||||
value,
|
||||
onClick
|
||||
}: ButtonProps) {
|
||||
let variant = 'primary'
|
||||
|
||||
if (secondary) {
|
||||
variant = 'secondary'
|
||||
} else if (danger) {
|
||||
variant = 'danger'
|
||||
} else if (light) {
|
||||
variant = 'light'
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
key={`aurora-button_${generateKeyId()}`}
|
||||
type={type}
|
||||
name={name}
|
||||
value={value}
|
||||
className={classNames('aurora-button', {
|
||||
'aurora-button--disabled': disabled,
|
||||
'aurora-button--loading': loading,
|
||||
[`aurora-button--${variant}`]: variant
|
||||
})}
|
||||
disabled={disabled || loading}
|
||||
onClick={(event) => {
|
||||
if (type !== 'button') {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
const data = {
|
||||
name,
|
||||
value
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
onClick(data)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<>
|
||||
{iconName && iconPosition === 'left' && (
|
||||
<Flexbox
|
||||
flexDirection="row"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gap="xs"
|
||||
>
|
||||
<Icon iconName={iconName} type="line" />
|
||||
{children}
|
||||
</Flexbox>
|
||||
)}
|
||||
{iconName && iconPosition === 'right' && (
|
||||
<Flexbox
|
||||
flexDirection="row"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gap="xs"
|
||||
>
|
||||
{children}
|
||||
<Icon iconName={iconName} type="line" />
|
||||
</Flexbox>
|
||||
)}
|
||||
{!iconName && children}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './button'
|
||||
@@ -0,0 +1,12 @@
|
||||
@import '../../styles/main.sass'
|
||||
|
||||
.aurora-card
|
||||
position: relative
|
||||
display: inline-block
|
||||
width: auto
|
||||
padding: var(--a-space-md)
|
||||
background-color: var(--a-color-background-surface-secondary)
|
||||
border-radius: var(--a-border-radius-md)
|
||||
|
||||
&--full-width
|
||||
width: 100%
|
||||
@@ -0,0 +1,25 @@
|
||||
import classNames from 'clsx'
|
||||
|
||||
import { generateKeyId } from '../../lib/utils'
|
||||
|
||||
import './card.sass'
|
||||
|
||||
export interface CardProps {
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
children: any
|
||||
// children: React.ReactNode
|
||||
fullWidth?: boolean
|
||||
}
|
||||
|
||||
export function Card({ children, fullWidth }: CardProps) {
|
||||
return (
|
||||
<div
|
||||
key={`aurora-card_${generateKeyId()}`}
|
||||
className={classNames('aurora-card', {
|
||||
'aurora-card--full-width': fullWidth
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './card'
|
||||
@@ -0,0 +1,39 @@
|
||||
@import '../../styles/main.sass'
|
||||
|
||||
.aurora-checkbox
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: var(--a-space-sm)
|
||||
cursor: pointer
|
||||
&[data-disabled]
|
||||
cursor: not-allowed
|
||||
|
||||
.aurora-checkbox-placeholder
|
||||
width: var(--a-checkbox-radio-size)
|
||||
height: var(--a-checkbox-radio-size)
|
||||
|
||||
.aurora-checkbox-control
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
width: var(--a-checkbox-radio-size)
|
||||
height: var(--a-checkbox-radio-size)
|
||||
border-radius: var(--a-border-radius-xs)
|
||||
border: 1px solid var(--a-color-input-border)
|
||||
background-color: var(--a-color-input-background)
|
||||
transition: background-color var(--a-transition-duration-main) var(--a-transition-timing-main), border-color var(--a-transition-duration-main) var(--a-transition-timing-main)
|
||||
|
||||
&[data-hover]:not([data-state="checked"])
|
||||
border-color: var(--a-color-input-border-hover)
|
||||
background-color: var(--a-color-input-background-hover)
|
||||
&[data-state="checked"]
|
||||
border-color: transparent
|
||||
background-color: var(--a-color-input-border-focus)
|
||||
&[data-disabled]
|
||||
opacity: var(--a-opacity-disabled)
|
||||
|
||||
.aurora-checkbox-label
|
||||
color: var(--a-color-text)
|
||||
font-size: var(--a-font-size-md)
|
||||
&[data-disabled]
|
||||
opacity: var(--a-opacity-disabled)
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Checkbox as ArkCheckbox,
|
||||
type CheckboxCheckedChangeDetails,
|
||||
type CheckboxRootProps as ArkCheckboxProps
|
||||
} from '@ark-ui/react/checkbox'
|
||||
|
||||
import { Icon } from '../icon'
|
||||
|
||||
import './checkbox.sass'
|
||||
|
||||
interface CheckboxOnChangeData {
|
||||
name: string
|
||||
value: string | undefined
|
||||
isChecked: boolean
|
||||
}
|
||||
|
||||
export interface CheckboxProps
|
||||
extends Pick<
|
||||
ArkCheckboxProps,
|
||||
'value' | 'checked' | 'disabled' | 'required'
|
||||
> {
|
||||
name: string
|
||||
label?: string
|
||||
onChange?: (data: CheckboxOnChangeData) => void
|
||||
}
|
||||
|
||||
export function Checkbox({
|
||||
name,
|
||||
label,
|
||||
checked,
|
||||
value,
|
||||
disabled,
|
||||
required,
|
||||
onChange
|
||||
}: CheckboxProps) {
|
||||
const [isChecked, setIsChecked] = useState(checked)
|
||||
|
||||
return (
|
||||
<ArkCheckbox.Root
|
||||
className="aurora-checkbox"
|
||||
name={name}
|
||||
value={value}
|
||||
checked={isChecked}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
onCheckedChange={(event: CheckboxCheckedChangeDetails) => {
|
||||
setIsChecked(event.checked as boolean)
|
||||
|
||||
const data = {
|
||||
name,
|
||||
value,
|
||||
isChecked: !!event.checked
|
||||
}
|
||||
|
||||
if (onChange) {
|
||||
onChange(data)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ArkCheckbox.HiddenInput />
|
||||
<ArkCheckbox.Control className="aurora-checkbox-control">
|
||||
{isChecked ? (
|
||||
<Icon iconName="check" size="sm" animated />
|
||||
) : (
|
||||
<div className="aurora-checkbox-placeholder" />
|
||||
)}
|
||||
</ArkCheckbox.Control>
|
||||
<ArkCheckbox.Label className="aurora-checkbox-label">
|
||||
{label}
|
||||
</ArkCheckbox.Label>
|
||||
</ArkCheckbox.Root>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './checkbox'
|
||||
@@ -0,0 +1,28 @@
|
||||
.aurora-circular-progress
|
||||
position: relative
|
||||
|
||||
&--sm
|
||||
width: 64px
|
||||
height: 64px
|
||||
&--md
|
||||
width: 128px
|
||||
height: 128px
|
||||
&--lg
|
||||
width: 256px
|
||||
height: 256px
|
||||
|
||||
path
|
||||
transition: stroke-dashoffset var(--a-transition-duration-main) var(--a-transition-timing-main)
|
||||
&:nth-child(1)
|
||||
stroke: var(--a-color-background-surface-secondary)
|
||||
stroke-width: 32px
|
||||
&:nth-child(2)
|
||||
stroke: var(--a-color-accent)
|
||||
stroke-width: 12px
|
||||
stroke-linecap: round
|
||||
|
||||
.aurora-circular-progress-content
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: 50%
|
||||
transform: translate(-50%, -50%)
|
||||
@@ -0,0 +1,41 @@
|
||||
import classNames from 'clsx'
|
||||
|
||||
import { generateKeyId } from '../../lib/utils'
|
||||
|
||||
import './circular-progress.sass'
|
||||
|
||||
export interface CircularProgressProps {
|
||||
value: number
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
children?: any
|
||||
// children?: React.ReactNode
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
|
||||
export function CircularProgress({
|
||||
value,
|
||||
children,
|
||||
size = 'md'
|
||||
}: CircularProgressProps) {
|
||||
const total = 100
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`aurora-circular-progress_${generateKeyId()}`}
|
||||
className={classNames('aurora-circular-progress', {
|
||||
[`aurora-circular-progress--${size}`]: size
|
||||
})}
|
||||
>
|
||||
<svg viewBox="0 0 276 276" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M138 16C154.021 16 169.886 19.1556 184.687 25.2867C199.489 31.4178 212.938 40.4042 224.267 51.733C235.596 63.0617 244.582 76.5109 250.713 91.3126C256.844 106.114 260 121.979 260 138C260 154.021 256.844 169.886 250.713 184.687C244.582 199.489 235.596 212.938 224.267 224.267C212.938 235.596 199.489 244.582 184.687 250.713C169.886 256.844 154.021 260 138 260C121.979 260 106.114 256.844 91.3126 250.713C76.5109 244.582 63.0617 235.596 51.7329 224.267C40.4042 212.938 31.4177 199.489 25.2867 184.687C19.1556 169.886 16 154.021 16 138C16 121.979 19.1556 106.114 25.2867 91.3126C31.4178 76.5108 40.4043 63.0617 51.733 51.7329C63.0618 40.4042 76.511 31.4177 91.3127 25.2867C106.114 19.1556 121.979 16 138 16L138 16Z" />
|
||||
<path
|
||||
pathLength={total}
|
||||
strokeDasharray={total}
|
||||
strokeDashoffset={total - value}
|
||||
d="M138 16C154.021 16 169.886 19.1556 184.687 25.2867C199.489 31.4178 212.938 40.4042 224.267 51.733C235.596 63.0617 244.582 76.5109 250.713 91.3126C256.844 106.114 260 121.979 260 138C260 154.021 256.844 169.886 250.713 184.687C244.582 199.489 235.596 212.938 224.267 224.267C212.938 235.596 199.489 244.582 184.687 250.713C169.886 256.844 154.021 260 138 260C121.979 260 106.114 256.844 91.3126 250.713C76.5109 244.582 63.0617 235.596 51.7329 224.267C40.4042 212.938 31.4177 199.489 25.2867 184.687C19.1556 169.886 16 154.021 16 138C16 121.979 19.1556 106.114 25.2867 91.3126C31.4178 76.5108 40.4043 63.0617 51.733 51.7329C63.0618 40.4042 76.511 31.4177 91.3127 25.2867C106.114 19.1556 121.979 16 138 16L138 16Z"
|
||||
/>
|
||||
</svg>
|
||||
<div className="aurora-circular-progress-content">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './circular-progress'
|
||||
@@ -0,0 +1,92 @@
|
||||
@import '../../styles/main.sass'
|
||||
|
||||
.aurora-flexbox
|
||||
position: relative
|
||||
display: flex
|
||||
flex-direction: column
|
||||
height: 100%
|
||||
|
||||
&--padding
|
||||
padding: var(--a-space-xl) var(--a-space-md)
|
||||
&--padding-x
|
||||
padding-left: var(--a-space-md)
|
||||
padding-right: var(--a-space-md)
|
||||
&--padding-y
|
||||
padding-top: var(--a-space-xl)
|
||||
padding-bottom: var(--a-space-xl)
|
||||
|
||||
&--full-width
|
||||
width: 100%
|
||||
|
||||
&--flex
|
||||
display: flex
|
||||
&--inline-flex
|
||||
display: inline-flex
|
||||
|
||||
&--row
|
||||
flex-direction: row
|
||||
&--row-reverse
|
||||
flex-direction: row-reverse
|
||||
&--column
|
||||
flex-direction: column
|
||||
&--column-reverse
|
||||
flex-direction: column-reverse
|
||||
|
||||
&--align-flex-start
|
||||
align-items: flex-start
|
||||
&--align-center
|
||||
align-items: center
|
||||
&--align-flex-end
|
||||
align-items: flex-end
|
||||
&--align-stretch
|
||||
align-items: stretch
|
||||
&--align-baseline
|
||||
align-items: baseline
|
||||
|
||||
&--justify-flex-start
|
||||
justify-content: flex-start
|
||||
&--justify-center
|
||||
justify-content: center
|
||||
&--justify-flex-end
|
||||
justify-content: flex-end
|
||||
&--justify-space-between
|
||||
justify-content: space-between
|
||||
&--justify-space-around
|
||||
justify-content: space-around
|
||||
&--justify-space-evenly
|
||||
justify-content: space-evenly
|
||||
&--justify-stretch
|
||||
justify-content: stretch
|
||||
|
||||
&--gap-xs
|
||||
gap: var(--a-space-xs)
|
||||
&--gap-sm
|
||||
gap: var(--a-space-sm)
|
||||
&--gap-md
|
||||
gap: var(--a-space-md)
|
||||
&--gap-lg
|
||||
gap: var(--a-space-lg)
|
||||
&--gap-xl
|
||||
gap: var(--a-space-xl)
|
||||
|
||||
&--row-gap-xs
|
||||
row-gap: var(--a-space-xs)
|
||||
&--row-gap-sm
|
||||
row-gap: var(--a-space-sm)
|
||||
&--row-gap-md
|
||||
row-gap: var(--a-space-md)
|
||||
&--row-gap-lg
|
||||
row-gap: var(--a-space-lg)
|
||||
&--row-gap-xl
|
||||
row-gap: var(--a-space-xl)
|
||||
|
||||
&--column-gap-xs
|
||||
column-gap: var(--a-space-xs)
|
||||
&--column-gap-sm
|
||||
column-gap: var(--a-space-sm)
|
||||
&--column-gap-md
|
||||
column-gap: var(--a-space-md)
|
||||
&--column-gap-lg
|
||||
column-gap: var(--a-space-lg)
|
||||
&--column-gap-xl
|
||||
column-gap: var(--a-space-xl)
|
||||
@@ -0,0 +1,66 @@
|
||||
import classNames from 'clsx'
|
||||
|
||||
import { type Size } from '../../lib/types'
|
||||
import { generateKeyId } from '../../lib/utils'
|
||||
|
||||
import './flexbox.sass'
|
||||
|
||||
export interface FlexboxProps {
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
children: any
|
||||
// children: React.ReactNode
|
||||
fullWidth?: boolean
|
||||
display?: 'flex' | 'inline-flex'
|
||||
alignItems?: 'center' | 'flex-start' | 'flex-end' | 'baseline' | 'stretch'
|
||||
justifyContent?:
|
||||
| 'center'
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'space-between'
|
||||
| 'space-around'
|
||||
| 'space-evenly'
|
||||
| 'stretch'
|
||||
flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse'
|
||||
gap?: Size
|
||||
rowGap?: Size
|
||||
columnGap?: Size
|
||||
padding?: boolean
|
||||
paddingX?: boolean
|
||||
paddingY?: boolean
|
||||
}
|
||||
|
||||
export function Flexbox({
|
||||
children,
|
||||
fullWidth,
|
||||
display,
|
||||
alignItems,
|
||||
justifyContent,
|
||||
flexDirection,
|
||||
gap,
|
||||
rowGap,
|
||||
columnGap,
|
||||
padding,
|
||||
paddingX,
|
||||
paddingY
|
||||
}: FlexboxProps) {
|
||||
return (
|
||||
<div
|
||||
key={`aurora-flexbox_${generateKeyId()}`}
|
||||
className={classNames('aurora-flexbox', {
|
||||
'aurora-flexbox--full-width': fullWidth,
|
||||
'aurora-flexbox--padding': padding,
|
||||
'aurora-flexbox--padding-x': paddingX,
|
||||
'aurora-flexbox--padding-y': paddingY,
|
||||
[`aurora-flexbox--${display}`]: display,
|
||||
[`aurora-flexbox--align-${alignItems}`]: alignItems,
|
||||
[`aurora-flexbox--justify-${justifyContent}`]: justifyContent,
|
||||
[`aurora-flexbox--${flexDirection}`]: flexDirection,
|
||||
[`aurora-flexbox--gap-${gap}`]: gap,
|
||||
[`aurora-flexbox--row-gap-${rowGap}`]: rowGap,
|
||||
[`aurora-flexbox--column-gap-${columnGap}`]: columnGap
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './flexbox'
|
||||
@@ -0,0 +1,4 @@
|
||||
@import '../../styles/main.sass'
|
||||
|
||||
.aurora-form
|
||||
position: relative
|
||||
@@ -0,0 +1,51 @@
|
||||
import { generateKeyId } from '../../lib/utils'
|
||||
|
||||
import './form.sass'
|
||||
|
||||
export interface FormProps {
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
children?: any
|
||||
// children?: React.ReactNode
|
||||
onSubmit: (data: Record<string, unknown>) => void
|
||||
}
|
||||
|
||||
export function Form({ children, onSubmit }: FormProps) {
|
||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
|
||||
const form = event.currentTarget
|
||||
const formData = new FormData(form)
|
||||
const data: Record<string, unknown> = {}
|
||||
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (data[key] && key.endsWith('[]')) {
|
||||
if (Array.isArray(data[key])) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
;(data[key] as []).push(value)
|
||||
} else {
|
||||
data[key] = [data[key], value]
|
||||
}
|
||||
} else {
|
||||
if (key.endsWith('[]')) {
|
||||
data[key] = [value]
|
||||
} else {
|
||||
data[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit(data)
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
autoComplete="off"
|
||||
className="aurora-form"
|
||||
onSubmit={handleSubmit}
|
||||
key={`aurora-form_${generateKeyId()}`}
|
||||
>
|
||||
{children}
|
||||
</form>
|
||||
)
|
||||
}
|
||||