Compare commits

...

397 Commits

Author SHA1 Message Date
louistiti af27a45fcb Merge branch 'develop' into feat/akinator-rewrite 2024-09-05 22:14:40 +08:00
louistiti cae634dfd4 Merge branch 'feat/akinator-rewrite' into develop 2024-09-05 22:14:32 +08:00
louistiti 185be39172 feat(skill/akinator): complete rewrite 2024-09-05 22:14:18 +08:00
louistiti 67bc7ddb32 Merge branch 'develop' into feat/todo-list-widgets 2024-09-04 22:01:56 +08:00
louistiti ffb1aa6bdc Merge branch 'feat/todo-list-widgets' into develop 2024-09-04 22:01:48 +08:00
louistiti ae0e5b0643 fix(bridge/python): bump requests to 2.32.3 for a better compatibility with new Python version 2024-09-04 22:01:41 +08:00
louistiti fe28a80908 feat(skill/todo_list): add widget on add_totos action 2024-09-04 22:00:13 +08:00
louistiti e0e3e59960 Merge branch 'develop' into feat/todo-list-widgets 2024-09-04 08:31:50 +08:00
louistiti fbe47e98bd Merge branch 'feat/todo-list-widgets' into develop 2024-09-04 08:31:41 +08:00
louistiti aa5d76333c feat(skill/todo_list): add widgets to actions 2024-09-04 08:31:19 +08:00
louistiti 8bca0595b4 feat: new onFetch skill API and more 2024-09-01 23:16:14 +08:00
louistiti 9aa9e0ba49 Merge branch 'develop' into feat/todo-list-widgets 2024-09-01 07:24:49 +08:00
louistiti c9828b7524 Merge branch 'feat/todo-list-widgets' into develop 2024-09-01 07:24:29 +08:00
louistiti d8902f9cb6 fix: upgrade Aurora for a smoother text rendering 2024-09-01 07:23:35 +08:00
louistiti 2de7e3cad3 feat(skill/todo_list): view list widget 2024-09-01 07:08:38 +08:00
louistiti a64fed2444 feat(skill/todo_list): list of lists widget 2024-08-30 07:52:34 +08:00
louistiti d679ae8c3c feat(server): execute skill action over HTTP from widget trigger 2024-08-29 09:40:31 +08:00
louistiti 3fd78c0d4c feat(server): add widget event error handler 2024-08-29 09:24:42 +08:00
louistiti e72f2bda8c feat(server): run skill action from widget (WIP) 2024-08-29 09:20:17 +08:00
louistiti c24be71585 fix(bridge/python): event handlers binding 2024-08-28 09:32:20 +08:00
louistiti 457422687d fix(bridge/python): event handlers 2024-08-28 08:41:03 +08:00
louistiti f6fa166d87 refactor(skill/todo_list): remove unnecessary params on view_lists action 2024-08-27 09:17:20 +08:00
louistiti ee732ceca8 feat(skill/todo_list): view_lists widget action kick off 2024-08-27 09:13:59 +08:00
louistiti 719664d1e5 fix(skill/todo_list): French utterance samples 2024-08-27 08:18:04 +08:00
louistiti effbdc1976 Merge branch 'feat/python-sdk-widget-and-more' into develop 2024-08-26 22:06:48 +08:00
louistiti be0da3d1e2 feat(bridge/python): widgets support 2024-08-26 22:06:20 +08:00
louistiti 534853d67a Merge branch 'feat/widgets' into develop 2024-08-25 21:55:51 +08:00
louistiti 2e2e766d77 feat: allow skill speech answer only 2024-08-25 21:53:06 +08:00
louistiti d49912e9a0 feat(web app): render deleted widget 2024-08-25 18:34:00 +08:00
louistiti dc9e9df907 feat(skill/timer): create cancel_timer action 2024-08-25 08:11:20 +08:00
louistiti 25af621e09 fix(web app): delayed next event loop tick for auto scroll down 2024-08-25 08:04:00 +08:00
louistiti 2c809331b4 fix(skill/timer): initial progress when remaining time is negative 2024-08-24 21:21:51 +08:00
louistiti 505ba15806 Merge branch 'develop' into feat/widgets 2024-08-24 21:10:14 +08:00
louistiti d417384204 Merge branch 'feat/widgets' into develop 2024-08-24 21:10:02 +08:00
louistiti 83a561c7b9 feat(web app): finish widget fetching 2024-08-24 21:09:51 +08:00
louistiti 543513b982 refactor(bridge/nodejs): redefine widget fetch API 2024-08-24 09:44:21 +08:00
louistiti f7bb8a1100 feat: widget fetching API 2024-08-24 08:22:28 +08:00
louistiti c0d9c3d4c7 feat: fetch widget without duplicates 2024-08-23 09:19:52 +08:00
louistiti 556650d471 feat: widget fetching backbone (WIP) 2024-08-22 08:42:41 +08:00
louistiti c695aadfbe feat(skill/timer): add check_timer action 2024-08-21 09:32:07 +08:00
louistiti ff83e272c0 feat(server): skill on fetch backbone (WIP) 2024-08-20 08:45:59 +08:00
louistiti cc4e90f397 Merge branch 'develop' into feat/widgets 2024-08-16 08:13:13 +08:00
louistiti 5462151e3f Merge branch 'feat/widgets' into develop 2024-08-16 08:13:00 +08:00
louistiti 470889a797 feat(skill/timer): custom timer component 2024-08-16 08:12:46 +08:00
louistiti d77c975800 feat(skill/timer): kick off (WIP) 2024-08-15 08:09:32 +08:00
louistiti 3b5d0432ae feat(server): get NER duration unit 2024-08-14 08:19:03 +08:00
louistiti 7b81fd05da Merge branch 'develop' into feat/widgets 2024-08-13 08:39:23 +08:00
louistiti 897acf0f2c fix(web app): load feed correctly in case of broken formatted strings from local storage 2024-08-13 08:39:14 +08:00
louistiti b4df4cf747 Merge branch 'feat/widgets' into develop 2024-08-13 08:35:17 +08:00
louistiti ee9835248f feat: load widgets from the feed 2024-08-13 08:34:42 +08:00
louistiti e4d9ea737a Merge branch 'feat/widgets' into develop 2024-08-12 21:58:02 +08:00
louistiti 840ee56423 feat: upgrade @leon-ai/aurora to 1.0.0-beta.13 2024-08-12 21:57:47 +08:00
louistiti ff29aa4ddd feat: bind widget event methods 2024-08-12 21:52:03 +08:00
louistiti 7176630ef7 Merge branch 'feat/widgets' into develop 2024-08-12 00:07:23 +08:00
louistiti b66027d22f feat: Aurora components bottom to top hydration 2024-08-12 00:07:03 +08:00
louistiti 7271e9673a Merge branch 'feat/widgets' into develop 2024-08-11 09:10:37 +08:00
louistiti 2486784250 feat: form event binding 2024-08-11 09:09:53 +08:00
louistiti 782085059b Merge branch 'develop' into feat/widgets 2024-08-10 17:04:37 +08:00
louistiti 7e9800be47 feat(server): upgrade node-llama-cpp to 3.0.0-beta.44 2024-08-10 11:51:37 +08:00
louistiti c9cd93b264 perf(server): try new Lexi V2 LLM 2024-08-10 11:35:53 +08:00
louistiti 7a69714efb feat: nested widget components rendering PoC 2024-08-06 08:25:45 +08:00
louistiti 29f44f3aed chore: merge branch 'develop' into feat/widgets 2024-08-06 07:47:35 +08:00
louistiti 0a10810cf2 feat: widget interaction PoC 2024-08-06 07:45:55 +08:00
louistiti b891a22d2d fix(server): HuggingFace alternative and Llama 3.1 trial 2024-08-05 23:00:36 +08:00
louistiti 82f495acb1 fix(server): upgrade node-llama-cpp to 3.0.0-beta.41 for context error 2024-08-05 21:22:36 +08:00
louistiti bb05adb483 feat: widget hydration PoC kickoff 2024-08-04 22:54:18 +08:00
louistiti edb9b367c1 fix(server): rollback to node-llama-cpp@3.0.0-beta.37 as further version can't compile well 2024-08-01 22:05:41 +08:00
louistiti 7ecb8589fb Merge branch 'develop' into feat/widgets 2024-07-28 21:11:07 +08:00
louistiti feebb22094 feat: upgrade typescript to 5.5.4 2024-07-28 21:10:49 +08:00
louistiti 98be8a0022 feat: widget hydration (WIP) 2024-07-28 21:09:21 +08:00
louistiti e135a5fcce feat: upgrade typescript to 5.5.3 2024-07-17 10:03:49 +08:00
louistiti d23925c7d5 perf(bridge/nodejs): faster cold start by avoiding to compile unnecessary chunks 2024-07-17 09:47:27 +08:00
louistiti f8289fa809 chore: upgrade React to latest 2024-07-16 09:38:51 +08:00
louistiti fd5e952695 feat(server): enable FlashAttention for faster inference 2024-07-14 21:19:42 +08:00
louistiti 626c77d340 feat(server): upgrade node-llama-cpp to 3.0.0-beta.38 2024-07-14 21:16:40 +08:00
louistiti ac3e61d7b2 feat(server): allow custom LLM duties from skills 2024-07-07 18:48:47 +08:00
louistiti db33126664 fix(server): update duties system prompt on mood and context info set 2024-07-07 17:49:37 +08:00
louistiti 822e20f80b fix(scripts): remove description property on LLM action classifier training 2024-07-07 16:03:20 +08:00
louistiti c21051b9a7 feat(server): upgrade llama.cpp to b3322 release; node-llama-cpp to node-llama-cpp 2024-07-07 15:51:34 +08:00
louistiti dcda888de6 fix(server): persona typo 2024-07-04 09:21:02 +08:00
louistiti 0c775ba2e5 feat: use VRAM as LLM unit requirements 2024-07-03 23:41:23 +08:00
louistiti 6867e9c6db feat(server): VRAM helpers 2024-07-03 13:42:10 +08:00
louistiti 1334c2013c feat(server): has GPU helper 2024-07-03 13:34:05 +08:00
louistiti 94885de71b feat(server): get graphics compute API 2024-07-03 13:31:26 +08:00
louistiti b86775846b feat(server): get GPU device names 2024-07-03 13:24:53 +08:00
louistiti 4019bd877b chore: better comments on LLM action matching 2024-07-03 09:11:25 +08:00
louistiti 7e483b9123 feat: add inspect:gpu npm script 2024-07-02 22:12:48 +08:00
louistiti ee615e6ade refactor(python tcp server): rename the RMS threshold setting for ASR 2024-07-01 22:53:34 +08:00
louistiti b092947e2e feat(web app): add headset tips for a better voice experience 2024-07-01 22:52:16 +08:00
louistiti 1d96655b01 fix(python tcp server): overflowed on ASR 2024-07-01 22:28:18 +08:00
louistiti 8ed7c78074 fix(web app): use correct config property for LLM warm up 2024-07-01 09:45:58 +08:00
louistiti be3df774dc feat(server): boost free RAM delta for LLM 2024-07-01 09:38:20 +08:00
louistiti 2c89041b3f feat(server): upgrade node-llama-cpp to 3.0.0-beta.36 2024-07-01 09:03:02 +08:00
louistiti e4277bfcf4 feat(web app): add more info data 2024-06-30 20:04:08 +08:00
louistiti 2e351d4218 feat(web app): add info 2024-06-30 19:58:26 +08:00
louistiti e24dee3b07 feat(server): VRAM context size management 2024-06-30 19:15:24 +08:00
louistiti 8eeff3a01c feat(python tcp server): map speech synthesis hardware device choice to settings 2024-06-30 16:54:59 +08:00
louistiti a05f34422e feat(server): upgrade node-llama-cpp to 3.0.0-beta.34 2024-06-30 16:47:57 +08:00
louistiti 74241b43fe feat(python tcp server): run speech synthesis inference on CPU 2024-06-30 16:30:12 +08:00
louistiti fae15c9f4d feat(server): kill existing PyTorch thread from TCP server on start 2024-06-30 16:03:11 +08:00
louistiti 2c1b35144d feat(server): use debug verbosity by default in LLM manager 2024-06-30 15:54:51 +08:00
louistiti c25c52532f feat(server): increase LLM threads number 2024-06-30 15:54:12 +08:00
louistiti 805c65b2b2 fix(web app): init state when shouldWarmUpLLM is not enabled 2024-06-30 15:53:49 +08:00
louistiti 0bb61e5301 Merge remote-tracking branch 'origin/develop' into develop 2024-06-29 23:29:52 +08:00
louistiti d2038061e7 feat(server): disable onToken when LLM duties are warming up 2024-06-29 23:29:25 +08:00
Louis Grenard 7a42f4df93 feat(server): disable onToken when LLM duties are warming up 2024-06-29 23:28:19 +08:00
louistiti ed9961c822 feat(server): sync LLM duties warmup with UI 2024-06-27 23:36:32 +08:00
louistiti fb91551364 feat(server): warm up LLM duties when necessary (WIP) 2024-06-25 23:49:09 +08:00
louistiti 8dc725a697 refactor(scripts): differentiate PyTorch info log on macOS 2024-06-24 08:28:32 +08:00
louistiti 6c3abf30b0 fix(server): sometimes the action recognition LLM duty add whitespace to intent 2024-06-23 23:36:11 +08:00
louistiti 1314217e00 BREAKING: upgrade from Python 3.9 to Python 3.11 2024-06-23 23:29:09 +08:00
louistiti 871617649a chore(bridge/python): upgrade cx_Freeze to 7.1.1 2024-06-23 22:42:42 +08:00
louistiti 85c3a40457 chore(python tcp server): upgrade cx_Freeze to 7.1.1 2024-06-23 22:41:46 +08:00
louistiti 0661e9e04f refactor(scripts): only install PyTorch when the targeted setup is the TCP server 2024-06-23 22:39:39 +08:00
louistiti 6045eb5105 feat(server): speed up translation LLM duty 2024-06-23 22:27:12 +08:00
louistiti 72954a3557 feat(server): speed up summarization LLM duty 2024-06-23 22:13:36 +08:00
louistiti cd65b84b7c feat(server): init LLM duty on inference requested by skills 2024-06-23 18:47:58 +08:00
louistiti c0e9fdcd8b feat(server): speed up paraphrase LLM duty 2024-06-23 18:46:32 +08:00
louistiti 780fd97006 feat(server): speed up NER custom LLM duty 2024-06-23 18:31:18 +08:00
louistiti e985d65165 feat(server): improve action recognition LLM duty to not hallucinate 2024-06-23 17:45:29 +08:00
louistiti 223cfdc0c9 fix(server): force lowercase on intent name for the action recognition LLM duty 2024-06-23 17:43:29 +08:00
louistiti 38a700feb0 fix: whitelist config.json for the TTS model 2024-06-23 17:24:38 +08:00
louistiti faee688924 feat: keep .gitkeep on audio models 2024-06-23 17:07:45 +08:00
louistiti a9e5a2978f feat(server): prepare LLM duties speed improvement on context/session creation 2024-06-22 23:23:39 +08:00
louistiti ab0e1493a0 feat(server): speed up LLM action recognition 2024-06-22 23:19:21 +08:00
louistiti 76b197baeb refactor(python tcp server): ASR CUDA log notification on bootup 2024-06-22 21:03:27 +08:00
louistiti aa63a6dabc chore(server): upgrade nodemon to 3.1.4 2024-06-22 12:44:23 +08:00
louistiti 1b4a1f15a9 feat(server): upgrade node-llama-cpp to 3.0.0-beta.32 2024-06-21 23:34:54 +08:00
louistiti 3734a16b6a fix(python tcp server): TTS inference correct params mapping 2024-06-21 09:51:52 +08:00
louistiti 8e28466cae feat(python tcp server): improve TTS performance by using generator for inference 2024-06-21 09:38:53 +08:00
louistiti a8ece30ced fix(python tcp server): use CPU on macOS as a tmp fix against the memory leak for TTS 2024-06-20 22:24:25 +08:00
louistiti a8b3f7c9bd feat(python tcp server): upgrade PyTorch to 2.3.1 (better MPS compatibility) 2024-06-20 09:54:16 +08:00
louistiti 963660db19 feat(python tcp server): empty cache on MPS PyTorch backend for TTS 2024-06-19 08:59:20 +08:00
louistiti 5b176c90f5 fix(server): use correct LLM action recognition judgement 2024-06-19 08:57:39 +08:00
Louis Grenard 200b425e0b docs: important notice on README 2024-06-18 09:11:56 +08:00
louistiti 8d3bffb64d fix: skill settings 2024-06-18 09:07:17 +08:00
louistiti 32dc3ced0b feat: Apple Silicon support for voice models 2024-06-18 09:02:09 +08:00
louistiti c9afb25ed3 feat(python tcp server): allow ASR device setting 2024-06-17 09:33:48 +08:00
louistiti de3ceb76be feat(python tcp server): unify settings to a single place, model settings included 2024-06-17 09:17:00 +08:00
louistiti 72d55870fe fix(web app): dynamic init states 2024-06-17 08:33:36 +08:00
louistiti 858b5090b4 feat(python tcp server): settings.json to store settings that can be edited on the fly 2024-06-16 23:33:47 +08:00
louistiti ebe156e2d8 feat: new universal ASR logic for OS cross compatibility 2024-06-16 21:26:23 +08:00
louistiti 3fc01d1906 feat: ignore Pipfile.lock files 2024-06-16 16:02:42 +08:00
louistiti 582b2aa572 feat: kick off ASR engine support for Apple Silicon 2024-06-12 00:31:47 +08:00
louistiti 04c3e88224 chore(server): upgrade llama.cpp to newer version 2024-06-11 00:40:49 +08:00
louistiti 52128039cb feat: synchronize major states between core and client 2024-06-11 00:11:03 +08:00
louistiti 1b66cc6218 feat(web app): better tips and prepare React components injection to init 2024-06-10 11:31:32 +08:00
louistiti 60e8b532de feat(web app): load fonts from local instead of Google Fonts APIs 2024-06-10 09:49:37 +08:00
louistiti d0b5b63840 feat(web app): allow multilines 2024-06-09 14:45:00 +08:00
louistiti c2849758e1 fix(server): avoid conversation loop 2024-06-08 11:27:47 +08:00
louistiti 7699739712 feat: add better quality TTS checkpoint 2024-06-08 02:17:26 +08:00
louistiti 7b8b702f92 fix(python tcp server): switch cx_Freeze to 7.1.0.post0 as 7.1.0 has been removed from the pip registry 2024-06-07 23:27:50 +08:00
louistiti 53e8cd2182 fix(bridge/python): switch cx_Freeze to 7.1.0.post0 as 7.1.0 has been removed from the pip registry 2024-06-07 23:27:09 +08:00
louistiti e0c10d4769 feat(bridge/python): upgrade cx_Freeze to 7.1.0 2024-06-07 09:55:41 +08:00
louistiti 65681521ab docs: add note on Git GUI clients 2024-06-07 09:49:08 +08:00
louistiti 22f892760f feat(server): boost free RAM for LLM usage 2024-06-07 09:46:24 +08:00
louistiti 4ce470d3e7 feat(python tcp server): force TTS BERT local files 2024-06-06 08:43:06 +08:00
louistiti 7c9487b62b feat(scripts): download and install TTS BERT model files for the Python TCP server env setup 2024-06-06 07:56:03 +08:00
louistiti 6df34d48ee feat(scripts): test comment 2024-06-05 23:58:01 +08:00
Louis Grenard d3f3c4e3a7 feat(scripts): setup Python TCP server TTS language model files kick off 2024-06-05 23:39:59 +08:00
louistiti 11e8ad43ae feat(python tcp server): use new V1_1 voice for TTS 2024-06-04 00:18:33 +08:00
louistiti 88457e4b7a feat(server): new V1_1 voice naming 2024-06-03 23:58:01 +08:00
louistiti 181f68109e chore: upgrade @ffprobe-installer/ffprobe to 2.1.2 2024-06-02 01:01:27 +08:00
louistiti 00f1dcef38 feat(server): catch exception on STT and TTS init 2024-06-02 00:29:43 +08:00
louistiti 4cb374e64d fix(python tcp server): exclude NVIDIA libs injection for non-macOS platforms on build 2024-06-02 00:10:25 +08:00
louistiti fd76681f81 chore(python tcp server): upgrade cx_Freeze to 7.1.0 (may fix macOS wheel build) 2024-06-01 23:33:02 +08:00
louistiti eb72065e5e feat(scripts): auto delete .venv when Python packages failed to be installed on setup 2024-06-01 23:23:00 +08:00
louistiti e9f1aadc67 feat(scripts): add Apple Metal developer tools error hint on Apple Silicon for llama.cpp setup 2024-06-01 21:57:20 +08:00
louistiti f1c6db6053 fix(scripts): llama.cpp release tag not failing when null 2024-06-01 21:33:07 +08:00
louistiti 40fd5fac88 fix(scripts): llama.cpp default tag number on LLM setup 2024-06-01 21:04:46 +08:00
Louis Grenard 97e0e04987 docs: update README (#527) 2024-05-30 23:17:49 +08:00
louistiti e250519c1b feat: voice engine status sync with UI status 2024-05-29 23:39:44 +08:00
louistiti 2e866cd2d7 feat(web app): add Leon voice animation states 2024-05-29 15:07:14 +08:00
louistiti 3bfd8ad893 perf(web app): slightly improve rendered text perf 2024-05-28 20:48:33 +08:00
louistiti a3f6cb1aba feat(web app): core animation on voice 2024-05-28 17:40:56 +08:00
louistiti 0804dc7be6 fix(server): confusion between owner and creator in Leon's persona 2024-05-28 11:05:18 +08:00
louistiti e328bd7f4c feat(server): add time zone to context persona 2024-05-28 11:01:58 +08:00
louistiti e435e526b4 feat(web app): improve text streaming animation 2024-05-28 10:58:22 +08:00
louistiti 747ec51143 feat(web app): text animation on stream 2024-05-28 10:43:24 +08:00
louistiti 96dabd9e6e feat(server): when utterance is sent, interrupt Leon's voice 2024-05-27 22:36:57 +08:00
louistiti 8eefe53473 feat(web app): prevent from sending utterance while Leon is generating answer 2024-05-27 19:25:57 +08:00
louistiti 0ce1f62c0d feat: support default conversations powered by LLM with action-first in mind 2024-05-27 18:54:54 +08:00
louistiti 4bad34278b feat(server): LLM action recognition management 2024-05-26 10:09:03 +08:00
louistiti 1516b18a11 refactor: unify Python TCP server warnings to ignore 2024-05-26 09:55:48 +08:00
louistiti fb5c258cf6 refactor: unify NLP models paths 2024-05-26 09:47:13 +08:00
louistiti 34170a38ac fix(server): provide correct duty type from LLM provider completion 2024-05-25 23:31:44 +08:00
louistiti a846eb9131 refactor(server): replace LLM providers options with params 2024-05-25 23:20:51 +08:00
louistiti 32db721f0e feat(server): create action recognition LLM duty 2024-05-25 23:05:19 +08:00
louistiti c18458ac41 feat(python tcp server): decrease throttling to 0.3s on ASR speech 2024-05-24 18:40:44 +08:00
louistiti 2fac3881eb feat: support (very) long speech on ASR 2024-05-24 18:38:00 +08:00
louistiti 37fe307035 feat: support speech interruption 2024-05-24 10:25:05 +08:00
louistiti c1349c930a feat: support incremental active listening for ASR 2024-05-24 01:26:28 +08:00
louistiti c54ad18cc3 feat(server): do not mention about context switch 2024-05-23 20:47:41 +08:00
louistiti be1a4d2ad3 feat(server): persona rules improvement 2024-05-23 19:46:22 +08:00
louistiti 9e43a78ecb feat(server): provide time of the day to persona 2024-05-23 19:45:11 +08:00
louistiti 22529a4348 feat(server): include more dynamic info in persona 2024-05-23 19:25:59 +08:00
louistiti 91fb441dcd feat: auto update mood on client; fix NER on slot filling 2024-05-23 17:49:55 +08:00
louistiti 9c8db3364b chore: upgrade nodemon to 3.1.0 and minor improvements 2024-05-23 13:46:21 +08:00
louistiti 8a80f4fce6 feat(server): allow emojis with streamed answer and unify token clean up 2024-05-23 12:32:15 +08:00
louistiti a762b3cbfe chore: upgrade tsx to 4.10.5 2024-05-23 10:47:38 +08:00
louistiti b238170e7c fix: memory leak in dev mode 2024-05-23 10:38:41 +08:00
louistiti 6b24e4280c fix(python tcp server): correctly load .env variables 2024-05-23 09:43:03 +08:00
louistiti beb208973f feat(python tcp server): warm up TTS model on start 2024-05-23 09:24:46 +08:00
louistiti 528257664a feat(server): clean up existing Python TCP server processes on start 2024-05-23 09:12:17 +08:00
louistiti f278ca16b8 feat: support stream for LLM outputs 2024-05-23 01:05:21 +08:00
louistiti 0d370a8c7e feat(python tcp server): fine tune parameters for the ASR 2024-05-22 20:52:01 +08:00
louistiti 7299f5fc4d feat(python tcp server): add new wake word alternative 2024-05-22 18:58:22 +08:00
louistiti 8acdbec6e3 feat(python tcp server): increase voice speed on TTS engine 2024-05-22 18:56:11 +08:00
louistiti e2bdbe9e8a feat(python tcp server): add new wake word alternative 2024-05-22 17:57:07 +08:00
louistiti c440a51f51 fix(server): if no action match the utterance, then immediately return the NLU result object as it is 2024-05-22 17:52:15 +08:00
louistiti c172f2d6af fix(server): handle exception on NLU 2024-05-22 17:32:13 +08:00
louistiti 0ff26739e7 feat(web app): display Leon's mood when LLM is enabled 2024-05-22 16:17:02 +08:00
louistiti 9b13520b9e feat(web app): increase font size on chat bubbles 2024-05-22 15:30:57 +08:00
louistiti b02d510490 feat(server): always load ASR model from local 2024-05-22 15:22:30 +08:00
louistiti 5855990bd5 feat(server): add TCP message listener for ASR 2024-05-22 01:23:41 +08:00
louistiti 596b7552dd feat: new ASR engine ready 2024-05-21 23:57:36 +08:00
louistiti f051c1d2cd feat(python tcp server): ASR engine communication with core 2024-05-21 19:20:09 +08:00
louistiti ef368f89fb feat(python tcp server): multi threading and new ASR engine 2024-05-21 16:38:45 +08:00
louistiti 72390e2fe6 feat(python tcp server): update to latest TTS checkpoint 2024-05-20 09:06:03 +08:00
louistiti 580289e05d feat(scripts): add error hint about PortAudio for audio stream 2024-05-20 00:39:33 +08:00
louistiti 39dae08b6b feat(python tcp server): ASR PoC kick off 2024-05-20 00:29:39 +08:00
louistiti e16782e2db feat(server): add Leon mood info on init 2024-05-19 11:58:01 +08:00
louistiti ed42f9b752 feat(python tcp server): prepare ASR entry point 2024-05-19 11:46:47 +08:00
louistiti b25b82b9dc feat(python tcp server): add break after : character on new TTS engine 2024-05-18 22:42:48 +08:00
louistiti fd36eafb91 feat(python tcp server): increase socket content length 2024-05-18 22:42:12 +08:00
louistiti a2e6466bb9 feat(python tcp server): clean up emojis on TTS engine 2024-05-18 22:20:15 +08:00
louistiti f776913ca3 feat(server): complete first version of the new TTS engine 2024-05-18 21:45:46 +08:00
louistiti 9a065175ca feat: prepare TCP server and core to communicate for the new TTS engine 2024-05-18 10:42:31 +08:00
louistiti a7cab344f8 feat(python tcp server): embed new text-to-speech engine in TCP server binary 2024-05-18 01:11:12 +08:00
louistiti e455a9d96b feat(scripts): TCP server setup add PyTorch nightly install 2024-05-17 12:20:34 +08:00
louistiti 85af31b614 feat(python tcp server): TTS tmp inference 2024-05-16 12:03:24 +08:00
louistiti 0e959412d5 feat(python tcp server): TTS cleaned up, ready to be implemented and embedded 2024-05-16 01:03:15 +08:00
Louis Grenard 6f24f50497 feat(python tcp server): kick off new TTS implementation 2024-05-15 00:09:49 +08:00
louistiti 74e2ffeb96 Merge branch 'mistral-llm-ner' into develop 2024-05-09 01:06:17 +08:00
louistiti bbb533f49f ci(bridge/python): tmp disable Windows build 2024-05-09 00:14:03 +08:00
louistiti bdff917e04 feat(scripts): avoid LLM setup when running in CI 2024-05-08 19:21:52 +08:00
louistiti 52cf4afbec ci(python tcp server): set macOS runner to macos-12 2024-05-08 19:13:35 +08:00
louistiti 0ef1ec9247 ci(bridge/python): set macOS runner to macos-12 2024-05-08 19:12:51 +08:00
louistiti aa862a6b5f fix(scripts): TCP_SERVER_SRC_PATH to PYTHON_TCP_SERVER_SRC_PATH to set up Python env 2024-05-08 19:04:15 +08:00
louistiti f10f310aef build(bridge/nodejs): bump to 1.2.0 2024-05-08 18:53:30 +08:00
louistiti 18b47f168a build(bridge/python): bump to 1.3.0 2024-05-08 18:53:01 +08:00
louistiti f7ad05275b chore(server): clean up comments 2024-05-08 01:39:01 +08:00
louistiti d62e0cd56c feat(server): Groq support as LLM provider; agnostic LLM provider 2024-05-08 01:37:47 +08:00
louistiti 1caa73a13a fix(server): init LLM provider; completed 2024-05-07 18:53:37 +08:00
louistiti 844af50008 feat(server): LLM provider architecture (WIP) 2024-05-07 02:51:18 +08:00
louistiti b26d924890 feat(server): improve Paraphrase duty accuracy 2024-05-06 11:22:28 +08:00
louistiti da1b469f8d chore(server): remove unused countTokens method 2024-05-06 01:00:31 +08:00
louistiti 0189c74a0e feat(server): finalize Leon's personality and optimize LLM duties 2024-05-06 00:57:20 +08:00
louistiti a0a4f9d7b0 feat(server): emit socket message when a new update is available 2024-05-05 11:08:16 +08:00
louistiti eeb1b07898 feat(server): chit-chat duty and skill and more 2024-05-05 00:20:59 +08:00
louistiti 78e5f79bce feat(server): make Leon's personality more lively 2024-05-04 00:24:21 +08:00
louistiti 764e815204 feat(server): switch LLM duties back to using ChatWrapper instead of completions 2024-05-03 23:23:46 +08:00
louistiti 03d25f2a48 feat(server): Leon's personality done 2024-05-03 23:00:32 +08:00
louistiti aa36c8cb2a feat(server): personality support kick off; answer queue 2024-05-02 16:40:05 +08:00
louistiti 2146637022 feat(server): add paraphrase LLM duty to kick off personality attribution 2024-05-01 01:04:29 +08:00
louistiti 0a4e0ed90d feat(server): add confidence value in logs 2024-04-30 11:48:09 +08:00
louistiti ceeb5bd70e feat(server): use Lexi-Llama-3-8B-Uncensored-Q5_K_S as default LLM 2024-04-30 09:58:30 +08:00
louistiti f77343918d chore: upgrade socket.io-client to latest 2024-04-29 21:28:20 +08:00
louistiti e2984ea87e chore: upgrade socket.io to latest 2024-04-29 21:27:16 +08:00
louistiti 694d398d7b chore: upgrade fastify to latest 2024-04-29 21:25:25 +08:00
louistiti a2bca433f6 chore: upgrade dotenv to latest 2024-04-29 21:24:21 +08:00
louistiti 60698704ca chore: uninstall async npm package 2024-04-29 21:15:47 +08:00
louistiti 7d4e64d575 chore: upgrade tsc-watch to latest 2024-04-29 21:08:49 +08:00
louistiti 2c03efc167 refactor(server): warning message on explicit deactivation of LLM 2024-04-29 19:33:30 +08:00
louistiti 539281110a feat(server): allow explicit deactivation of LLM 2024-04-29 19:31:57 +08:00
louistiti 3ed88544f8 fix(server): specify correct minimem total/free RAM for LLM 2024-04-29 19:18:27 +08:00
louistiti 9ee73ea5f9 feat(server): map resolved slots to dialog answers 2024-04-29 18:37:20 +08:00
louistiti c934d0e30d feat(server): support dialog type action after slots filled 2024-04-29 18:02:41 +08:00
louistiti d9f0144e62 feat(server): action loop support 2024-04-29 11:25:18 +08:00
louistiti 57923f83ee fix(scripts): always update manifest on LLM setup 2024-04-28 10:10:57 +08:00
louistiti a38eee71f0 feat(server): set Phi-3 as default LLM 2024-04-26 15:14:28 +08:00
louistiti c8e03ec401 fix(server): utterance as expected_item loop indefinitely 2024-04-22 23:29:30 +08:00
louistiti 4ca32b5070 feat(scripts): fallback to mirror in case of error to download LLM 2024-04-22 00:00:46 +08:00
louistiti d8660251af feat: use mistral-7b-instruct-v0.2.Q4_K_S as final choice 2024-04-20 14:33:37 +08:00
louistiti 26af271edc feat(server): add utterance as expected_item type 2024-04-20 14:29:00 +08:00
louistiti da49a4dd82 feat(scripts): llama.cpp compatible build 2024-04-18 20:38:02 +08:00
louistiti cf02f0f91d feat(server): final LLM setup 2024-04-17 00:10:41 +08:00
louistiti 2de95c4ef9 feat(server): Gemma support (prototype) 2024-04-16 20:18:18 +08:00
louistiti 2434d36564 feat(skill/translator-poc): short translate action kick off 2024-04-15 20:42:11 +08:00
louistiti 73d919868b feat(server): LLM entities support 2024-04-14 17:41:39 +08:00
Théo LUDWIG 39cbd7114e fix(bridge/python): usage of dict instead of empty TypedDict to avoid types errors
We might in the future generate automatically from the TypeScript types, generate the Python types.
For the moment, it is not typed, so we use a dict.
2024-02-25 20:57:02 +01:00
louistiti a16ab4abfa feat(server): add response data to LLM inference endpoint 2024-02-19 18:31:56 +08:00
louistiti 6e12d2638d feat(server): verify whether LLM is enabled on inference 2024-02-19 18:30:10 +08:00
louistiti 0d3c42beb2 feat(server): implement PoC skill to validate LLM execution 2024-02-19 18:01:41 +08:00
louistiti 66f65b25b4 feat(server): expose LLM duties behind HTTP endpoint 2024-02-19 17:00:14 +08:00
louistiti 3f85a93a03 feat(server): LLM duties architecture + custom NER, summarization and translation duties 2024-02-18 23:43:25 +08:00
Louis Grenard e9e9155366 feat(server): bootstrap LLM TCP server <> LLM duties <> TCP client (tmp) 2024-02-15 00:13:16 +08:00
Louis Grenard 30c9d3bce5 feat(server): create updater 2024-02-14 17:37:21 +08:00
Louis Grenard dba5d90b94 chore: add LLM TCP server to Git commit message 2024-02-14 09:45:26 +08:00
Louis Grenard 0705659f3e fix(scripts): Python TCP server setup 2024-02-14 09:44:50 +08:00
louistiti 4815651a1d feat(server): bootstrap LLM TCP server (tmp) 2024-02-14 09:27:22 +08:00
louistiti 71ebdc5d80 refactor(server): prepare LLM TCP client connection 2024-02-13 18:51:45 +08:00
louistiti 49ac3218f7 feat(server): preparing LLM TCP server loading 2024-02-13 16:22:32 +08:00
louistiti 1c0c8080bf feat(scripts): download and compile llama.cpp 2024-02-08 21:05:06 +08:00
louistiti 46f6c09739 scripts(setup-llm): tell when LLM is up-to-date 2024-01-30 00:09:20 +08:00
louistiti 93eb2d22b3 scripts(setup-llm): set up LLM 2024-01-29 23:57:23 +08:00
louistiti c4d2524922 scripts(setup-llm): kick off LLM setup and unify common functions 2024-01-29 00:20:38 +08:00
louistiti 7482edde0a Merge branch 'widget-backbone' into mistral-llm-ner 2024-01-28 22:22:48 +08:00
louistiti 09b9a02284 feat(skill/random_number): widget rendering 2024-01-28 17:16:10 +08:00
Théo LUDWIG f895963ab9 feat(bridge/python): add Text component 2023-12-12 19:48:03 +01:00
Théo LUDWIG 637ab43626 feat(bridge/python): add TextInput component 2023-12-12 19:47:44 +01:00
Théo LUDWIG 811bb7ab44 feat(bridge/python): add Tab component 2023-12-12 19:47:22 +01:00
Théo LUDWIG a65d330256 feat(bridge/python): add TabList component 2023-12-12 19:47:02 +01:00
Théo LUDWIG 04cff622b2 feat(bridge/python): add TabGroup component 2023-12-12 19:46:04 +01:00
Théo LUDWIG b2f260d04d feat(bridge/python): add TabContent component 2023-12-12 19:45:29 +01:00
Théo LUDWIG adda7bc6ac feat(bridge/python): add Switch component 2023-12-12 19:45:03 +01:00
Théo LUDWIG fb936c45b6 feat(bridge/python): add Slider component 2023-12-12 19:44:33 +01:00
Théo LUDWIG d0ade211ad feat(bridge/python): add Select component 2023-12-12 19:44:11 +01:00
Théo LUDWIG 90397f8029 feat(bridge/python): add SelectOption component 2023-12-12 19:43:48 +01:00
Théo LUDWIG 7ee89d9e58 feat(bridge/python): add ScrollContainer component 2023-12-12 19:43:14 +01:00
Théo LUDWIG 050fcec835 feat(bridge/python): add Radio component 2023-12-12 19:42:32 +01:00
Théo LUDWIG f714a99108 feat(bridge/python): add RadioGroup component 2023-12-12 19:42:10 +01:00
Théo LUDWIG 5623c33d04 feat(bridge/python): add Progress component 2023-12-12 19:41:42 +01:00
Théo LUDWIG 8d937b1e5f feat(bridge/python): add Loader component 2023-12-12 19:41:16 +01:00
Théo LUDWIG 1613b12765 feat(bridge/python): add List component 2023-12-12 19:40:54 +01:00
Théo LUDWIG db13c1b51e feat(bridge/python): add ListItem component 2023-12-12 19:40:33 +01:00
Théo LUDWIG 78e577114e feat(bridge/python): add ListHeader component 2023-12-12 19:40:02 +01:00
Théo LUDWIG f5999bef99 feat(bridge/python): add Link component 2023-12-12 19:39:30 +01:00
Théo LUDWIG 1284a8f6aa feat(bridge/python): add Image component 2023-12-12 19:38:57 +01:00
Théo LUDWIG 1a89eec108 feat(bridge/python): add Icon component 2023-12-12 19:38:33 +01:00
Théo LUDWIG dfb9b81c8b feat(bridge/python): add IconButton component 2023-12-12 19:37:24 +01:00
Théo LUDWIG 68ca55c82e feat(bridge/python): add Flexbox component 2023-12-12 19:36:55 +01:00
Théo LUDWIG f68a338dec feat(bridge/python): add CircularProgress component 2023-12-12 19:36:11 +01:00
Théo LUDWIG 435170659e feat(bridge/python): add Checkbox component 2023-12-12 19:34:12 +01:00
Théo LUDWIG 1a0e29e6dd feat(bridge/python): add Card component 2023-12-12 19:33:25 +01:00
Théo LUDWIG c9cf7d3ae8 style: format python with autopep8 2023-12-12 19:30:54 +01:00
Théo LUDWIG 0d4104b629 feat(bridge/python): render widgets 2023-12-10 18:33:07 +01:00
Théo LUDWIG c361682cab fix(bridge/nodejs): types improvements 2023-12-10 16:44:25 +01:00
louistiti 55b81a3c8f feat(bridge/python): widget renderer kick off 2023-12-10 23:06:44 +08:00
louistiti c5e1ed8e86 chore: remove html-react-parser 2023-12-10 10:47:23 +08:00
louistiti 4baf8f0d55 feat: add widgets folder to skills 2023-12-10 10:02:34 +08:00
louistiti 5a55397ef8 refactor(bridge/nodejs): render return type 2023-12-10 08:59:19 +08:00
louistiti 05200a195b feat: final widget new structure 2023-12-10 08:48:13 +08:00
louistiti 7167849d6a feat: widget new structure 2023-12-09 23:07:34 +08:00
louistiti edfa90d820 refactor(skill/widget-playground): flexbox component usage 2023-12-03 22:48:24 +08:00
louistiti 3511452d93 chore: upgrade Aurora to 1.0.0-beta.9 2023-12-03 22:31:31 +08:00
louistiti 1408f358c7 feat(bridge/nodejs): add Text component 2023-12-03 22:07:12 +08:00
louistiti 0297e1d22b feat(bridge/nodejs): add TabList component 2023-12-03 22:06:30 +08:00
louistiti 10a17b0146 feat(bridge/nodejs): add TabGroup component 2023-12-03 22:05:52 +08:00
louistiti bba60ea45d feat(bridge/nodejs): add TabContent component 2023-12-03 22:05:02 +08:00
louistiti 64e1e7da3e feat(bridge/nodejs): add Tab component 2023-12-03 22:04:27 +08:00
louistiti 14e5bee7d6 feat(bridge/nodejs): add Switch component 2023-12-03 22:03:53 +08:00
louistiti 2576987c44 feat(bridge/nodejs): add Status component 2023-12-03 22:03:15 +08:00
louistiti ea7d6c48db feat(bridge/nodejs): add Slider component 2023-12-03 22:02:37 +08:00
louistiti 52f37fc3ed feat(bridge/nodejs): add SelectOption component 2023-12-03 22:02:04 +08:00
louistiti 8cb0816f24 feat(bridge/nodejs): add Select component 2023-12-03 22:01:28 +08:00
louistiti ca59f02f4f feat(bridge/nodejs): add ScrollContainer component 2023-12-03 22:00:50 +08:00
louistiti 7af761c5f6 feat(bridge/nodejs): add RadioGroup component 2023-12-03 22:00:11 +08:00
louistiti f2b75fc184 feat(bridge/nodejs): add Radio component 2023-12-03 21:59:41 +08:00
louistiti 3bf457caef feat(bridge/nodejs): add Progress component 2023-12-03 21:58:55 +08:00
louistiti 90b5596b76 feat(bridge/nodejs): add Loader component 2023-12-03 21:58:01 +08:00
louistiti 93c0d6c83b chore: upgrade Aurora to1.0.0-beta.8 2023-12-03 21:57:10 +08:00
louistiti 9b5aa5f901 feat(bridge/nodejs): add ListItem component 2023-12-03 21:49:00 +08:00
louistiti bad92d3569 feat(bridge/nodejs): add ListHeader component 2023-12-03 21:48:23 +08:00
louistiti 1f0e4d2260 feat(bridge/nodejs): add List component 2023-12-03 21:47:41 +08:00
louistiti d460dc59f5 feat(bridge/nodejs): add Link component 2023-12-03 21:46:47 +08:00
louistiti eb64695697 feat(bridge/nodejs): add TextInput component 2023-12-03 21:44:00 +08:00
louistiti 308bebeb48 feat(bridge/nodejs): add Image component 2023-12-03 21:41:08 +08:00
louistiti bd71866342 feat(bridge/nodejs): add IconButton component 2023-12-03 21:40:21 +08:00
louistiti b55d58ec30 feat(bridge/nodejs): add Icon component 2023-12-03 21:39:34 +08:00
louistiti 2cf4ef54c3 feat(bridge/nodejs): add Flexbox component 2023-12-03 21:38:36 +08:00
louistiti 34a5e8bac1 feat(bridge/nodejs): add CircularProgress component 2023-12-03 21:37:10 +08:00
louistiti 502ec8ba2e feat(bridge/nodejs): add Checkbox component 2023-12-03 21:35:56 +08:00
louistiti ac25897e9e feat(bridge/nodejs): add Card component 2023-12-03 21:32:38 +08:00
louistiti c44824c89d feat: Aurora components auto mapping of types 2023-11-27 23:17:51 +08:00
louistiti 673de84936 chore: upgrade @leon-ai/aurora to latest (component types exports) 2023-11-27 22:41:19 +08:00
louistiti 78aa9c6ddc feat: new rendering engine prototype on widgets 2023-11-26 23:22:32 +08:00
Théo LUDWIG ea2e5b9747 chore: bump @leon-ai/aurora to v1.0.0-beta.5 2023-11-22 18:10:45 +01:00
louistiti ea1a33b51f refactor(web app): fonts change 2023-11-22 21:39:38 +08:00
louistiti 7a441d50b9 chore: remove ark-ui from core 2023-11-22 20:51:26 +08:00
Théo LUDWIG 364b74568e chore: usage of @leon-ai/aurora 2023-11-21 23:09:12 +01:00
Théo LUDWIG 5f5cb1f55b chore: remove aurora 2023-11-21 23:04:17 +01:00
louistiti 4479fcd655 feat: tmp understanding of the JSX parser 2023-11-21 22:19:06 +08:00
louistiti ad56672819 feat: add checkbox component to skill renderer test 2023-11-21 20:48:05 +08:00
louistiti b4bcabbb9e feat: use TSX template for widget format 2023-11-20 21:52:44 +08:00
louistiti aaf1a1989b feat(web app): cast props value as boolean when necessary 2023-11-18 17:05:29 +08:00
louistiti 2025e366d2 feat(web app): widget components tmp case parsing 2023-11-17 00:33:27 +08:00
louistiti 201d1ce4e4 feat(web app): widget PoC 2023-11-17 00:06:30 +08:00
louistiti 2d1e27926d chore: add classnames npm dep 2023-11-16 23:20:01 +08:00
louistiti e449011f90 chore: merge branch 'develop' into widget-backbone 2023-11-16 23:18:25 +08:00
louistiti 84b84453a7 Merge branch 'chore/clean-up' into develop 2023-11-15 22:38:53 +08:00
louistiti 00cee2d2fd ci: remove macOS ARM64 test runner 2023-11-15 21:56:59 +08:00
Louis Grenard 78cce1970e ci: macOS ARM64 runner test 2023-11-15 21:46:51 +08:00
louistiti 977a822cc8 fix(scripts): clone intent-object JSON files for bridges on check command 2023-11-15 21:15:29 +08:00
Théo LUDWIG 1558970619 chore: forgotten tsconfig.json to update 2023-11-15 02:00:57 +01:00
Théo LUDWIG 86de211fa8 BREAKING: drop support for Docker 2023-11-15 01:23:05 +01:00
Théo LUDWIG 2923c6504b chore: update dependencies + usage of tsx instead of ts-node 2023-11-15 01:10:06 +01:00
Théo LUDWIG 3e23950519 chore: remove dependabot 2023-11-14 23:59:10 +01:00
louistiti 92eb32b4da build(tcp server): 1.1.0 2023-11-14 23:31:10 +08:00
louistiti e175ea5c12 feat(web app): widget PoC beginning 2023-11-12 23:19:51 +08:00
louistiti 167c97120d Merge branch 'develop' into widget-backbone 2023-11-12 22:23:18 +08:00
louistiti e73bd632f5 feat: progress on widget brainstorming 2023-11-12 18:34:09 +08:00
louistiti f61e4f08a4 feat(web app): import Aurora 2023-06-29 22:18:46 +08:00
louistiti 0ad8b9e8e5 chore: merge 2023-06-29 00:28:39 +08:00
louistiti dbed559799 feat(skill/widget-playground): forecast backbone draft 2023-06-04 17:47:29 +08:00
louistiti 305d9cd581 feat(skill/widget-playground): first widget components draft structure 2023-06-04 17:06:24 +08:00
louistiti 212df695f2 feat(skill/widget-playground): tmp skill to develop widgets 2023-06-04 15:51:35 +08:00
353 changed files with 146713 additions and 2127 deletions
-9
View File
@@ -1,9 +0,0 @@
__pycache__/
**/dist/*
**/build/
**/node_modules/
**/tmp/*
**/src/.venv/*
logs/*
*.pyc
.DS_Store
+12 -1
View File
@@ -8,6 +8,17 @@ LEON_LANG=en-US
LEON_HOST=http://localhost
LEON_PORT=1337
# Enable/disable LLM
LEON_LLM=false
# LLM provider
LEON_LLM_PROVIDER=local
# LLM provider API key (if not local)
LEON_LLM_PROVIDER_API_KEY=
# Enable/disable LLM natural language generation
LEON_LLM_NLG=false
# Enable/disable LLM Action Recognition
LEON_LLM_ACTION_RECOGNITION=false
# Time zone (current one by default)
LEON_TIME_ZONE=
@@ -39,7 +50,7 @@ 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
PIPENV_PIPFILE=tcp_server/src/Pipfile
# Path to the virtual env in .venv/
PIPENV_VENV_IN_PROJECT=true
+2 -5
View File
@@ -18,10 +18,7 @@
"browser": true
},
"parserOptions": {
"ecmaVersion": 2021
},
"globals": {
"io": true
"ecmaVersion": "latest"
},
"plugins": ["@typescript-eslint", "unicorn", "import"],
"ignorePatterns": "*.spec.js",
@@ -29,7 +26,7 @@
"@typescript-eslint/no-non-null-assertion": ["off"],
"no-async-promise-executor": ["off"],
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
"prefer-destructuring": ["error"],
"prefer-destructuring": ["off"],
"comma-dangle": ["error", "never"],
"semi": ["error", "never"],
"object-curly-spacing": ["error", "always"],
+8 -20
View File
@@ -77,22 +77,6 @@ npm run dev:server
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
```
## Versioning
- We use [Semantic Versioning](https://semver.org) for releases.
@@ -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,11 +178,11 @@ Once Pyenv installed, run:
```bash
# Install Python
pyenv install 3.9.10 --force
pyenv global 3.9.10
pyenv install 3.11.9 --force
pyenv global 3.11.9
# Install Pipenv
pip install pipenv==2022.7.24
pip install pipenv==2024.0.1
```
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:
-1
View File
@@ -16,7 +16,6 @@ If the bug is related to the setup, please submit the issue at: https://github.c
- OS (or browser) version:
- Node.js version:
- Complete "leon check" (or "npm run check") output:
- (if using Docker) Complete "npm run docker:check" output:
- (optional) Leon skill version:
### Expected Behavior
-13
View File
@@ -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'
@@ -13,7 +13,11 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
# @see https://github.com/actions/runner-images/tree/main/images/macos
# Use macos-12 instead of macos-latest because the latter use ARM64 (M1) architecture
os: [ubuntu-20.04, macos-12]
# Temporarily disable Windows release
# os: [ubuntu-20.04, macos-12, windows-latest]
runs-on: ${{ matrix.os }}
@@ -24,10 +28,10 @@ jobs:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: 3.9.10
python-version: 3.11.9
- name: Install Pipenv
run: pip install --upgrade pip && pip install pipenv==2022.7.24
run: pip install --upgrade pip && pip install pipenv==2024.0.1
- name: Install Node.js
uses: actions/setup-node@v3
@@ -69,7 +73,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: 3.9.10
python-version: 3.11.9
- name: Set Python bridge version
working-directory: bridges/python/src
+6 -4
View File
@@ -13,7 +13,9 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
# @see https://github.com/actions/runner-images/tree/main/images/macos
# Use macos-12 instead of macos-latest because the latter use ARM64 (M1) architecture
os: [ubuntu-20.04, macos-12, windows-latest]
runs-on: ${{ matrix.os }}
@@ -24,10 +26,10 @@ jobs:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: 3.9.10
python-version: 3.11.9
- name: Install Pipenv
run: pip install --upgrade pip && pip install pipenv==2022.7.24
run: pip install --upgrade pip && pip install pipenv==2024.0.1
- name: Install Node.js
uses: actions/setup-node@v3
@@ -69,7 +71,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: 3.9.10
python-version: 3.11.9
- name: Set TCP server version
working-directory: tcp_server/src
+6
View File
@@ -31,6 +31,12 @@ tcp_server/src/Pipfile.lock
skills/**/src/settings.json
skills/**/memory/*.json
core/data/models/*.nlp
core/data/models/*.json
core/data/models/llm/*
core/data/models/audio/tts/**/*.*
!core/data/models/audio/tts/config.json
core/data/models/audio/asr/**/*.*
!core/data/models/**/.gitkeep
package.json.backup
.python-version
schemas/**/*.json
+1 -1
View File
@@ -6,6 +6,6 @@ if ! [ -x "$(command -v npm)" ]; then
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
npx tsx scripts/commit-msg.js
fi
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
npm run pre-commit
-60
View File
@@ -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"]
+27 -37
View File
@@ -30,38 +30,41 @@ _<p align="center">Your open-source personal assistant.</p>_
---
## Current State
## Important Notice (as of 2024-06-18)
> [!IMPORTANT]
> Due to all the new major changes coming to Leon AI, the development branch might be unstable. It is recommended to use the older version under the master branch.
>
> Please note that older versions do not make use of any foundation model, which will be introduced in upcoming versions.
**Outdated Documentation**
Please note that the documentation and this README are not up to date. We've made significant changes to Leon over the past few months, including the introduction of new TTS and ASR engines, and a hybrid approach that balances LLM, simple classification, and multiple NLP techniques to achieve optimal speed, customization, and accuracy. We'll update the documentation for the official release.
**Project History and Future Plans**
Since its inception in 2017, Leon has undergone significant transformations. Although we've been inconsistent in shipping updates over the years, we're now focused on maturing the project. With the recent integration of transformers-based models, we're prepared to unlock Leon's full potential.
Our next step is to finalize the latest features for the official release.
Then we'll be establishing a group of active contributors to work together, develop new skills, and share them with the community. A skill registry platform will be built (see it as the npm or pip registry but for skills).
- Check out [the roadmap](http://roadmap.getleon.ai/) for more information on our upcoming plans.
- Watch a [preview of our last progress](https://www.youtube.com/watch?v=6CInSt6pTVA) to see what we've been working on.
**Get Involved**
[Join us on Discord](https://discord.gg/MNQqqKg) to ask questions, or express interest in becoming an active contributor.
---
### Why is there a small amount of contributors?
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!
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 during my spare time. Hence, I'm blocking any 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!
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
@@ -152,19 +155,6 @@ leon start
# Hooray! Leon is running
```
### Docker Installation
```sh
# Install Leon
leon create birth --docker
# Run
leon start
# Go to http://localhost:1337
# Hooray! Leon is running
```
## 📚 Documentation
For full documentation, visit [docs.getleon.ai](https://docs.getleon.ai).
+112 -32
View File
@@ -1,4 +1,11 @@
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800);
@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 'voice-energy/main.scss';
html,
body,
@@ -83,7 +90,6 @@ video {
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
@@ -129,48 +135,68 @@ table {
--light-black-color: #222426;
--white-color: #fff;
--grey-color: #323739;
--blue-color: #1c75db;
--pink-color: #ed297a;
.settingup {
--a-loader-size-md: 20px !important;
}
}
a {
color: inherit;
}
ul li {
#feed ul li:not(.aurora-list-item) {
margin-left: 20px;
}
body {
color: var(--white-color);
background-color: var(--black-color);
font-family: 'Open Sans', sans-serif;
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;
}
body > * {
transition: opacity 0.5s;
}
body.settingup > * {
body.settingup > *:not(#init) {
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;
#init .not-initialized {
visibility: hidden;
}
@keyframes scaleout {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
opacity: 0;
}
#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;
}
main {
@@ -187,24 +213,43 @@ footer {
text-align: center;
left: 50%;
bottom: 0;
line-height: 18px;
transform: translate(-50%, -50%);
}
input {
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: 400;
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;
}
small {
#tip {
display: inline-flex;
margin-top: 2px;
color: var(--white-color);
font-size: 0.7em;
line-height: 22px;
font-size: 0.9em;
gap: 20px;
li {
margin-left: 0;
}
}
.hide {
@@ -218,10 +263,32 @@ small {
height: 40px;
}
#top-container {
position: absolute;
top: 4%;
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: 10%;
top: 8%;
height: 50%;
overflow-y: auto;
border: 2px solid var(--grey-color);
@@ -278,6 +345,15 @@ small {
}
}
.llm-token {
opacity: 0;
transition: opacity 2.5s;
}
.llm-token.fade-in {
animation: fadeIn 2.5s forwards;
}
.bubble-container {
padding: 6px;
}
@@ -300,7 +376,7 @@ small {
}
.bubble {
padding: 6px 12px;
padding: 10px 16px;
border-radius: 16px;
display: inline-block;
max-width: 60%;
@@ -309,6 +385,8 @@ small {
opacity: 0;
animation: fadeIn 0.2s ease-in forwards;
overflow: hidden;
font-size: 1.8rem;
line-height: 2.4rem;
}
#feed .me .bubble {
background-color: #1c75db;
@@ -343,7 +421,9 @@ small {
padding: 2px 8px;
font-size: inherit;
cursor: pointer;
transition: background-color 0.2s, color 0.2s;
transition:
background-color 0.2s,
color 0.2s;
}
.suggestion:hover {
color: var(--black-color);
@@ -353,7 +433,7 @@ small {
#input-container {
position: absolute;
width: 100%;
bottom: 22%;
bottom: 18%;
}
#mic-container {
+162
View File
@@ -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);
}
+59
View File
@@ -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);
}
}
+59
View File
@@ -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);
}
}
+5
View File
@@ -0,0 +1,5 @@
@import 'base.scss';
@import 'listening.scss';
@import 'idle.scss';
@import 'processing.scss';
@import 'talking.scss';
+88
View File
@@ -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);
}
}
}
}
+82
View File
@@ -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,70 @@
import React, { useState, useEffect } from 'react'
import { CircularProgress, Flexbox, Text } from '@leon-ai/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>
)
}
+304 -9
View File
@@ -1,15 +1,306 @@
<!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>
<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.
@@ -27,16 +318,20 @@
<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>
</ul>
</div>
</main>
<footer>
<div id="logo"></div>
<br />
<div id="version">
<small>v</small>
</div>
-9
View File
@@ -1,9 +0,0 @@
function App() {
return (
<>
<p>Hello from React</p>
</>
)
}
export default App
-9
View File
@@ -1,9 +0,0 @@
import type React from 'react'
interface Props {
children: React.ReactNode
}
export function Button({ children }: Props) {
return <button>{children}</button>
}
+160 -24
View File
@@ -1,7 +1,17 @@
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 '@leon-ai/aurora'
import renderAuroraComponent from './render-aurora-component'
const WIDGETS_TO_FETCH = []
const WIDGETS_FETCH_CACHE = new Map()
export default class Chatbot {
constructor() {
constructor(socket, serverURL) {
this.socket = socket
this.serverURL = serverURL
this.et = new EventTarget()
this.feed = document.querySelector('#feed')
this.typing = document.querySelector('#is-typing')
@@ -15,11 +25,17 @@ export default class Chatbot {
this.scrollDown()
this.et.addEventListener('to-leon', (event) => {
this.createBubble('me', event.detail)
this.createBubble({
who: 'me',
string: event.detail
})
})
this.et.addEventListener('me-received', (event) => {
this.createBubble('leon', event.detail)
this.createBubble({
who: 'leon',
string: event.detail
})
})
}
@@ -62,10 +78,7 @@ export default class Chatbot {
}
loadFeed() {
/**
* TODO: widget: load widget from local storage
*/
return new Promise((resolve) => {
return new Promise(async (resolve) => {
if (this.parsedBubbles === null || this.parsedBubbles.length === 0) {
this.noBubbleMessage.classList.remove('hide')
localStorage.setItem('bubbles', JSON.stringify([]))
@@ -75,7 +88,12 @@ export default class Chatbot {
for (let i = 0; i < this.parsedBubbles.length; i += 1) {
const bubble = this.parsedBubbles[i]
this.createBubble(bubble.who, bubble.string, false)
this.createBubble({
who: bubble.who,
string: bubble.string,
save: false,
isCreatingFromLoadingFeed: true
})
if (i + 1 === this.parsedBubbles.length) {
setTimeout(() => {
@@ -83,40 +101,150 @@ export default class Chatbot {
}, 100)
}
}
/**
* Browse widgets that need to be fetched.
* Reverse widgets to fetch the last widgets first.
* Replace the loading content with the fetched widget
*/
const widgetContainers = WIDGETS_TO_FETCH.reverse()
for (let i = 0; i < widgetContainers.length; i += 1) {
const widgetContainer = widgetContainers[i]
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)
continue
}
const data = await axios.get(
`${this.serverURL}/api/v1/fetch-widget?skill_action=${widgetContainer.onFetch.actionName}&widget_id=${widgetContainer.widgetId}`
)
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)
}
}
})
}
createBubble(who, string, save = true) {
createBubble(params) {
const {
who,
string,
save = true,
bubbleId,
isCreatingFromLoadingFeed = false
} = params
const container = document.createElement('div')
const bubble = document.createElement('p')
container.className = `bubble-container ${who}`
bubble.className = 'bubble'
bubble.innerHTML = string
const formattedString = this.formatMessage(string)
bubble.innerHTML = formattedString
if (bubbleId) {
container.classList.add(bubbleId)
}
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'
let widgetComponentTree = null
let widgetSupportedEvents = null
showMore.className = 'show-more'
showMore.innerHTML = showMoreText
/**
* Widget rendering
*/
if (
formattedString.includes &&
formattedString.includes('"component":"WidgetWrapper"')
) {
const parsedWidget = JSON.parse(formattedString)
container.setAttribute('data-widget-id', parsedWidget.id)
container.appendChild(showMore)
/**
* On widget fetching, render the loader
*/
if (isCreatingFromLoadingFeed && parsedWidget.onFetch) {
const root = createRoot(container)
showMore.addEventListener('click', () => {
bubble.classList.toggle('show-all')
showMore.innerHTML =
showMore.innerHTML === showMoreText ? 'Show less' : showMoreText
})
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
}
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, string)
this.saveBubble(who, formattedString)
}
return container
}
saveBubble(who, string) {
@@ -132,4 +260,12 @@ export default class Chatbot {
localStorage.setItem('bubbles', JSON.stringify(this.parsedBubbles))
this.scrollDown()
}
formatMessage(message) {
if (typeof message === 'string') {
message = message.replace(/\n/g, '<br />')
}
return message
}
}
+254 -26
View File
@@ -1,24 +1,28 @@
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 VoiceEnergy from './voice-energy'
import { INIT_MESSAGES } from './constants'
export default class Client {
constructor(client, serverUrl, input, res) {
constructor(client, serverUrl, input) {
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.history = localStorage.getItem('history')
this.parsedHistory = []
this.info = res
this.chatbot = new Chatbot()
this.chatbot = new Chatbot(this.socket, this.serverUrl)
this.voiceEnergy = new VoiceEnergy(this)
this._recorder = {}
this._suggestions = []
this._answerGenerationId = 'xxx'
this._ttsAudioContext = null
this._isLeonGeneratingAnswer = false
this._isVoiceModeEnabled = false
// this._ttsAudioContextes = {}
}
set input(newInput) {
@@ -35,6 +39,15 @@ export default class Client {
return this._recorder
}
updateMood(mood) {
if (window.leonConfigInfo.llm.enabled) {
const moodContainer = document.querySelector('#mood')
moodContainer.textContent = `Leon's mood: ${mood.emoji}`
moodContainer.setAttribute('title', mood.type)
}
}
async sendInitMessages() {
for (let i = 0; i < INIT_MESSAGES.length; i++) {
const messages = INIT_MESSAGES[i]
@@ -53,15 +66,61 @@ export default class Client {
}
}
init(loader) {
setInitStatus(statusName, statusType) {
window.leonInitStatusEvent.dispatchEvent(
new CustomEvent('initStatusChange', {
detail: {
statusName,
statusType
}
})
)
}
asrStartRecording() {
if (!window.leonConfigInfo.stt.enabled) {
console.warn('ASR is not enabled')
return
}
if (!this._isVoiceModeEnabled) {
this.enableVoiceMode()
this.voiceEnergy.status = 'listening'
this.socket.emit('asr-start-record')
}
}
init() {
this.chatbot.init()
this.voiceEnergy.init()
this.socket.on('connect', () => {
this.socket.emit('init', this.client)
})
/**
* 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-llm', (status) => {
this.setInitStatus('llm', status)
})
this.socket.on('warmup-llm-duties', (status) => {
this.setInitStatus('llmDutiesWarmUp', status)
})
this.socket.on('ready', () => {
loader.stop()
setTimeout(() => {
const body = document.querySelector('body')
body.classList.remove('settingup')
}, 250)
if (this.chatbot.parsedBubbles?.length === 0) {
this.sendInitMessages()
@@ -69,7 +128,36 @@ export default class Client {
})
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
/**
* Just save the bubble if the newest bubble is from the streaming.
* Otherwise, create a new bubble
*/
const newestBubbleContainerElement =
document.querySelector('.leon:last-child')
const isNewestBubbleFromStreaming =
newestBubbleContainerElement?.classList.contains(
this._answerGenerationId
)
if (isNewestBubbleFromStreaming) {
this.chatbot.saveBubble('leon', data)
// Slightly delay the update to avoid the stream animation to be interrupted
setTimeout(() => {
// Update the text of the bubble (quick emoji fix)
newestBubbleContainerElement.querySelector('p.bubble').innerHTML =
this.chatbot.formatMessage(data)
}, 2_500)
} else {
this.chatbot.receivedFrom('leon', data)
}
})
this.socket.on('suggest', (data) => {
@@ -90,27 +178,121 @@ export default class Client {
})
this.socket.on('widget', (data) => {
/**
* TODO: widget: widget handler to core/skill; dynamic component rendering
*/
console.log('data', data)
this.chatbot.createBubble({
who: 'leon',
string: data
})
})
const container = document.createElement('div')
container.className = 'widget'
this.chatbot.feed.appendChild(container)
this.socket.on('widget-send-utterance', (utterance) => {
this._input.value = utterance
this.send('utterance')
})
const root = createRoot(container)
this.socket.on('new-mood', (mood) => {
this.updateMood(mood)
})
// 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
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('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'
})
this.socket.on('audio-forwarded', (data, cb) => {
@@ -127,7 +309,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,6 +346,11 @@ export default class Client {
}
send(keyword) {
// Prevent from sending utterance if Leon is still generating text (stream)
if (keyword === 'utterance' && this._isLeonGeneratingAnswer) {
return false
}
if (this._input.value !== '') {
this.socket.emit(keyword, {
client: this.client,
@@ -205,6 +392,10 @@ 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) {
@@ -222,4 +413,41 @@ export default class Client {
this._suggestions.push(newSuggestion)
}
enableVoiceMode() {
if (!this._isVoiceModeEnabled) {
this._isVoiceModeEnabled = true
const body = document.querySelector('body')
if (!body.classList.contains('voice-mode-enabled')) {
body.classList.add('voice-mode-enabled')
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
const body = document.querySelector('body')
const voiceContainer = document.querySelector('#voice-container')
if (voiceContainer) {
voiceContainer.style.animation = 'none'
voiceContainer.offsetHeight
voiceContainer.style.animation = null
}
if (body.classList.contains('voice-mode-enabled')) {
body.classList.remove('voice-mode-enabled')
}
}
}
}
-3
View File
@@ -12,9 +12,6 @@ export const INIT_MESSAGES = [
[
`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!`
],
[
`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!`
]
+193
View File
@@ -0,0 +1,193 @@
import { useEffect, useState, useRef } from 'react'
import { createRoot } from 'react-dom/client'
import {
WidgetWrapper,
Text,
Icon,
Flexbox,
List,
ListHeader,
ListItem,
Loader
} from '@leon-ai/aurora'
const container = document.querySelector('#init')
const root = createRoot(container)
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 [statusMap, setStatusMap] = useState({
clientCoreServerHandshake: 'loading',
tcpServerBoot: 'loading',
llm: 'loading',
llmDutiesWarmUp: 'loading'
})
useEffect(() => {
setTimeout(() => {
if (parentRef.current) {
parentRef.current.classList.remove('not-initialized')
}
}, 250)
function handleStatusChange(event) {
const { statusName, statusType } = event.detail
setStatusMap((prev) => ({ ...prev, [statusName]: statusType }))
}
window.leonInitStatusEvent.addEventListener(
'initStatusChange',
handleStatusChange
)
return () =>
window.leonInitStatusEvent.removeEventListener(
'initStatusChange',
handleStatusChange
)
}, [])
const statuses = []
for (let key of Object.keys(statusMap)) {
// If LLM is not enabled, we don't need to check for LLM duties warm up
if (
key === 'llmDutiesWarmUp' &&
(!config.llm?.enabled || !config.shouldWarmUpLLMDuties)
) {
statuses.push('success')
} else if (!config[key] || config[key].enabled) {
statuses.push(statusMap[key])
}
}
const areAllStatusesSuccess = statuses.every((status) => status === 'success')
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}>
Client and core server handshaked
</Item>
<Item status={statusMap.tcpServerBoot}>TCP server booted</Item>
{config.llm && config.llm.enabled && (
<Item status={statusMap.llm}>LLM loaded</Item>
)}
{config.shouldWarmUpLLMDuties && (
<Item status={statusMap.llmDutiesWarmUp}>
LLM duties warmed up
</Item>
)}
</List>
</WidgetWrapper>
</div>
</div>
)
}
root.render(<Init />)
-22
View File
@@ -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 }))
}
}
+53 -22
View File
@@ -1,10 +1,13 @@
import axios from 'axios'
import '@leon-ai/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 Recorder from './recorder'
// import listener from './listener'
import { onkeydownstartrecording, onkeydowninput } from './onkeydown'
const config = {
app: 'webapp',
@@ -19,29 +22,54 @@ 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 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)
// let rec = {}
// let chunks = []
v.innerHTML += client.info.version
window.leonConfigInfo = response.data
const infoKeys = [
'timeZone',
'telemetry',
'gpu',
'graphicsComputeAPI',
'totalVRAM',
'freeVRAM',
'usedVRAM',
'llm',
'shouldWarmUpLLMDuties',
'isLLMActionRecognitionEnabled',
'isLLMNLGEnabled',
'stt',
'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.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 +77,7 @@ document.addEventListener('DOMContentLoaded', async () => {
})
rec.onstart(() => {
/* */
/!* *!/
})
rec.onstop(() => {
@@ -106,18 +134,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 +157,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)}`)
+15 -13
View File
@@ -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 }
+53
View File
@@ -0,0 +1,53 @@
import { createElement } from 'react'
import * as auroraComponents from '@leon-ai/aurora'
import * as customAuroraComponents from '../custom-aurora-components'
export default function renderAuroraComponent(
socket,
component,
supportedEvents
) {
if (component) {
// 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} not found`)
}
// Check if the browsed component has a supported event and bind it
if (reactComponent) {
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)
}
}
+74
View File
@@ -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)
}
}
}
}
-20
View File
@@ -1,20 +0,0 @@
{
"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
}
}
+18 -5
View File
@@ -1,8 +1,7 @@
import fs from 'node:fs'
import path from 'node:path'
import { SKILLS_PATH as SKILLS_ROOT_PATH } from '@/constants'
import type { SkillConfigSchema } from '@server/schemas/skill-schemas'
import type { SkillConfigSchema } from '@/schemas/skill-schemas'
import type { IntentObject } from '@sdk/types'
@@ -10,15 +9,30 @@ const {
argv: [, , INTENT_OBJ_FILE_PATH]
} = process
export const LEON_VERSION = process.env['npm_package_version']
const BRIDGES_PATH = path.join(process.cwd(), 'bridges')
const NODEJS_BRIDGE_ROOT_PATH = path.join(BRIDGES_PATH, 'nodejs')
const NODEJS_BRIDGE_SRC_PATH = path.join(NODEJS_BRIDGE_ROOT_PATH, 'src')
const NODEJS_BRIDGE_VERSION_FILE_PATH = path.join(
NODEJS_BRIDGE_SRC_PATH,
'version.ts'
)
export const [, NODEJS_BRIDGE_VERSION] = fs
.readFileSync(NODEJS_BRIDGE_VERSION_FILE_PATH, 'utf8')
.split("'")
export const INTENT_OBJECT: IntentObject = JSON.parse(
fs.readFileSync(INTENT_OBJ_FILE_PATH as string, 'utf8')
)
export const SKILLS_PATH = path.join(process.cwd(), 'skills')
export const SKILL_PATH = path.join(
SKILLS_ROOT_PATH,
SKILLS_PATH,
INTENT_OBJECT.domain,
INTENT_OBJECT.skill
)
export const SKILLS_PATH = SKILLS_ROOT_PATH
export const SKILL_CONFIG: SkillConfigSchema = JSON.parse(
fs.readFileSync(
path.join(
@@ -29,4 +43,3 @@ export const SKILL_CONFIG: SkillConfigSchema = JSON.parse(
'utf8'
)
)
export { LEON_VERSION, NODEJS_BRIDGE_VERSION } from '@/constants'
+2
View File
@@ -9,6 +9,7 @@ import { INTENT_OBJECT } from '@bridge/constants'
action,
lang,
utterance,
new_utterance,
current_entities,
entities,
current_resolvers,
@@ -20,6 +21,7 @@ import { INTENT_OBJECT } from '@bridge/constants'
const params: ActionParams = {
lang,
utterance,
new_utterance,
current_entities,
entities,
current_resolvers,
+5 -8
View File
@@ -1,12 +1,9 @@
import { Widget } from '../widget'
import { type ButtonProps } from '@leon-ai/aurora'
// TODO: contains the button API. rendering engine <-> SDK
interface ButtonOptions {
text: string
}
import { WidgetComponent } from '../widget-component'
export class Button extends Widget<ButtonOptions> {
public constructor(options: ButtonOptions) {
super(options)
export class Button extends WidgetComponent<ButtonProps> {
constructor(props: ButtonProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type CardProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Card extends WidgetComponent<CardProps> {
constructor(props: CardProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type CheckboxProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Checkbox extends WidgetComponent<CheckboxProps> {
constructor(props: CheckboxProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type CircularProgressProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class CircularProgress extends WidgetComponent<CircularProgressProps> {
constructor(props: CircularProgressProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type FlexboxProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Flexbox extends WidgetComponent<FlexboxProps> {
constructor(props: FlexboxProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type FormProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Form extends WidgetComponent<FormProps> {
constructor(props: FormProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type IconButtonProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class IconButton extends WidgetComponent<IconButtonProps> {
constructor(props: IconButtonProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type IconProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Icon extends WidgetComponent<IconProps> {
constructor(props: IconProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type ImageProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Image extends WidgetComponent<ImageProps> {
constructor(props: ImageProps) {
super(props)
}
}
+30
View File
@@ -0,0 +1,30 @@
export * from './button'
export * from './card'
export * from './checkbox'
export * from './circular-progress'
export * from './flexbox'
export * from './form'
export * from './icon'
export * from './icon-button'
export * from './image'
export * from './input'
export * from './link'
export * from './list'
export * from './list-header'
export * from './list-item'
export * from './loader'
export * from './progress'
export * from './radio'
export * from './radio-group'
export * from './range-slider'
export * from './scroll-container'
export * from './select'
export * from './select-option'
export * from './status'
export * from './switch'
export * from './tab'
export * from './tab-content'
export * from './tab-group'
export * from './tab-list'
export * from './text'
export * from './widget-wrapper'
+9
View File
@@ -0,0 +1,9 @@
import { type InputProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Input extends WidgetComponent<InputProps> {
constructor(props: InputProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type LinkProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Link extends WidgetComponent<LinkProps> {
constructor(props: LinkProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type ListHeaderProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class ListHeader extends WidgetComponent<ListHeaderProps> {
constructor(props: ListHeaderProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type ListItemProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class ListItem extends WidgetComponent<ListItemProps> {
constructor(props: ListItemProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type ListProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class List extends WidgetComponent<ListProps> {
constructor(props: ListProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type LoaderProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Loader extends WidgetComponent<LoaderProps> {
constructor(props: LoaderProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type ProgressProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Progress extends WidgetComponent<ProgressProps> {
constructor(props: ProgressProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type RadioGroupProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class RadioGroup extends WidgetComponent<RadioGroupProps> {
constructor(props: RadioGroupProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type RadioProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Radio extends WidgetComponent<RadioProps> {
constructor(props: RadioProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type RangeSliderProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class RangeSlider extends WidgetComponent<RangeSliderProps> {
constructor(props: RangeSliderProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type ScrollContainerProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class ScrollContainer extends WidgetComponent<ScrollContainerProps> {
constructor(props: ScrollContainerProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type SelectOptionProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class SelectOption extends WidgetComponent<SelectOptionProps> {
constructor(props: SelectOptionProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type SelectProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Select extends WidgetComponent<SelectProps> {
constructor(props: SelectProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type StatusProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Status extends WidgetComponent<StatusProps> {
constructor(props: StatusProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type SwitchProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Switch extends WidgetComponent<SwitchProps> {
constructor(props: SwitchProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type TabContentProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class TabContent extends WidgetComponent<TabContentProps> {
constructor(props: TabContentProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type TabGroupProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class TabGroup extends WidgetComponent<TabGroupProps> {
constructor(props: TabGroupProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type TabListProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class TabList extends WidgetComponent<TabListProps> {
constructor(props: TabListProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type TabProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Tab extends WidgetComponent<TabProps> {
constructor(props: TabProps) {
super(props)
}
}
+9
View File
@@ -0,0 +1,9 @@
import { type TextProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class Text extends WidgetComponent<TextProps> {
constructor(props: TextProps) {
super(props)
}
}
@@ -0,0 +1,9 @@
import { type WidgetWrapperProps } from '@leon-ai/aurora'
import { WidgetComponent } from '../widget-component'
export class WidgetWrapper extends WidgetComponent<WidgetWrapperProps> {
constructor(props: WidgetWrapperProps) {
super(props)
}
}
+45 -20
View File
@@ -5,6 +5,8 @@ import type {
AnswerConfig
} from '@sdk/types'
import { INTENT_OBJECT, SKILL_CONFIG } from '@bridge/constants'
import { WidgetWrapper } from '@sdk/aurora'
import { SUPPORTED_WIDGET_EVENTS } from '@sdk/widget-component'
class Leon {
private static instance: Leon
@@ -42,18 +44,26 @@ class Leon {
if (data != null) {
for (const key in data) {
// In case the answer needs speech and text differentiation
if (typeof answer !== 'string' && answer.text) {
answer.text = answer.text.replaceAll(`%${key}%`, String(data[key]))
answer.speech = answer.speech.replaceAll(
`%${key}%`,
String(data[key])
)
} else {
if (typeof answer === 'string') {
answer = (answer as string).replaceAll(
`%${key}%`,
String(data[key])
)
} else {
// In case the answer needs speech and text differentiation
if (answer.text) {
answer.text = answer.text.replaceAll(
`%${key}%`,
String(data[key])
)
}
if (answer.speech) {
answer.speech = answer.speech.replaceAll(
`%${key}%`,
String(data[key])
)
}
}
}
}
@@ -62,21 +72,26 @@ class Leon {
const { variables } = SKILL_CONFIG
for (const key in variables) {
// In case the answer needs speech and text differentiation
if (typeof answer !== 'string' && answer.text) {
answer.text = answer.text.replaceAll(
`%${key}%`,
String(variables[key])
)
answer.speech = answer.speech.replaceAll(
`%${key}%`,
String(variables[key])
)
} else {
if (typeof answer === 'string') {
answer = (answer as string).replaceAll(
`%${key}%`,
String(variables[key])
)
} else {
// In case the answer needs speech and text differentiation
if (answer.text) {
answer.text = answer.text.replaceAll(
`%${key}%`,
String(variables[key])
)
}
if (answer.speech) {
answer.speech = answer.speech.replaceAll(
`%${key}%`,
String(variables[key])
)
}
}
}
}
@@ -114,7 +129,17 @@ class Leon {
}
if (answerInput.widget) {
answerObject.output.widget = answerInput.widget
answerObject.output.widget = {
actionName: `${INTENT_OBJECT.domain}:${INTENT_OBJECT.skill}:${INTENT_OBJECT.action}`,
widget: answerInput.widget.widget,
id: answerInput.widget.id,
onFetch: answerInput.widget.onFetch ?? null,
componentTree: new WidgetWrapper({
...answerInput.widget.wrapperProps,
children: [answerInput.widget.render()]
}),
supportedEvents: SUPPORTED_WIDGET_EVENTS
}
}
// "Temporize" for the data buffer output on the core
+1
View File
@@ -24,6 +24,7 @@ export class Memory<T = unknown> {
if (this.name.includes(':') && this.name.split(':').length === 3) {
this.isFromAnotherSkill = true
const [domainName, skillName, memoryName] = this.name.split(':')
this.memoryPath = path.join(
SKILLS_PATH,
+2 -2
View File
@@ -38,7 +38,7 @@ interface NetworkResponse<ResponseData> {
export class NetworkError<ResponseErrorData = unknown> extends Error {
public readonly response: NetworkResponse<ResponseErrorData>
public constructor(response: NetworkResponse<ResponseErrorData>) {
constructor(response: NetworkResponse<ResponseErrorData>) {
super(`[NetworkError]: ${response.statusCode} ${response.data}`)
this.response = response
Object.setPrototypeOf(this, NetworkError.prototype)
@@ -49,7 +49,7 @@ export class Network {
private readonly options: NetworkOptions
private axios: AxiosInstance
public constructor(options: NetworkOptions = {}) {
constructor(options: NetworkOptions = {}) {
this.options = options
this.axios = axios.create({
baseURL: this.options.baseURL
+1 -1
View File
@@ -7,7 +7,7 @@ export class Settings<T extends Record<string, unknown>> {
private readonly settingsPath: string
private readonly settingsSamplePath: string
public constructor() {
constructor() {
this.settingsPath = path.join(SKILL_PATH, 'src', 'settings.json')
this.settingsSamplePath = path.join(
SKILL_PATH,
+13
View File
@@ -0,0 +1,13 @@
import { INTENT_OBJECT } from '@bridge/constants'
/**
* Get the widget id if any
* @example getWidgetId() // 'timerwidget-5q1xlzeh
*/
export function getWidgetId(): string | null {
return (
INTENT_OBJECT.current_entities.find(
(entity) => entity.entity === 'widgetid'
)?.sourceText ?? null
)
}
+4 -2
View File
@@ -9,6 +9,8 @@ import type {
} from '@/core/brain/types'
import type { SkillAnswerConfigSchema } from '@/schemas/skill-schemas'
import type { Widget } from '@sdk/widget'
export type { ActionParams, IntentObject }
export * from '@/core/nlp/types'
@@ -20,7 +22,7 @@ export type ActionFunction = (params: ActionParams) => Promise<void>
*/
export interface Answer {
key?: string
widget?: unknown // TODO
widget?: Widget
data?: AnswerData
core?: SkillAnswerCoreData
}
@@ -28,7 +30,7 @@ export interface TextAnswer extends Answer {
key: string
}
export interface WidgetAnswer extends Answer {
widget: unknown
widget: Widget
key?: string
}
export type AnswerData = Record<string, string | number> | null
@@ -0,0 +1,51 @@
export type SupportedWidgetEvent = (typeof SUPPORTED_WIDGET_EVENTS)[number]
interface WidgetEvent {
type: SupportedWidgetEvent
id: string
}
export const SUPPORTED_WIDGET_EVENTS = [
'onClick',
'onSubmit',
'onChange',
'onStart',
'onEnd'
] as const
function generateId(): string {
return Math.random().toString(36).substring(2, 7)
}
export abstract class WidgetComponent<T = unknown> {
public readonly component: string
public readonly id: string
public readonly props: T
public readonly events: WidgetEvent[]
protected constructor(props: T) {
this.component = this.constructor.name
this.id = `${this.component.toLowerCase()}-${generateId()}`
this.props = props
this.events = this.parseEvents()
}
private parseEvents(): WidgetEvent[] {
if (!this.props) {
return []
}
const eventTypes = Object.keys(this.props).filter(
(key) =>
key.startsWith('on') &&
SUPPORTED_WIDGET_EVENTS.includes(key as SupportedWidgetEvent)
) as SupportedWidgetEvent[]
return eventTypes.map((type) => ({
type,
id: `${this.id}_${type.toLowerCase()}-${generateId()}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
method: this.props[type]()
}))
}
}
+135 -6
View File
@@ -1,9 +1,138 @@
export abstract class Widget<T> {
public readonly type: string
public readonly options: T
import { type WidgetWrapperProps } from '@leon-ai/aurora'
protected constructor(options: T) {
this.type = this.constructor.name
this.options = options
import { INTENT_OBJECT, SKILL_CONFIG } from '@bridge/constants'
import { WidgetComponent } from '@sdk/widget-component'
type UtteranceSender = 'leon' | 'owner'
interface SendUtteranceWidgetEventMethodParams {
from: UtteranceSender
utterance: string
}
interface RunSkillActionWidgetEventMethodParams {
actionName: string
params: Record<string, unknown>
}
interface SendUtteranceOptions {
from?: UtteranceSender
data?: Record<string, unknown>
}
export interface WidgetEventMethod {
methodName: 'send_utterance' | 'run_skill_action'
methodParams:
| SendUtteranceWidgetEventMethodParams
| RunSkillActionWidgetEventMethodParams
}
export interface WidgetOptions<T = unknown> {
wrapperProps?: Omit<WidgetWrapperProps, 'children'>
onFetch?: {
widgetId?: string | undefined
actionName: string
}
params: T
}
export abstract class Widget<T = unknown> {
public actionName: string
public id: string
public widget: string
public onFetch: WidgetOptions<T>['onFetch'] | null = null
public wrapperProps: WidgetOptions<T>['wrapperProps']
public params: WidgetOptions<T>['params']
protected constructor(options: WidgetOptions<T>) {
if (options?.wrapperProps) {
this.wrapperProps = options.wrapperProps
}
this.actionName = `${INTENT_OBJECT.domain}:${INTENT_OBJECT.skill}:${INTENT_OBJECT.action}`
this.params = options.params
this.widget = this.constructor.name
if (options?.onFetch) {
this.onFetch = {
widgetId: options.onFetch.widgetId,
actionName: `${INTENT_OBJECT.domain}:${INTENT_OBJECT.skill}:${options.onFetch.actionName}`
}
}
this.id =
options.onFetch?.widgetId ||
`${this.widget.toLowerCase()}-${Math.random()
.toString(36)
.substring(2, 10)}`
}
/**
* Render the widget
*/
public abstract render(): WidgetComponent<unknown>
/**
* Indicate the core to send a given utterance
* @param key The key of the content
* @param options The options of the utterance
* @example content('provider_selected', { data: { provider: 'Spotify' } }) // 'I chose the Spotify provider'
*/
protected sendUtterance(
key: string,
options?: SendUtteranceOptions
): WidgetEventMethod {
const utteranceContent = this.content(key, options?.data)
const from = options?.from || 'owner'
return {
methodName: 'send_utterance',
methodParams: {
from,
utterance: utteranceContent
}
}
}
/**
* Indicate the core to run a given skill action
* @param actionName The name of the action
* @param params The parameters of the action
* @example runSkillAction('music_audio:player:next', { provider: 'Spotify' })
*/
protected runSkillAction(
actionName: string,
params: Record<string, unknown>
): WidgetEventMethod {
return {
methodName: 'run_skill_action',
methodParams: {
actionName,
params
}
}
}
/**
* Grab and compute the target content of the widget
* @param key The key of the content
* @param data The data to apply
* @example content('select_provider') // 'Please select a provider'
* @example content('provider_selected', { provider: 'Spotify' }) // 'I chose the Spotify provider'
*/
protected content(key: string, data?: Record<string, unknown>): string {
const { widget_contents: widgetContents } = SKILL_CONFIG
if (!widgetContents || !widgetContents[key]) {
return 'INVALID'
}
let content = widgetContents[key]
if (Array.isArray(content)) {
content = content[Math.floor(Math.random() * content.length)] as string
}
if (data) {
for (const key in data) {
content = content.replaceAll(`%${key}%`, String(data[key]))
}
}
return content
}
}
+1 -1
View File
@@ -1 +1 @@
export const VERSION = '1.1.0'
export const VERSION = '1.2.0'
-5
View File
@@ -14,11 +14,6 @@
"exactOptionalPropertyTypes": false,
"declaration": true
},
"ts-node": {
"swc": true,
"require": ["tsconfig-paths/register"],
"files": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
+3 -3
View File
@@ -4,11 +4,11 @@ verify_ssl = true
name = "pypi"
[requires]
python_version = "3.9.10"
python_version = "3.11.9"
[packages]
setuptools = "*"
wheel = "*"
cx-freeze = "==6.11.1"
requests = "==2.28.1"
cx-freeze = "==7.1.1"
requests = "==2.32.3"
beautifulsoup4 = "==4.7.1"
+1
View File
@@ -9,6 +9,7 @@ def main():
params = {
'lang': INTENT_OBJECT['lang'],
'utterance': INTENT_OBJECT['utterance'],
'new_utterance': INTENT_OBJECT['new_utterance'],
'current_entities': INTENT_OBJECT['current_entities'],
'entities': INTENT_OBJECT['entities'],
'current_resolvers': INTENT_OBJECT['current_resolvers'],
+4 -7
View File
@@ -1,9 +1,6 @@
from ..widget import Widget
from ..widget_component import WidgetComponent
# TODO: contains the button API. rendering engine <-> SDK
class Button(Widget):
def __init__(self, options):
super().__init__(options)
class Button(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Card(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Checkbox(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class CircularProgress(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Flexbox(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Form(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Icon(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class IconButton(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Image(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Input(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Link(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class List(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class ListHeader(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class ListItem(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Loader(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Progress(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
+6
View File
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class Radio(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class RadioGroup(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)
@@ -0,0 +1,6 @@
from ..widget_component import WidgetComponent
class RangeSlider(WidgetComponent[dict]):
def __init__(self, props: dict):
super().__init__(props)

Some files were not shown because too many files have changed in this diff Show More