├── .gitignore ├── LICENCE ├── Makefile ├── README.md ├── docs └── copter_leonardo_no_bg.png ├── nuxt_app ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── app.vue ├── assets │ ├── fonts │ │ ├── raleway │ │ │ ├── OFL.txt │ │ │ ├── README.txt │ │ │ ├── Raleway-Italic-VariableFont_wght.ttf │ │ │ ├── Raleway-VariableFont_wght.ttf │ │ │ └── static │ │ │ │ ├── Raleway-Black.ttf │ │ │ │ ├── Raleway-BlackItalic.ttf │ │ │ │ ├── Raleway-Bold.ttf │ │ │ │ ├── Raleway-BoldItalic.ttf │ │ │ │ ├── Raleway-ExtraBold.ttf │ │ │ │ ├── Raleway-ExtraBoldItalic.ttf │ │ │ │ ├── Raleway-ExtraLight.ttf │ │ │ │ ├── Raleway-ExtraLightItalic.ttf │ │ │ │ ├── Raleway-Italic.ttf │ │ │ │ ├── Raleway-Light.ttf │ │ │ │ ├── Raleway-LightItalic.ttf │ │ │ │ ├── Raleway-Medium.ttf │ │ │ │ ├── Raleway-MediumItalic.ttf │ │ │ │ ├── Raleway-Regular.ttf │ │ │ │ ├── Raleway-SemiBold.ttf │ │ │ │ ├── Raleway-SemiBoldItalic.ttf │ │ │ │ ├── Raleway-Thin.ttf │ │ │ │ └── Raleway-ThinItalic.ttf │ │ └── semantic-ui-icon │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── brand-icons.eot │ │ │ │ ├── brand-icons.svg │ │ │ │ ├── brand-icons.ttf │ │ │ │ ├── brand-icons.woff │ │ │ │ ├── brand-icons.woff2 │ │ │ │ ├── icons.eot │ │ │ │ ├── icons.svg │ │ │ │ ├── icons.ttf │ │ │ │ ├── icons.woff │ │ │ │ ├── icons.woff2 │ │ │ │ ├── outline-icons.eot │ │ │ │ ├── outline-icons.svg │ │ │ │ ├── outline-icons.ttf │ │ │ │ ├── outline-icons.woff │ │ │ │ └── outline-icons.woff2 │ │ │ └── icon.css │ └── style │ │ ├── app.scss │ │ ├── forms.scss │ │ ├── semantic-ui-overrides.scss │ │ ├── tooltip.scss │ │ ├── vue-datepicker.scss │ │ ├── vueform-multiselect.scss │ │ └── vueform-toggle.scss ├── components │ ├── ClipboardButton.vue │ ├── InfoBox.vue │ └── MenuHeader.vue ├── composables │ ├── chatbot.ts │ ├── menu.ts │ ├── params.ts │ └── utils.ts ├── deploy.sh ├── nuxt.config.ts ├── package-lock.json ├── package.json ├── pages │ ├── index.vue │ └── index │ │ ├── about.vue │ │ ├── ai_response.vue │ │ ├── api_token.vue │ │ └── params.vue ├── plugins │ └── tooltip.ts ├── public │ ├── favicon.png │ ├── images │ │ ├── boy.png │ │ ├── copter.png │ │ ├── girl.png │ │ ├── man.png │ │ └── old_lady.png │ └── semantic-ui │ │ └── components │ │ └── loader.min.css └── tsconfig.json ├── python_server ├── .dockerignore ├── Dockerfile ├── Makefile ├── fly.toml ├── python_server │ └── __main__.py ├── requirements.txt └── setup.py └── training ├── Makefile ├── data.py ├── generate_prompt.py ├── get_training_data_from_gpt4.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | auth 2 | cog-llama-template 3 | gen_images -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Nikita Logos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: format_check 2 | format_check: 3 | cd python_server && make format 4 | cd python_server && make check 5 | 6 | cd training && make format 7 | cd training && make check 8 | 9 | cd nuxt_app && npm run format 10 | 11 | .PHONY: install_git_pre_commit_hook 12 | install_git_pre_commit_hook: 13 | echo '#!/bin/sh\nmake format_check' > .git/hooks/pre-commit 14 | chmod +x .git/hooks/pre-commit 15 | 16 | .PHONY: uninstall_git_pre_commit_hook 17 | uninstall_git_pre_commit_hook: 18 | rm .git/hooks/pre-commit || true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Copter with a present](docs/copter_leonardo_no_bg.png) 2 | 3 | # Birthday Greetings AI 4 | 5 | Create outstanding birthday greetings for your loved ones, friends and colleagues! 6 | Use them right away, or take them as templates to spark your own creativity. 7 | Welcome to the ~~future~~ present! Your birthday present! 🎁 8 | 9 | ## How to use 10 | 11 | 1. Navigate to https://logosnikita.com/birthday_greetings_ai/ 12 | 2. Obtain a token from [Replicate](https://replicate.com/) and paste it into the corresponding field 13 | 1. You can skip this step and copy prompt for your favourite chatbot 14 | 3. Click "Next" to get to "Params" tab 15 | 4. Fill in data about a person you want to congratulate. 16 | 1. You can just enter name and that will be enough, but to get more personalized greeting you may want to fill in other fields 17 | 2. Params you enter will result in a prompt for AI model. You can see it below the input form. 18 | 5. Press "Create greeting" to generate a greeting with Llama 2. 19 | 1. You will get to "AI Response" tab. Please wait until AI creates a response. 20 | 2. You can copy response to clipboard, regenerate response or export the entire dialogue as .txt or .json 21 | 6. On "Params" page you can either copy prompt to clipboard to paste it into a different chatbot. 22 | 1. For example: [ChatGPT](https://chat.openai.com/), [Claude 2](https://chat.claudeai.ai) 23 | 24 | ## Example results 25 | 26 | Although this app uses Llama 2 as a language model, you can achive better results 27 | in bigger, but yet less accessible :( models. 28 | 29 | For example, here's what I've managed to generate in OpenAI's GPT-4: 30 | 1. [Samurai Tiger's Birthday 🐯🎂 (verbose)](https://chat.openai.com/share/5f85c7ef-d6db-4cf8-bde3-753f82439688) 31 | 2. [Tiger's Samurai Birthday 🐯🎂⚔️ (laconic)](https://chat.openai.com/share/bf914614-0502-4c43-a8f6-88dd41da4713) 32 | 33 | ## Integrations 34 | 35 | Birthday Greetings AI supports passing params in url query: 36 | ``` 37 | https://logosnikita.com/birthday_greetings_ai/?name=Victor&date_of_birth=1798-07-30&use_age=false&use_zodiac_sign=true 38 | ``` 39 | 40 | ### Supported params 41 | 42 | | Parameter | Format | Example | 43 | |:------------------|:------------------|:----------------------| 44 | | `name` | URL encoded | `Victor+Frankenstein` | 45 | | `date_of_birth` | `YYYY-MM-DD` | `1798-07-30` | 46 | | `use_age` | `true` or `false` | `false` | 47 | | `use_zodiac_sign` | `true` or `false` | `true` | 48 | 49 | If you need more params for your application, feel free to contact me :) 50 | 51 | ### Example integration 52 | 53 | My another project, [Birthday Reminder](https://github.com/nikitalogos/birthday_reminder) has integration with `Birthday Greetings AI`. 54 | 55 | It creates birthday events in Google Calendar and 56 | adds a link to this app with pre-computed `name`, `date_of_birth`, etc. fields into the event description, 57 | so you can just click it from calendar or from email notification. 58 | 59 | ## Tech specs 60 | 61 | This app uses Python3 [aiohttp](https://pypi.org/project/aiohttp/) server hosted at [fly.io](https://fly.io/) as a backend. 62 | 63 | Frontend is built with [Nuxt 3](https://nuxt.com/) framework 64 | and uses [Vue 3](https://vuejs.org/), [Pug](https://www.npmjs.com/package/pug) and [Sass](https://sass-lang.com/). 65 | It also uses a few packages defined in `package.json` and some parts of [Semantic.ui](https://semantic-ui.com/) UI framework. 66 | It is hosted via [Github Pages](https://pages.github.com/). 67 | 68 | ## Licences 69 | 70 | This app is licenced under [MIT licence](https://github.com/nikitalogos/birthday_greetings_ai/blob/main/LICENCE). 71 | 72 | It uses Llama 2 language model developed by Meta, which is licenced under [Llama 2 Community License Agreement](https://ai.meta.com/resources/models-and-libraries/llama-downloads/) 73 | 74 | Llama 2 is served by [Replicate.com](https://replicate.com/) under [Replicate Terms of Use](https://replicate.com/terms) 75 | 76 | Arts for web page were generated via `Stable Diffusion XL` at [Clipdrop.co](https://clipdrop.co/stable-diffusion) 77 | 78 | Cover for this README.md was generated with [Leonardo.Ai](https://leonardo.ai/). 79 | 80 | ## Support 81 | 82 | If you like this project, leave star on GitHub :) 83 | 84 | ## Related 85 | 86 | If you like this project, you may also like my another project - [Birthday Reminder](https://github.com/nikitalogos/birthday_reminder)! 87 | 88 | This is a simple app to keep track of all birthdays you don't want to miss out. 89 | -------------------------------------------------------------------------------- /docs/copter_leonardo_no_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/docs/copter_leonardo_no_bg.png -------------------------------------------------------------------------------- /nuxt_app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["@nuxtjs/eslint-config", "plugin:prettier/recommended"], 4 | }; 5 | -------------------------------------------------------------------------------- /nuxt_app/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .nuxt 4 | .nitro 5 | .cache 6 | dist 7 | 8 | # Node dependencies 9 | node_modules 10 | 11 | # Logs 12 | logs 13 | *.log 14 | 15 | # Misc 16 | .DS_Store 17 | .fleet 18 | .idea 19 | 20 | # Local env files 21 | .env 22 | .env.* 23 | !.env.example 24 | -------------------------------------------------------------------------------- /nuxt_app/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /nuxt_app/.prettierignore: -------------------------------------------------------------------------------- 1 | assets/fonts 2 | public -------------------------------------------------------------------------------- /nuxt_app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } 4 | -------------------------------------------------------------------------------- /nuxt_app/app.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010 The Raleway Project Authors (impallari@gmail.com), with Reserved Font Name "Raleway". 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/README.txt: -------------------------------------------------------------------------------- 1 | Raleway Variable Font 2 | ===================== 3 | 4 | This download contains Raleway as both variable fonts and static fonts. 5 | 6 | Raleway is a variable font with this axis: 7 | wght 8 | 9 | This means all the styles are contained in these files: 10 | Raleway-VariableFont_wght.ttf 11 | Raleway-Italic-VariableFont_wght.ttf 12 | 13 | If your app fully supports variable fonts, you can now pick intermediate styles 14 | that aren’t available as static fonts. Not all apps support variable fonts, and 15 | in those cases you can use the static font files for Raleway: 16 | static/Raleway-Thin.ttf 17 | static/Raleway-ExtraLight.ttf 18 | static/Raleway-Light.ttf 19 | static/Raleway-Regular.ttf 20 | static/Raleway-Medium.ttf 21 | static/Raleway-SemiBold.ttf 22 | static/Raleway-Bold.ttf 23 | static/Raleway-ExtraBold.ttf 24 | static/Raleway-Black.ttf 25 | static/Raleway-ThinItalic.ttf 26 | static/Raleway-ExtraLightItalic.ttf 27 | static/Raleway-LightItalic.ttf 28 | static/Raleway-Italic.ttf 29 | static/Raleway-MediumItalic.ttf 30 | static/Raleway-SemiBoldItalic.ttf 31 | static/Raleway-BoldItalic.ttf 32 | static/Raleway-ExtraBoldItalic.ttf 33 | static/Raleway-BlackItalic.ttf 34 | 35 | Get started 36 | ----------- 37 | 38 | 1. Install the font files you want to use 39 | 40 | 2. Use your app's font picker to view the font family and all the 41 | available styles 42 | 43 | Learn more about variable fonts 44 | ------------------------------- 45 | 46 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 47 | https://variablefonts.typenetwork.com 48 | https://medium.com/variable-fonts 49 | 50 | In desktop apps 51 | 52 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 53 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 54 | 55 | Online 56 | 57 | https://developers.google.com/fonts/docs/getting_started 58 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 59 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 60 | 61 | Installing fonts 62 | 63 | MacOS: https://support.apple.com/en-us/HT201749 64 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 65 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 66 | 67 | Android Apps 68 | 69 | https://developers.google.com/fonts/docs/android 70 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 71 | 72 | License 73 | ------- 74 | Please read the full license text (OFL.txt) to understand the permissions, 75 | restrictions and requirements for usage, redistribution, and modification. 76 | 77 | You can use them in your products & projects - print or digital, 78 | commercial or otherwise. 79 | 80 | This isn't legal advice, please consider consulting a lawyer and see the full 81 | license for all details. 82 | -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/Raleway-Italic-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/Raleway-Italic-VariableFont_wght.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/Raleway-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/Raleway-VariableFont_wght.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-Black.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-BlackItalic.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-Bold.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-BoldItalic.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-ExtraBold.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-ExtraLight.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-Italic.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-Light.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-LightItalic.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-Medium.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-MediumItalic.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-Regular.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-SemiBold.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-Thin.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/raleway/static/Raleway-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/raleway/static/Raleway-ThinItalic.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/README.md: -------------------------------------------------------------------------------- 1 | `icon.css` file is from semantic-ui-icon. 2 | 3 | Icons were taken from Semantic UI full package, because from semantic-ui-icon package they were not working. 4 | 5 | To make it work, paths to resources were modified. Other remains intact. -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/brand-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/brand-icons.eot -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/brand-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/brand-icons.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/brand-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/brand-icons.woff -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/brand-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/brand-icons.woff2 -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/icons.eot -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/icons.woff -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/icons.woff2 -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/outline-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/outline-icons.eot -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/outline-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/outline-icons.ttf -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/outline-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/outline-icons.woff -------------------------------------------------------------------------------- /nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/outline-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/assets/fonts/semantic-ui-icon/assets/fonts/outline-icons.woff2 -------------------------------------------------------------------------------- /nuxt_app/assets/style/app.scss: -------------------------------------------------------------------------------- 1 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~global css variables~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 | // for colors adjustment: https://color.adobe.com/ru/create/color-wheel 3 | :root { 4 | // colors 5 | --bg-color: #222; 6 | --input-bg-color: #444; 7 | 8 | --color-faded: #7f7f7f; 9 | --color: #eee; 10 | 11 | --accent-color: #ffc452; // #c0c; 12 | --accent-color-transparent: #ffc45250; // #cc00cc50; 13 | 14 | --error-color: #ff0000; 15 | 16 | --border-color: #7f7f7f; 17 | --border-color-active: #fff; 18 | 19 | --white-color: #fff; 20 | --light-grey-color: #eee; 21 | 22 | --page-max-width: 600px; 23 | } 24 | 25 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~html tags~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | body { 27 | margin: 0; 28 | overflow-x: hidden; 29 | 30 | font-family: Raleway; 31 | font-size: 16px; 32 | 33 | background-color: var(--bg-color); 34 | color: var(--color); 35 | color-scheme: dark !important; 36 | 37 | // Fix for mobile chrome browser. It fails to update BackgroundStars on scroll, when the page height changes 38 | // solution: https://stackoverflow.com/questions/24944925/background-image-jumps-when-address-bar-hides-ios-android-mobile-chrome 39 | &:before { 40 | content: ""; 41 | display: block; 42 | position: fixed; 43 | left: 0; 44 | top: 0; 45 | width: 100%; 46 | height: calc(100% + 200px); 47 | z-index: -10; 48 | background-color: var(--bg-color); 49 | } 50 | } 51 | 52 | a { 53 | font-weight: bold; 54 | } 55 | 56 | a, 57 | button { 58 | text-decoration: none; 59 | 60 | color: var(--light-grey-color); 61 | &:hover { 62 | color: var(--white-color); 63 | } 64 | } 65 | 66 | button { 67 | // reset all default button css but outline to make it look more like 68 | background: none; 69 | // color: inherit; 70 | border: none; 71 | padding: 0; 72 | font: inherit; 73 | cursor: pointer; 74 | // outline: inherit; 75 | 76 | // apply styles 77 | font-weight: bold; 78 | } 79 | 80 | // ~~~~~~~~~scrollbars~~~~~~~~~ 81 | *::-webkit-scrollbar { 82 | height: 5px; 83 | width: 10px; 84 | } 85 | *::-webkit-scrollbar-thumb { 86 | background: var(--accent-color-transparent); 87 | border-radius: 1.5px; 88 | 89 | &:hover { 90 | background: var(--accent-color); 91 | } 92 | } 93 | *::-webkit-scrollbar-corner { 94 | opacity: 0; 95 | } 96 | 97 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~classes~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 98 | 99 | .button { 100 | margin: 0.5rem; 101 | padding: 0.5rem; 102 | box-sizing: border-box; 103 | 104 | font-weight: bold; 105 | 106 | text-align: center; 107 | cursor: pointer; 108 | user-select: none; 109 | 110 | backdrop-filter: blur(10px); 111 | 112 | border-radius: 0.25rem; 113 | border: 1px solid var(--border-color); 114 | &:not(.disabled):hover { 115 | border: 1px solid var(--border-color-active); 116 | } 117 | &.accent { 118 | color: var(--accent-color); 119 | border: 1px solid var(--accent-color-transparent); 120 | &:not(.disabled):hover { 121 | border: 1px solid var(--accent-color); 122 | } 123 | } 124 | &.compact { 125 | font-size: 0.8rem; 126 | } 127 | 128 | &.disabled { 129 | cursor: default; 130 | opacity: 0.2; 131 | pointer-events: none; // disable onClick and other events on disabled button 132 | } 133 | } 134 | 135 | .page-wrapper { 136 | max-width: var(--page-max-width); 137 | margin: 0 auto; 138 | padding: 0 20px; 139 | } 140 | 141 | .sep-line { 142 | width: 100%; 143 | height: 1px; 144 | 145 | margin-top: 30px; 146 | margin-bottom: 30px; 147 | 148 | background-image: linear-gradient( 149 | 90deg, 150 | rgba(255, 255, 255, 0) 0%, 151 | rgba(255, 255, 255, 0.5) 50%, 152 | rgba(255, 255, 255, 0) 100% 153 | ); 154 | } 155 | 156 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~fonts~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 157 | // how to specify font: https://stackoverflow.com/questions/11737168/how-to-import-fonts-in-css 158 | @font-face { 159 | font-family: "Raleway"; 160 | src: url("assets/fonts/raleway/static/Raleway-Regular.ttf") format("truetype"); 161 | font-weight: normal; 162 | font-style: normal; 163 | font-display: swap; 164 | } 165 | @font-face { 166 | font-family: "Raleway"; 167 | src: url("assets/fonts/raleway/static/Raleway-Bold.ttf") format("truetype"); 168 | font-weight: bold; 169 | font-style: normal; 170 | font-display: swap; 171 | } 172 | @font-face { 173 | font-family: "Raleway"; 174 | src: url("assets/fonts/raleway/static/Raleway-Italic.ttf") format("truetype"); 175 | font-weight: normal; 176 | font-style: italic; 177 | font-display: swap; 178 | } 179 | @font-face { 180 | font-family: "Raleway"; 181 | src: url("assets/fonts/raleway/static/Raleway-BoldItalic.ttf") format("truetype"); 182 | font-weight: bold; 183 | font-style: italic; 184 | font-display: swap; 185 | } 186 | -------------------------------------------------------------------------------- /nuxt_app/assets/style/forms.scss: -------------------------------------------------------------------------------- 1 | form { 2 | button.button { 3 | margin-left: 0; 4 | margin-right: 0; 5 | } 6 | 7 | .param { 8 | display: flex; 9 | flex-direction: row; 10 | align-items: top; 11 | justify-content: left; 12 | flex-wrap: wrap; 13 | 14 | margin: 10px 0; 15 | } 16 | label { 17 | line-height: 2rem; 18 | width: 150px; 19 | font-weight: bold; 20 | 21 | &.accent { 22 | color: var(--accent-color); 23 | } 24 | } 25 | .group-label { 26 | color: var(--accent-color); 27 | font-weight: bold; 28 | font-size: 1.2em; 29 | } 30 | 31 | .multi-select-wrapper, 32 | :not(.multiselect-tags-search-wrapper) > input, 33 | .date-wrapper { 34 | flex-base: calc(100% - 150px); 35 | flex: 1; 36 | min-width: 300px; 37 | } 38 | .textarea-wrapper, 39 | textarea { 40 | width: 100%; 41 | } 42 | .hint { 43 | color: var(--color-faded); 44 | margin: 5px 0; 45 | } 46 | .multi-select-wrapper .hint { 47 | margin-left: 5px; 48 | } 49 | input, 50 | textarea { 51 | box-sizing: border-box; 52 | } 53 | textarea { 54 | position: relative; 55 | .symbols-used { 56 | position: absolute; 57 | bottom: 0; 58 | right: 0; 59 | font-size: 0.8em; 60 | color: #999; 61 | } 62 | } 63 | input, 64 | textarea { 65 | font-size: 1rem; 66 | line-height: 1.5rem; 67 | padding: 6px; 68 | background-color: var(--input-bg-color); 69 | border: 1px solid var(--border-color); 70 | border-radius: 4px; 71 | &:hover { 72 | border-color: var(--border-color-active); 73 | } 74 | } 75 | input.required { 76 | border-color: var(--accent-color); 77 | } 78 | 79 | .date-wrapper { 80 | display: flex; 81 | flex-direction: row; 82 | align-items: center; 83 | justify-content: left; 84 | flex-wrap: wrap; 85 | 86 | .dp__main { 87 | width: 100%; 88 | 89 | // hide buggy elements 90 | .dp__btn.dp__button.dp__overlay_action.dp__button_bottom, 91 | .dp__month_year_row > .dp__btn.dp--arrow-btn-nav { 92 | display: none; 93 | } 94 | .dp__month_year_row .dp__btn.dp__month_year_select { 95 | pointer-events: none; 96 | } 97 | // end hide buggy elements 98 | } 99 | .computed { 100 | margin: 10px 0; 101 | color: var(--accent-color); 102 | .hidden { 103 | text-decoration: line-through; 104 | color: var(--color-faded); 105 | } 106 | } 107 | } 108 | .toggle-wrapper { 109 | display: flex; 110 | flex-direction: row; 111 | align-items: center; 112 | flex-wrap: wrap; 113 | 114 | .toggle-wrapper-inner { 115 | margin-right: 10px; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /nuxt_app/assets/style/semantic-ui-overrides.scss: -------------------------------------------------------------------------------- 1 | .ui.active.inline.loader:before { 2 | border-color: var(--accent-color-transparent); 3 | } 4 | .ui.active.inline.loader:after { 5 | border-color: var(--accent-color) transparent transparent; 6 | } 7 | -------------------------------------------------------------------------------- /nuxt_app/assets/style/tooltip.scss: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | display: none; 3 | position: absolute; 4 | z-index: 10; 5 | 6 | padding: 5px 10px; 7 | border-radius: 5px; 8 | 9 | background-color: rgba(0, 0, 0, 0.6); 10 | color: white; 11 | 12 | font-size: 0.9rem; 13 | } 14 | -------------------------------------------------------------------------------- /nuxt_app/assets/style/vue-datepicker.scss: -------------------------------------------------------------------------------- 1 | .dp__theme_dark { 2 | --dp-background-color: var(--input-bg-color); 3 | --dp-text-color: var(--color); 4 | --dp-hover-color: #484848; 5 | --dp-hover-text-color: #ffffff; 6 | --dp-hover-icon-color: #959595; 7 | --dp-primary-color: #005cb2; 8 | --dp-primary-text-color: #ffffff; 9 | --dp-secondary-color: #a9a9a9; 10 | --dp-border-color: var(--border-color); 11 | --dp-menu-border-color: #2d2d2d; 12 | --dp-border-color-hover: var(--border-color-active); 13 | --dp-disabled-color: #737373; 14 | --dp-scroll-bar-background: #212121; 15 | --dp-scroll-bar-color: #484848; 16 | --dp-success-color: #00701a; 17 | --dp-success-color-disabled: #428f59; 18 | --dp-icon-color: #959595; 19 | --dp-danger-color: #e53935; 20 | --dp-highlight-color: rgba(0, 92, 178, 0.2); 21 | } 22 | -------------------------------------------------------------------------------- /nuxt_app/assets/style/vueform-multiselect.scss: -------------------------------------------------------------------------------- 1 | .multiselect { 2 | --ms-font-size: 1rem; 3 | --ms-line-height: 1.375; 4 | --ms-bg: var(--input-bg-color); 5 | --ms-bg-disabled: #f3f4f6; 6 | --ms-border-color: var(--border-color); 7 | --ms-border-width: 1px; 8 | --ms-border-color-active: var(--border-color-active); 9 | --ms-border-width-active: 1px; 10 | --ms-radius: 4px; 11 | --ms-py: 0.5rem; 12 | --ms-px: 0.875rem; 13 | --ms-ring-width: 3px; 14 | --ms-ring-color: #10b98130; 15 | --ms-placeholder-color: var(--color-faded); 16 | --ms-max-height: 10rem; 17 | 18 | --ms-spinner-color: #10b981; 19 | --ms-caret-color: #999999; 20 | --ms-clear-color: var(--color-faded); 21 | --ms-clear-color-hover: var(--color); 22 | 23 | --ms-tag-font-size: 0.875rem; 24 | --ms-tag-line-height: 1.25rem; 25 | --ms-tag-font-weight: 600; 26 | --ms-tag-bg: var(--accent-color-transparent); 27 | --ms-tag-bg-disabled: #9ca3af; 28 | --ms-tag-color: var(--accent-color); 29 | --ms-tag-color-disabled: #ffffff; 30 | --ms-tag-radius: 4px; 31 | --ms-tag-py: 0.125rem; 32 | --ms-tag-px: 0.5rem; 33 | --ms-tag-my: 0.25rem; 34 | --ms-tag-mx: 0.25rem; 35 | 36 | --ms-tag-remove-radius: 4px; 37 | --ms-tag-remove-py: 0.25rem; 38 | --ms-tag-remove-px: 0.25rem; 39 | --ms-tag-remove-my: 0rem; 40 | --ms-tag-remove-mx: 0.125rem; 41 | 42 | --ms-dropdown-bg: var(--input-bg-color); 43 | --ms-dropdown-border-color: var(--border-color-active); 44 | --ms-dropdown-border-width: 1px; 45 | --ms-dropdown-radius: 4px; 46 | 47 | --ms-group-label-py: 0.3rem; 48 | --ms-group-label-px: 0.75rem; 49 | --ms-group-label-line-height: 1.375; 50 | --ms-group-label-bg: #e5e7eb; 51 | --ms-group-label-color: #374151; 52 | --ms-group-label-bg-pointed: #d1d5db; 53 | --ms-group-label-color-pointed: #374151; 54 | --ms-group-label-bg-disabled: #f3f4f6; 55 | --ms-group-label-color-disabled: #d1d5db; 56 | --ms-group-label-bg-selected: #059669; 57 | --ms-group-label-color-selected: #ffffff; 58 | --ms-group-label-bg-selected-pointed: #0c9e70; 59 | --ms-group-label-color-selected-pointed: #ffffff; 60 | --ms-group-label-bg-selected-disabled: #75cfb1; 61 | --ms-group-label-color-selected-disabled: #d1fae5; 62 | 63 | --ms-option-font-size: 1rem; 64 | --ms-option-line-height: 1.375; 65 | --ms-option-bg-pointed: #ffffff; 66 | --ms-option-color-pointed: #1f2937; 67 | --ms-option-bg-selected: var(--accent-color-transparent); 68 | --ms-option-color-selected: var(--color); 69 | --ms-option-bg-disabled: #ffffff; 70 | --ms-option-color-disabled: #d1d5db; 71 | --ms-option-bg-selected-pointed: var(--accent-color); 72 | --ms-option-color-selected-pointed: var(--color); 73 | --ms-option-bg-selected-disabled: #ffffff; 74 | --ms-option-color-selected-disabled: #d1fae5; 75 | --ms-option-py: 0.5rem; 76 | --ms-option-px: 0.75rem; 77 | 78 | --ms-empty-color: #4b5563; 79 | } 80 | -------------------------------------------------------------------------------- /nuxt_app/assets/style/vueform-toggle.scss: -------------------------------------------------------------------------------- 1 | .toggle-container { 2 | --toggle-width: 5rem; 3 | --toggle-height: 1.25rem; 4 | --toggle-border: 0.125rem; 5 | --toggle-font-size: 0.75rem; 6 | --toggle-duration: 150ms; 7 | --toggle-bg-on: var(--accent-color-transparent); 8 | --toggle-bg-off: var(--input-bg-color); 9 | --toggle-bg-on-disabled: #d1d5db; 10 | --toggle-bg-off-disabled: #e5e7eb; 11 | --toggle-border-on: var(--accent-color); 12 | --toggle-border-off: var(--border-color); 13 | --toggle-border-on-disabled: #d1d5db; 14 | --toggle-border-off-disabled: #e5e7eb; 15 | --toggle-ring-width: 3px; 16 | --toggle-ring-color: #10b98130; 17 | --toggle-text-on: var(--color); 18 | --toggle-text-off: var(--color); 19 | --toggle-text-on-disabled: #9ca3af; 20 | --toggle-text-off-disabled: #9ca3af; 21 | --toggle-handle-enabled: var(--color); 22 | --toggle-handle-disabled: #f3f4f6; 23 | } 24 | -------------------------------------------------------------------------------- /nuxt_app/components/ClipboardButton.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 70 | 71 | 108 | -------------------------------------------------------------------------------- /nuxt_app/components/InfoBox.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 32 | -------------------------------------------------------------------------------- /nuxt_app/components/MenuHeader.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | 24 | 77 | -------------------------------------------------------------------------------- /nuxt_app/composables/chatbot.ts: -------------------------------------------------------------------------------- 1 | import { reactive, computed } from "vue"; 2 | 3 | import { params } from "./params"; 4 | 5 | function export_file(name, text) { 6 | var element = document.createElement("a"); 7 | element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text)); 8 | element.setAttribute("download", name); 9 | 10 | element.style.display = "none"; 11 | document.body.appendChild(element); 12 | 13 | element.click(); 14 | 15 | document.body.removeChild(element); 16 | } 17 | 18 | const is_production = process.env.NODE_ENV === "production"; 19 | const backend_url = is_production 20 | ? "https://birthday-greetings-ai.fly.dev" 21 | : "http://" + window.location.hostname + ":8080"; 22 | 23 | export const chatbot = reactive({ 24 | token: null, 25 | is_in_progress: false, 26 | 27 | chat_history: [ 28 | // { 29 | // timestamp_ms: new Date().getTime(), 30 | // duration_ms: 10.533 * 1000, 31 | // prompt: "Hello, how are you?", 32 | // response: "I'm fine, how are you?", 33 | // is_error: false, 34 | // error_str: "", 35 | // completed: true, 36 | // }, 37 | // { 38 | // timestamp_ms: new Date().getTime(), 39 | // duration_ms: 10.533 * 1000, 40 | // prompt: 41 | // "Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?", 42 | // response: 43 | // "I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?", 44 | // is_error: false, 45 | // error_str: "", 46 | // completed: true, 47 | // }, 48 | // { 49 | // timestamp_ms: new Date().getTime(), 50 | // duration_ms: 10.533 * 1000, 51 | // prompt: 52 | // "Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?", 53 | // response: 54 | // "I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?", 55 | // is_error: false, 56 | // error_str: "", 57 | // completed: true, 58 | // }, 59 | // { 60 | // timestamp_ms: new Date().getTime(), 61 | // duration_ms: 10.533 * 1000, 62 | // prompt: 63 | // "Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?Hello, how are you?", 64 | // response: 65 | // "I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?I'm fine, how are you?", 66 | // is_error: false, 67 | // error_str: "", 68 | // }, 69 | // { 70 | // timestamp_ms: new Date().getTime(), 71 | // duration_ms: 10.533 * 1000, 72 | // prompt: "Hello, how are you?", 73 | // response: "", 74 | // is_error: true, 75 | // error_str: "Access denied", 76 | // completed: true, 77 | // }, 78 | ], 79 | 80 | response_or_error(message) { 81 | if (!message.completed) { 82 | return "Waiting for response..."; 83 | } 84 | if (message.is_error) { 85 | return `Error: ${message.error_str}`; 86 | } 87 | return message.response; 88 | }, 89 | 90 | export_txt() { 91 | const data_str = this.chat_history 92 | .map((item) => { 93 | return ( 94 | `~~~~~~~~~~~~~Prompt~~~~~~~~~~~~~\n\n${item.prompt}\n\n` + 95 | `~~~~~~~~~~~~~Response~~~~~~~~~~~~~\n\n${this.response_or_error(item)}` 96 | ); 97 | }) 98 | .join("\n\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n\n"); 99 | 100 | export_file("birthday_greetings_ai_session.txt", data_str); 101 | }, 102 | 103 | export_json() { 104 | const data_str = JSON.stringify({ chat_history: this.chat_history }, null, 4); 105 | export_file("birthday_greetings_ai_session.json", data_str); 106 | }, 107 | 108 | async run_impl(prompt) { 109 | this.is_in_progress = true; 110 | try { 111 | const response = await fetch(backend_url + "/api/chatbot", { 112 | method: "POST", 113 | body: JSON.stringify({ 114 | token: this.token, 115 | prompt, 116 | }), 117 | }); 118 | const data = await response.json(); 119 | 120 | if (data.error) { 121 | throw new Error(data.error); 122 | } 123 | return data.message; 124 | } finally { 125 | this.is_in_progress = false; 126 | } 127 | }, 128 | 129 | async run() { 130 | const prompt = params.prompt; 131 | const start_time = Date.now(); 132 | 133 | let response = ""; 134 | let is_error = false; 135 | let error_str = ""; 136 | 137 | const message = { 138 | timestamp_ms: start_time, 139 | prompt, 140 | // temporary values: 141 | duration_ms: 0, 142 | response, 143 | is_error, 144 | error_str, 145 | completed: false, 146 | }; 147 | this.chat_history.push(message); 148 | 149 | try { 150 | response = await this.run_impl(prompt); 151 | } catch (error) { 152 | is_error = true; 153 | error_str = error.message; 154 | } 155 | const end_time = Date.now(); 156 | 157 | message.duration_ms = end_time - start_time; 158 | message.response = response; 159 | message.is_error = is_error; 160 | message.error_str = error_str; 161 | message.completed = true; 162 | // trigger vue3 to update message 163 | this.chat_history.pop(message); 164 | this.chat_history.push(message); 165 | }, 166 | }); 167 | 168 | chatbot.can_run = computed(() => { 169 | return params.is_valid && chatbot.token && !chatbot.is_in_progress; 170 | }); 171 | 172 | chatbot.google_translate_url = computed(() => { 173 | const history = chatbot.chat_history; 174 | 175 | if (history.length === 0) { 176 | return null; 177 | } 178 | const last_item = history[history.length - 1]; 179 | if (last_item.is_error) { 180 | return null; 181 | } 182 | const text = encodeURIComponent(last_item.response); 183 | 184 | const language_codes = { 185 | Arabic: "ar", 186 | Chinese: "zh-CN", 187 | Danish: "da", 188 | Dutch: "nl", 189 | English: "en", 190 | French: "fr", 191 | German: "de", 192 | Hebrew: "he", 193 | Hindi: "hi", 194 | Italian: "it", 195 | Japanese: "ja", 196 | Korean: "ko", 197 | Norwegian: "no", 198 | Polish: "pl", 199 | Portuguese: "pt", 200 | Russian: "ru", 201 | Swedish: "sv", 202 | Thai: "th", 203 | Turkish: "tr", 204 | Spanish: "es", 205 | }; 206 | const target_lang = language_codes[params.target_language.value] ?? "en"; 207 | 208 | const prompt = params.prompt; 209 | const url = `https://translate.google.com/?sl=auto&tl=${target_lang}&text=${text}&op=translate`; 210 | return url; 211 | }); 212 | -------------------------------------------------------------------------------- /nuxt_app/composables/menu.ts: -------------------------------------------------------------------------------- 1 | import { reactive, computed } from "vue"; 2 | 3 | export const menu = reactive({ 4 | title: "Birthday Greetings AI", 5 | items: [ 6 | { 7 | title: "API Token", 8 | path: "/api_token", 9 | }, 10 | { 11 | title: "Params", 12 | path: "/params", 13 | }, 14 | { 15 | title: "AI Response", 16 | path: "/ai_response", 17 | }, 18 | { 19 | title: "About", 20 | path: "/about", 21 | }, 22 | ], 23 | }); 24 | -------------------------------------------------------------------------------- /nuxt_app/composables/params.ts: -------------------------------------------------------------------------------- 1 | import { reactive, computed, ref } from "vue"; 2 | 3 | import { format_date } from "./utils"; 4 | 5 | export const params = reactive({ 6 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~essentials~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7 | name: { 8 | label: "Name", 9 | type: "text", 10 | value: null, 11 | required: true, 12 | }, 13 | date_of_birth: { 14 | label: "Date of Birth", 15 | type: "date", 16 | value: null, 17 | }, 18 | use_age: { 19 | label: "Use age", 20 | type: "toggle", 21 | value: true, 22 | }, 23 | use_zodiac_sign: { 24 | label: "Use zodiac sign", 25 | type: "toggle", 26 | value: false, 27 | }, 28 | 29 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~person details~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 30 | gender: { 31 | label: "Gender", 32 | type: "select", 33 | value: null, 34 | options: ["male", "female", "non-binary", "prefer not to say"], 35 | }, 36 | relationship: { 37 | label: "Relationship", 38 | type: "select", 39 | value: null, 40 | options: [ 41 | "child", 42 | "parent", 43 | "partner", 44 | "spouse", 45 | "sibling", 46 | "grandparent", 47 | "cousin", 48 | "friend", 49 | "colleague", 50 | "mentor", 51 | "boss", 52 | "acquaintance", 53 | ], 54 | }, 55 | profession: { 56 | label: "Profession", 57 | type: "text", 58 | value: null, 59 | }, 60 | hobby: { 61 | label: "Hobby", 62 | type: "text", 63 | value: null, 64 | }, 65 | 66 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~content~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 67 | what_to_wish: { 68 | label: "What to wish", 69 | type: "multiselect", 70 | value: [], 71 | options: [ 72 | "Health", 73 | "Wealth", 74 | "Happiness", 75 | "Success", 76 | "Love", 77 | "Adventure", 78 | "Wisdom", 79 | "Peace", 80 | "Creativity", 81 | "Growth", 82 | "Friendship", 83 | "Courage", 84 | "Opportunities", 85 | "Balance", 86 | "Longevity", 87 | ], 88 | }, 89 | target_language: { 90 | label: "Target language", 91 | type: "select", 92 | value: "English", 93 | required: true, 94 | options: [ 95 | "Arabic", 96 | "Chinese", 97 | "Danish", 98 | "Dutch", 99 | "English", 100 | "French", 101 | "German", 102 | "Hebrew", 103 | "Hindi", 104 | "Italian", 105 | "Japanese", 106 | "Korean", 107 | "Norwegian", 108 | "Polish", 109 | "Portuguese", 110 | "Russian", 111 | "Swedish", 112 | "Thai", 113 | "Turkish", 114 | "Spanish", 115 | ], 116 | }, 117 | 118 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~styling~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 119 | use_emojis: { 120 | label: "Use emojis", 121 | type: "select", 122 | value: null, 123 | options: ["no", "few", "more", "lots of"], 124 | }, 125 | greeting_length: { 126 | label: "Greeting length", 127 | type: "select", 128 | value: null, 129 | options: ["short (<50 words)", "medium (~100 words)", "long (>150 words)"], 130 | }, 131 | greeting_style: { 132 | label: "Greeting style", 133 | type: "select", 134 | value: null, 135 | options: [ 136 | "informal", 137 | "formal", 138 | "funny", 139 | "inspirational", 140 | "romantic", 141 | "energetic", 142 | "calm", 143 | "sarcastic", 144 | "poetic", 145 | "philosophical", 146 | "spiritual", 147 | "minimalistic", 148 | ], 149 | }, 150 | theme: { 151 | label: "Theme", 152 | type: "select", 153 | value: null, 154 | options: [ 155 | "Anime", 156 | "Cyberpunk", 157 | "Fairy Tale", 158 | "Fantasy", 159 | "Futuristic", 160 | "Gothic", 161 | "Horror", 162 | "Medieval", 163 | "Mermaid", 164 | "Nature", 165 | "Pirate", 166 | "Princess", 167 | "Retro", 168 | "Samurai", 169 | "Sci-Fi", 170 | "Space", 171 | "Sport", 172 | "Steampunk", 173 | "Travel", 174 | "Unicorn", 175 | "Victorian", 176 | "Viking", 177 | "Western", 178 | ], 179 | }, 180 | use_quotation: { 181 | label: "Use quotation", 182 | type: "toggle", 183 | value: false, 184 | }, 185 | use_affirmation: { 186 | label: "Use affirmation", 187 | type: "toggle", 188 | value: false, 189 | }, 190 | 191 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~comment~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 192 | comment: { 193 | label: "Comment", 194 | type: "textarea", 195 | max_length: 200, 196 | value: null, 197 | }, 198 | }); 199 | for (let key in params) { 200 | params[key].key = key; 201 | } 202 | params.use_age.hint = computed( 203 | () => "(Age " + (params.use_age.value ? "will" : "won't") + " be mentioned in greeting)", 204 | ); 205 | params.use_zodiac_sign.hint = computed( 206 | () => "(Zodiac sign " + (params.use_zodiac_sign.value ? "will" : "won't") + " be mentioned in greeting)", 207 | ); 208 | params.is_advanced_mode = ref(false); 209 | 210 | params.age = computed(() => { 211 | const now = new Date(); 212 | const date = params.date_of_birth.value; 213 | if (!date) return null; 214 | 215 | const was_birthday_this_year = 216 | now.getMonth() > date.getMonth() || (now.getMonth() === date.getMonth() && now.getDate() >= date.getDate()); 217 | const age = now.getFullYear() - date.getFullYear() - (was_birthday_this_year ? 0 : 1); 218 | return age; 219 | }); 220 | 221 | const ZODIAC_SIGNS = [ 222 | { sign: ["Capricorn", "♑"], date: [1, 19] }, 223 | { sign: ["Aquarius", "♒"], date: [2, 18] }, 224 | { sign: ["Pisces", "♓"], date: [3, 20] }, 225 | { sign: ["Aries", "♈"], date: [4, 19] }, 226 | { sign: ["Taurus", "♉"], date: [5, 20] }, 227 | { sign: ["Gemini", "♊"], date: [6, 20] }, 228 | { sign: ["Cancer", "♋"], date: [7, 22] }, 229 | { sign: ["Leo", "♌"], date: [8, 22] }, 230 | { sign: ["Virgo", "♍"], date: [9, 22] }, 231 | { sign: ["Libra", "♎"], date: [10, 22] }, 232 | { sign: ["Scorpio", "♏"], date: [11, 21] }, 233 | { sign: ["Sagittarius", "♐"], date: [12, 21] }, 234 | { sign: ["Capricorn", "♑"], date: [12, 31] }, // to handle the case of dates after Dec 21 235 | ]; 236 | params.zodiac_sign = computed(() => { 237 | const date = params.date_of_birth.value; 238 | if (!date) return null; 239 | 240 | for (let zodiac_sign of ZODIAC_SIGNS) { 241 | if ( 242 | date.getMonth() + 1 < zodiac_sign.date[0] || 243 | (date.getMonth() + 1 === zodiac_sign.date[0] && date.getDate() <= zodiac_sign.date[1]) 244 | ) { 245 | return `${zodiac_sign.sign[0]} (${zodiac_sign.sign[1]})`; 246 | } 247 | } 248 | throw new Error("Date is invalid. That's weird. Shouldn't get here"); 249 | }); 250 | 251 | params.groups = computed(() => { 252 | const p = params; 253 | return [ 254 | { items: [p.name], advanced: false }, 255 | { items: [p.date_of_birth, p.use_age, p.use_zodiac_sign], advanced: false }, 256 | { items: [p.what_to_wish, p.target_language], advanced: false }, 257 | { 258 | label: "Personal details:", 259 | items: [p.gender, p.relationship, p.profession, p.hobby], 260 | advanced: true, 261 | }, 262 | { 263 | label: "Styling:", 264 | items: [p.use_emojis, p.greeting_length, p.greeting_style, p.theme, p.use_quotation, p.use_affirmation], 265 | advanced: true, 266 | }, 267 | { items: [p.comment], advanced: true }, 268 | ]; 269 | }); 270 | params.values = computed(() => { 271 | const dict = {}; 272 | params.groups.forEach((group) => { 273 | group.items.forEach((item) => { 274 | dict[item.key] = item.value; 275 | }); 276 | }); 277 | return dict; 278 | }); 279 | 280 | params.is_valid = computed(() => { 281 | return Object.values(params).every((item) => { 282 | if (!item || typeof item !== "object" || Array.isArray(item)) return true; 283 | return !item.required || item.value; 284 | }); 285 | }); 286 | 287 | params.prompt = computed(() => { 288 | const v = params.values; 289 | 290 | let prompt = `I want to congratulate ${v.name} on their birthday. Please create a birthday greeting.\n`; 291 | if (v.date_of_birth) { 292 | prompt += `${v.name} was born on ${format_date(v.date_of_birth)}.\n`; 293 | 294 | if (v.use_age) { 295 | prompt += `Their age is ${params.age}.\n`; 296 | } else { 297 | prompt += `Please do not mention ${v.name}'s age in the greeting.\n`; 298 | } 299 | 300 | if (v.use_zodiac_sign) { 301 | prompt += `Their zodiac sign is ${params.zodiac_sign}.\n`; 302 | } else { 303 | prompt += `Please do not mention ${v.name}'s zodiac sign in the greeting.\n`; 304 | } 305 | prompt += "\n"; 306 | } 307 | 308 | if (v.gender) { 309 | prompt += `${v.name} identifies as ${v.gender}.\n`; 310 | } 311 | if (v.relationship) { 312 | prompt += `I am ${v.name}'s ${v.relationship}.\n`; 313 | } 314 | if (v.profession) { 315 | prompt += `${v.name} is a ${v.profession}.\n`; 316 | } 317 | if (v.hobby) { 318 | prompt += `${v.name} enjoys ${v.hobby}.\n`; 319 | } 320 | prompt += "\n"; 321 | 322 | if (v.what_to_wish.length > 0) { 323 | prompt += "Please include the following wishes in the greeting: "; 324 | prompt += v.what_to_wish.map((wish) => `"${wish}"`).join(", "); 325 | prompt += "\n"; 326 | } 327 | 328 | prompt += `The birthday greeting should be in ${v.target_language} language.\n`; 329 | prompt += "\n"; 330 | 331 | if (v.use_emojis) { 332 | prompt += `Please include ${v.use_emojis} emojis in the greeting.\n`; 333 | } 334 | if (v.greeting_length) { 335 | prompt += `The greeting should be ${v.greeting_length}.\n`; 336 | } 337 | if (v.greeting_style) { 338 | prompt += `Please use a ${v.greeting_style} style for the greeting.\n`; 339 | } 340 | if (v.theme) { 341 | prompt += `Please incorporate a ${v.theme} theme into the greeting.\n`; 342 | } 343 | if (v.use_quotation) { 344 | prompt += "Please include a relevant famous quotation in the greeting.\n"; 345 | } 346 | if (v.use_affirmation) { 347 | prompt += "Please include a motivating affirmation in the greeting.\n"; 348 | } 349 | prompt += "\n"; 350 | 351 | if (v.comment) { 352 | prompt += `Additional context: ${v.comment}\n`; 353 | } 354 | 355 | prompt = prompt.replace(/\n\n\n/g, "\n\n"); 356 | prompt = prompt.trim(); 357 | return prompt; 358 | }); 359 | -------------------------------------------------------------------------------- /nuxt_app/composables/utils.ts: -------------------------------------------------------------------------------- 1 | export function prevent_short_word_hangs(text) { 2 | return text 3 | .split(" ") 4 | .map((word) => ([1, 2, 3].includes(word.length) ? word + " " : word + " ")) 5 | .join(""); 6 | } 7 | 8 | export function format_date(timestamp_ms) { 9 | const date = new Date(timestamp_ms); 10 | 11 | const year = date.getFullYear(); 12 | const month = String(date.getMonth() + 1).padStart(2, "0"); 13 | const day = String(date.getDate()).padStart(2, "0"); 14 | 15 | return `${year}-${month}-${day}`; 16 | } 17 | 18 | export function format_datetime(timestamp_ms) { 19 | const date = new Date(timestamp_ms); 20 | 21 | const year = date.getFullYear(); 22 | const month = String(date.getMonth() + 1).padStart(2, "0"); 23 | const day = String(date.getDate()).padStart(2, "0"); 24 | const hours = String(date.getHours()).padStart(2, "0"); 25 | const minutes = String(date.getMinutes()).padStart(2, "0"); 26 | const seconds = String(date.getSeconds()).padStart(2, "0"); 27 | 28 | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; 29 | } 30 | -------------------------------------------------------------------------------- /nuxt_app/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # abort on errors 4 | set -e 5 | 6 | # fetch repo remote url 7 | REPO_URL=$(git config --get remote.origin.url) 8 | 9 | # clear output folder 10 | rm -rf .output 11 | 12 | # build 13 | npm run generate 14 | 15 | # navigate into the build output directory 16 | cd dist 17 | 18 | # workaround for github pages. 19 | # Explaination: https://learnvue.co/2020/09/how-to-deploy-your-vue-app-to-github-pages/#tip-handling-vue-router-with-a-custom-404-page--> 20 | # seems that it's not needed for nuxt 21 | # cp index.html 404.html 22 | 23 | # if you are deploying to a custom domain 24 | echo 'logosnikita.com' > CNAME 25 | 26 | # prevent Github Pages returning 404 for files/dirs starting with an underscore 27 | touch .nojekyll 28 | 29 | git init 30 | git remote add origin "$REPO_URL" 31 | git config --add --local core.sshCommand 'ssh -i ~/.ssh/id_github_nikita_logos' 32 | git config user.email "thelogosnik@gmail.com" 33 | 34 | git checkout -b gh-pages 35 | git add -A 36 | git commit -m 'deploy' 37 | git push -f origin gh-pages 38 | 39 | cd - 40 | -------------------------------------------------------------------------------- /nuxt_app/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | ssr: false, // Disable Server Side rendering 4 | 5 | // setup for Github Pages 6 | target: "static", 7 | app: { 8 | baseURL: "/birthday_greetings_ai/", 9 | 10 | // fixed by .nojekyll file at root of repo: 11 | //"buildAssetsDir": "/nuxt/", // default is /_nuxt/ which is ignored by Github Pages 12 | }, 13 | 14 | telemetry: false, // disable nuxt telemetry 15 | 16 | devtools: { enabled: true }, 17 | 18 | // for vuepic/vue-datepicker 19 | build: { 20 | transpile: ["@vuepic/vue-datepicker"], 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /nuxt_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "scripts": { 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare", 10 | "deploy": "./deploy.sh", 11 | "lint": "eslint --ext .ts,.js,.vue .", 12 | "format": "prettier --write ." 13 | }, 14 | "devDependencies": { 15 | "@nuxt/devtools": "latest", 16 | "@nuxtjs/eslint-config": "^12.0.0", 17 | "@types/node": "^18", 18 | "eslint": "^8.44.0", 19 | "eslint-config-prettier": "^8.8.0", 20 | "eslint-plugin-prettier": "^5.0.0-alpha.1", 21 | "nuxt": "^3.6.2", 22 | "prettier": "^3.0.0", 23 | "pug": "^3.0.2", 24 | "pug-plain-loader": "^1.1.0", 25 | "sass": "^1.63.6" 26 | }, 27 | "dependencies": { 28 | "@vueform/multiselect": "^2.6.2", 29 | "@vueform/toggle": "^2.1.3", 30 | "@vuepic/vue-datepicker": "^6.0.0", 31 | "vue-github-button": "^3.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /nuxt_app/pages/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 36 | 37 | 46 | -------------------------------------------------------------------------------- /nuxt_app/pages/index/about.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 46 | 47 | 72 | -------------------------------------------------------------------------------- /nuxt_app/pages/index/ai_response.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 89 | 90 | 216 | -------------------------------------------------------------------------------- /nuxt_app/pages/index/api_token.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 89 | 90 | 174 | -------------------------------------------------------------------------------- /nuxt_app/pages/index/params.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 254 | -------------------------------------------------------------------------------- /nuxt_app/plugins/tooltip.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtPlugin((nuxtApp) => { 2 | nuxtApp.vueApp.directive("tooltip", { 3 | mounted(el) { 4 | const tooltip_el = document.createElement("span"); 5 | tooltip_el.classList.add("tooltip"); 6 | document.body.appendChild(tooltip_el); 7 | 8 | let timeout = null; 9 | 10 | function showTooltip(event) { 11 | tooltip_el.textContent = el.getAttribute("aria-label"); 12 | 13 | const touchEvent = event.touches ? event.touches[0] : event; 14 | tooltip_el.style.left = touchEvent.pageX + "px"; 15 | tooltip_el.style.top = touchEvent.pageY + "px"; 16 | 17 | tooltip_el.style.display = "block"; 18 | } 19 | function showTooltipDelayed(event) { 20 | timeout = setTimeout(() => { 21 | if (tooltip_el.style.display === "block") return; // if tooltip is already shown - do nothing 22 | showTooltip(event); 23 | }, 500); 24 | } 25 | 26 | function hideTooltip() { 27 | clearTimeout(timeout); 28 | tooltip_el.style.display = "none"; 29 | } 30 | 31 | el.addEventListener("click", showTooltip); // on mobile on touch - show tooltip immediately 32 | // mouseleave doesn't work when mouse already above element and tooltip disappears under mouse 33 | el.addEventListener("mouseenter", showTooltipDelayed); 34 | el.addEventListener("mouseleave", hideTooltip); 35 | 36 | el.destroyEvents = () => { 37 | el.removeEventListener("click", showTooltip); 38 | el.removeEventListener("mouseenter", showTooltipDelayed); 39 | el.removeEventListener("mouseleave", hideTooltip); 40 | 41 | hideTooltip(); 42 | }; 43 | }, 44 | beforeUnmount(el) { 45 | el.destroyEvents(); 46 | }, 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /nuxt_app/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/public/favicon.png -------------------------------------------------------------------------------- /nuxt_app/public/images/boy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/public/images/boy.png -------------------------------------------------------------------------------- /nuxt_app/public/images/copter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/public/images/copter.png -------------------------------------------------------------------------------- /nuxt_app/public/images/girl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/public/images/girl.png -------------------------------------------------------------------------------- /nuxt_app/public/images/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/public/images/man.png -------------------------------------------------------------------------------- /nuxt_app/public/images/old_lady.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikitalogos/birthday_greetings_ai/9ea1e019fd1f64744b57ef169bc2b85d7548e269/nuxt_app/public/images/old_lady.png -------------------------------------------------------------------------------- /nuxt_app/public/semantic-ui/components/loader.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 2.5.0 - Loader 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.loader{display:none;position:absolute;top:50%;left:50%;margin:0;text-align:center;z-index:1000;transform:translateX(-50%) translateY(-50%)}.ui.loader:before{position:absolute;content:'';top:0;left:50%;width:100%;height:100%;border-radius:500rem;border:.2em solid rgba(0,0,0,.1)}.ui.loader:after{position:absolute;content:'';top:0;left:50%;width:100%;height:100%;-webkit-animation:loader .6s linear;animation:loader .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 transparent transparent;border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent}@-webkit-keyframes loader{from{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes loader{from{transform:rotate(0)}to{transform:rotate(360deg)}}.ui.mini.loader:after,.ui.mini.loader:before{width:1rem;height:1rem;margin:0 0 0 -.5rem}.ui.tiny.loader:after,.ui.tiny.loader:before{width:1.14285714rem;height:1.14285714rem;margin:0 0 0 -.57142857rem}.ui.small.loader:after,.ui.small.loader:before{width:1.71428571rem;height:1.71428571rem;margin:0 0 0 -.85714286rem}.ui.loader:after,.ui.loader:before{width:2.28571429rem;height:2.28571429rem;margin:0 0 0 -1.14285714rem}.ui.large.loader:after,.ui.large.loader:before{width:3.42857143rem;height:3.42857143rem;margin:0 0 0 -1.71428571rem}.ui.big.loader:after,.ui.big.loader:before{width:3.71428571rem;height:3.71428571rem;margin:0 0 0 -1.85714286rem}.ui.huge.loader:after,.ui.huge.loader:before{width:4.14285714rem;height:4.14285714rem;margin:0 0 0 -2.07142857rem}.ui.massive.loader:after,.ui.massive.loader:before{width:4.57142857rem;height:4.57142857rem;margin:0 0 0 -2.28571429rem}.ui.dimmer .loader{display:block}.ui.dimmer .ui.loader{color:rgba(255,255,255,.9)}.ui.dimmer .ui.loader:before{border-color:rgba(255,255,255,.15)}.ui.dimmer .ui.loader:after{border-color:#fff transparent transparent}.ui.inverted.dimmer .ui.loader{color:rgba(0,0,0,.87)}.ui.inverted.dimmer .ui.loader:before{border-color:rgba(0,0,0,.1)}.ui.inverted.dimmer .ui.loader:after{border-color:#767676 transparent transparent}.ui.text.loader{width:auto!important;height:auto!important;text-align:center;font-style:normal}.ui.indeterminate.loader:after{animation-direction:reverse;-webkit-animation-duration:1.2s;animation-duration:1.2s}.ui.loader.active,.ui.loader.visible{display:block}.ui.loader.disabled,.ui.loader.hidden{display:none}.ui.inverted.dimmer .ui.mini.loader,.ui.mini.loader{width:1rem;height:1rem;font-size:.78571429em}.ui.inverted.dimmer .ui.tiny.loader,.ui.tiny.loader{width:1.14285714rem;height:1.14285714rem;font-size:.85714286em}.ui.inverted.dimmer .ui.small.loader,.ui.small.loader{width:1.71428571rem;height:1.71428571rem;font-size:.92857143em}.ui.inverted.dimmer .ui.loader,.ui.loader{width:2.28571429rem;height:2.28571429rem;font-size:1em}.ui.inverted.dimmer .ui.large.loader,.ui.large.loader{width:3.42857143rem;height:3.42857143rem;font-size:1.14285714em}.ui.big.loader,.ui.inverted.dimmer .ui.big.loader{width:3.71428571rem;height:3.71428571rem;font-size:1.28571429em}.ui.huge.loader,.ui.inverted.dimmer .ui.huge.loader{width:4.14285714rem;height:4.14285714rem;font-size:1.42857143em}.ui.inverted.dimmer .ui.massive.loader,.ui.massive.loader{width:4.57142857rem;height:4.57142857rem;font-size:1.71428571em}.ui.mini.text.loader{min-width:1rem;padding-top:1.78571429rem}.ui.tiny.text.loader{min-width:1.14285714rem;padding-top:1.92857143rem}.ui.small.text.loader{min-width:1.71428571rem;padding-top:2.5rem}.ui.text.loader{min-width:2.28571429rem;padding-top:3.07142857rem}.ui.large.text.loader{min-width:3.42857143rem;padding-top:4.21428571rem}.ui.big.text.loader{min-width:3.71428571rem;padding-top:4.5rem}.ui.huge.text.loader{min-width:4.14285714rem;padding-top:4.92857143rem}.ui.massive.text.loader{min-width:4.57142857rem;padding-top:5.35714286rem}.ui.inverted.loader{color:rgba(255,255,255,.9)}.ui.inverted.loader:before{border-color:rgba(255,255,255,.15)}.ui.inverted.loader:after{border-top-color:#fff}.ui.inline.loader{position:relative;vertical-align:middle;margin:0;left:0;top:0;transform:none}.ui.inline.loader.active,.ui.inline.loader.visible{display:inline-block}.ui.centered.inline.loader.active,.ui.centered.inline.loader.visible{display:block;margin-left:auto;margin-right:auto} -------------------------------------------------------------------------------- /nuxt_app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /python_server/.dockerignore: -------------------------------------------------------------------------------- 1 | .mypy_cache/**/* 2 | fly.toml 3 | -------------------------------------------------------------------------------- /python_server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim-bullseye 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt requirements.txt 6 | RUN pip3 install -r requirements.txt 7 | 8 | COPY python_server python_server 9 | COPY setup.py setup.py 10 | RUN pip3 install . 11 | 12 | CMD [ "python3", "-m" , "python_server", "--host=0.0.0.0"] 13 | -------------------------------------------------------------------------------- /python_server/Makefile: -------------------------------------------------------------------------------- 1 | PYTHON=venv/bin/python3 2 | DIRS=python_server 3 | 4 | .PHONY: install_python 5 | install_python: 6 | sudo apt-get install python3.11 python3.11-venv python3.11-dev 7 | rm -r venv || true 8 | python3.11 -m venv venv 9 | venv/bin/pip install --upgrade pip 10 | venv/bin/pip install -r requirements.txt 11 | venv/bin/pip install -e . 12 | 13 | 14 | .PHONY: run_dev 15 | run_dev: 16 | ${PYTHON} -m python_server --dev 17 | 18 | .PHONY: run_prod 19 | run_prod: 20 | ${PYTHON} -m python_server 21 | 22 | 23 | .PHONY: deploy 24 | deploy: 25 | flyctl deploy 26 | 27 | .PHONY: check_server 28 | check_server: 29 | curl -X POST -H "Content-Type: application/json" -d '{}' https://birthday-greetings-ai.fly.dev/api/ping 30 | 31 | 32 | .PHONY: check 33 | check: 34 | ${PYTHON} -m flake8 --max-line-length 120 ${DIRS} 35 | 36 | ${PYTHON} -m mypy --install-types --non-interactive ${DIRS} > /dev/null 2>&1 || true 37 | ${PYTHON} -m mypy --ignore-missing-imports --explicit-package-bases --check-untyped-defs ${DIRS} 38 | 39 | .PHONY: format 40 | format: 41 | ${PYTHON} -m black -t py311 -l 120 ${DIRS} 42 | ${PYTHON} -m isort -l 120 ${DIRS} 43 | -------------------------------------------------------------------------------- /python_server/fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for birthday-greetings-ai on 2023-07-29T23:46:00+03:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = "birthday-greetings-ai" 7 | primary_region = "cdg" 8 | 9 | [http_service] 10 | internal_port = 8080 11 | force_https = true 12 | auto_stop_machines = true 13 | auto_start_machines = true 14 | min_machines_running = 1 15 | processes = ["app"] 16 | -------------------------------------------------------------------------------- /python_server/python_server/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import json 4 | import os 5 | import socket 6 | import traceback 7 | from typing import List 8 | 9 | import aiohttp_cors 10 | import replicate 11 | from aiohttp import web 12 | from cerberus import Validator 13 | 14 | 15 | class Server: 16 | def __init__(self, routes_post: dict, host: str, port: int, allowed_client_hosts: List[str]): 17 | self.routes_post = {k: self._make_async_route(*v) for k, v in routes_post.items()} 18 | self.host = host 19 | self.port = int(port) 20 | self.allowed_client_hosts = allowed_client_hosts 21 | 22 | def _make_async_route(self, func, request_validator): 23 | def exception_to_json(func): 24 | def wrapper(*args, **kwargs): 25 | try: 26 | return func(*args, **kwargs) 27 | except Exception as e: 28 | traceback.print_exc() 29 | return {"error": str(e)} 30 | 31 | return wrapper 32 | 33 | async def route(request): 34 | origin = request.headers.get("Origin") 35 | if origin not in self.allowed_client_hosts: 36 | print(f"invalid origin: {origin=}, {request.headers=}") 37 | return web.json_response({"error": "Invalid client host!"}) 38 | 39 | r_json = await request.json() 40 | 41 | if not request_validator.validate(r_json): 42 | errors_dict = request_validator.errors 43 | errors_str = json.dumps(errors_dict, indent=4) 44 | return web.json_response({"error": f"Request json validation error:\n{errors_str}"}) 45 | 46 | func_safe = exception_to_json(func) 47 | loop = asyncio.get_running_loop() 48 | response = await loop.run_in_executor(None, func_safe, r_json) 49 | return web.json_response(response) 50 | 51 | return route 52 | 53 | def _create_app(self): 54 | app = web.Application( 55 | client_max_size=int(1e8), 56 | ) 57 | cors = aiohttp_cors.setup( 58 | app, 59 | defaults={ 60 | host: aiohttp_cors.ResourceOptions( 61 | allow_credentials=True, 62 | expose_headers="*", 63 | allow_headers="*", 64 | ) 65 | for host in self.allowed_client_hosts 66 | }, 67 | ) 68 | 69 | for path, handler in self.routes_post.items(): 70 | resource = cors.add(app.router.add_resource(path)) 71 | cors.add(resource.add_route("POST", handler)) 72 | return app 73 | 74 | def serve_forever(self): 75 | app = self._create_app() 76 | web.run_app( 77 | app, 78 | host=self.host, 79 | port=self.port, 80 | access_log=None, # disable logs 81 | ) 82 | 83 | 84 | def chatbot(r_json): 85 | os.environ["REPLICATE_API_TOKEN"] = r_json["token"] 86 | 87 | user_prompt = r_json["prompt"] 88 | prompt = ( 89 | "You are a helpful assistant. You do not respond as 'User' or pretend to be 'User'. " 90 | "You only respond once as 'Assistant'.\n\n\n" 91 | f"User: {user_prompt}\n\n\n" 92 | "Assistant: " 93 | ) 94 | print(f"{prompt=}") 95 | 96 | resp = replicate.run( 97 | "a16z-infra/llama-2-13b-chat:d5da4236b006f967ceb7da037be9cfc3924b20d21fed88e1e94f19d56e2d3111", 98 | input=dict( 99 | prompt=prompt, 100 | temperature=0.5, 101 | top_p=0.9, 102 | max_length=1024, 103 | repetition_penalty=1, 104 | ), 105 | ) 106 | # for p in resp: 107 | # print(p, end="") 108 | resp_str = "".join(list(resp)) 109 | return dict(message=resp_str) 110 | 111 | 112 | def get_ip_in_localnet(): 113 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 114 | try: 115 | # doesn't even have to be reachable 116 | s.connect(("10.255.255.255", 1)) 117 | ip = s.getsockname()[0] 118 | except Exception: 119 | ip = "127.0.0.1" 120 | finally: 121 | s.close() 122 | return ip 123 | 124 | 125 | if __name__ == "__main__": 126 | parser = argparse.ArgumentParser() 127 | parser.add_argument("--host", type=str, default="0.0.0.0") 128 | parser.add_argument("--port", type=int, default=8080) 129 | parser.add_argument("--dev", action="store_true") 130 | args = parser.parse_args() 131 | 132 | allowed_client_hosts = ["https://logosnikita.com"] 133 | if args.dev: 134 | allowed_client_hosts.append("http://localhost:3000") 135 | allowed_client_hosts.append(f"http://{get_ip_in_localnet()}:3000") 136 | 137 | server = Server( 138 | routes_post={ 139 | "/api/ping": [ 140 | lambda r_json: r_json, 141 | Validator({}), 142 | ], 143 | "/api/chatbot": [ 144 | chatbot, 145 | Validator( 146 | { 147 | "token": {"type": "string", "required": True}, 148 | "prompt": {"type": "string", "required": True}, 149 | } 150 | ), 151 | ], 152 | }, 153 | host=args.host, 154 | port=args.port, 155 | allowed_client_hosts=allowed_client_hosts, 156 | ) 157 | server.serve_forever() 158 | -------------------------------------------------------------------------------- /python_server/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | aiohttp_cors 3 | replicate 4 | cerberus 5 | 6 | flake8 7 | mypy 8 | black 9 | isort -------------------------------------------------------------------------------- /python_server/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | # Minimal setup.py file for local installation of the package. 4 | setup( 5 | name='python_server', 6 | packages=find_packages(), 7 | zip_safe=False, 8 | ) -------------------------------------------------------------------------------- /training/Makefile: -------------------------------------------------------------------------------- 1 | PYTHON=../python_server/venv/bin/python3 2 | 3 | .PHONY: check 4 | check: 5 | cd ../python_server && make check DIRS=../training 6 | 7 | .PHONY: format 8 | format: 9 | cd ../python_server && make format DIRS=../training 10 | -------------------------------------------------------------------------------- /training/data.py: -------------------------------------------------------------------------------- 1 | hobbies = [ 2 | "Reading", 3 | "Traveling", 4 | "Painting", 5 | "Gardening", 6 | "Cooking", 7 | "Running", 8 | "Swimming", 9 | "Cycling", 10 | "Hiking", 11 | "Photography", 12 | "Dancing", 13 | "Writing", 14 | "Fishing", 15 | "Camping", 16 | "Yoga", 17 | "Skiing", 18 | "Surfing", 19 | "Rock Climbing", 20 | "Bird Watching", 21 | "Star Gazing", 22 | "Pottery", 23 | "Knitting", 24 | "Sculpture", 25 | "Archery", 26 | "Basketball", 27 | "Baseball", 28 | "Soccer", 29 | "Football", 30 | "Rugby", 31 | "Sailing", 32 | "Baking", 33 | "Piano", 34 | "Guitar", 35 | "Violin", 36 | "Drums", 37 | "Harmonica", 38 | "Saxophone", 39 | "Beatboxing", 40 | "Singing", 41 | "Storytelling", 42 | "Magic Tricks", 43 | "Origami", 44 | "Chess", 45 | "Puzzles", 46 | "Board Games", 47 | "Video Games", 48 | "Movies", 49 | "Music", 50 | "Podcasts", 51 | "Volunteering", 52 | "Blogging", 53 | "Vlogging", 54 | "Coding", 55 | "Meditation", 56 | "Weightlifting", 57 | "Boxing", 58 | "Martial Arts", 59 | "Ballet", 60 | "Theater", 61 | "Drama", 62 | "Drawing", 63 | "Graphic Design", 64 | "Interior Design", 65 | "Fashion", 66 | "Makeup", 67 | "Tattoos", 68 | "Piercing", 69 | "Wine Tasting", 70 | "Beer Brewing", 71 | "Coffee Roasting", 72 | "Collecting", 73 | "Shopping", 74 | "Thrifting", 75 | "Restoration", 76 | "DIY", 77 | "Home Improvement", 78 | "Jewelry Making", 79 | "Tattooing", 80 | "Card Games", 81 | "Stand-Up Comedy", 82 | "Impersonations", 83 | "Public Speaking", 84 | "Creative Writing", 85 | "Radio Broadcasting", 86 | "Podcast Hosting", 87 | "Social Media", 88 | "Networking", 89 | "Investing", 90 | "Entrepreneurship", 91 | "Mentoring", 92 | "Modeling", 93 | "Youtubing", 94 | "Streaming", 95 | "Astrology", 96 | "Tarot Reading", 97 | "Mysticism", 98 | "Religion", 99 | "Spirituality", 100 | "Philosophy", 101 | "Political Debates", 102 | "Languages", 103 | "Culture Studies", 104 | "History", 105 | "Anthropology", 106 | "Archaeology", 107 | "Paleontology", 108 | "Geology", 109 | "Meteorology", 110 | "Astronomy", 111 | "Biology", 112 | "Chemistry", 113 | "Physics", 114 | "Mathematics", 115 | "Cooking", 116 | "Nutrition", 117 | "Dieting", 118 | "Veterinary", 119 | "Botany", 120 | "Environmentalism", 121 | "Recycling", 122 | "Composting", 123 | "Zero Waste", 124 | "Minimalism", 125 | "Organizing", 126 | "Productivity", 127 | "Life Hacks", 128 | "Life Coaching", 129 | "Fitness", 130 | "Health", 131 | "Beauty", 132 | "Hygiene", 133 | "Aromatherapy", 134 | "Holistic Health", 135 | "Natural Remedies", 136 | "Medical Research", 137 | "Forensics", 138 | "Criminology", 139 | "Psychology", 140 | "Sociology", 141 | "Human Rights", 142 | "Social Justice", 143 | "Activism", 144 | "Charity", 145 | "Humanitarianism", 146 | ] 147 | 148 | comments = [ 149 | "Cheerful and joyous message.", 150 | "Message in a funny style.", 151 | "Emphasize strong friendship.", 152 | "Focus on bright future.", 153 | "Professional style message.", 154 | "Include wisdom quote.", 155 | "Fun and excitement for a child.", 156 | "Appreciate their qualities.", 157 | "For a travel enthusiast.", 158 | "For a sports lover.", 159 | "Mention their pet.", 160 | "Poetic style message.", 161 | "Acknowledge their impact in your life.", 162 | "For a fantasy book lover.", 163 | "For a nature lover.", 164 | "Highlight their courage.", 165 | "Artistic style message.", 166 | "For a foodie.", 167 | "Mention their passion for music.", 168 | "For a movie enthusiast.", 169 | "Message with a tone of adventure.", 170 | "Acknowledge their kindness.", 171 | "Elegant style message.", 172 | "For a bookworm.", 173 | "For a pet lover.", 174 | "Highlight their humor.", 175 | "Romantic style message.", 176 | "For a photography enthusiast.", 177 | "Mention their love for dance.", 178 | "For a coffee lover.", 179 | "Message with a tone of mystery.", 180 | "Acknowledge their intelligence.", 181 | "Rustic style message.", 182 | "For a history buff.", 183 | "For a gamer.", 184 | "Highlight their generosity.", 185 | "Modern style message.", 186 | "For a fashion enthusiast.", 187 | "Mention their love for gardening.", 188 | "For a tea lover.", 189 | "Message with a tone of nostalgia.", 190 | "Acknowledge their creativity.", 191 | "Vintage style message.", 192 | "For a science enthusiast.", 193 | "For a tech lover.", 194 | "Highlight their leadership.", 195 | "Sophisticated style message.", 196 | "For a fitness enthusiast.", 197 | "Mention their love for the outdoors.", 198 | "For a wine enthusiast.", 199 | ] 200 | -------------------------------------------------------------------------------- /training/generate_prompt.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import random 3 | from datetime import datetime, timedelta 4 | from pprint import pprint 5 | 6 | import numpy as np 7 | from data import comments, hobbies 8 | from faker import Faker 9 | 10 | language_locale_mapping = { 11 | "Arabic": "ar_AA", 12 | "Chinese": "zh_CN", 13 | "Danish": "da_DK", 14 | "Dutch": "nl_NL", 15 | "English": "en_US", 16 | "French": "fr_FR", 17 | "German": "de_DE", 18 | "Hebrew": "he_IL", 19 | "Hindi": "hi_IN", 20 | "Italian": "it_IT", 21 | "Japanese": "ja_JP", 22 | "Korean": "ko_KR", 23 | "Norwegian": "no_NO", 24 | "Polish": "pl_PL", 25 | "Portuguese": "pt_PT", 26 | "Russian": "ru_RU", 27 | "Swedish": "sv_SE", 28 | "Thai": "th_TH", 29 | "Turkish": "tr_TR", 30 | "Spanish": "es_ES", 31 | } 32 | locales = list(language_locale_mapping.values()) 33 | 34 | faker_langs = Faker(locales) 35 | faker_no_langs = Faker() 36 | 37 | 38 | def generate_random_name(): 39 | choice = np.random.choice( 40 | ["name", "first_name", "last_name", "1_word", "2_words", "3_words", "4_words"], 41 | p=[0.5, 0.15, 0.15, 0.1, 0.05, 0.025, 0.025], 42 | ) 43 | if choice == "name": 44 | name = faker_langs.name() 45 | elif choice == "first_name": 46 | name = faker_langs.first_name() 47 | elif choice == "last_name": 48 | name = faker_langs.last_name() 49 | else: 50 | word_count = int(choice.split("_")[0]) 51 | words = faker_langs.words(nb=word_count) 52 | # Capitalize the first letter of each word 53 | name = " ".join(word.capitalize() for word in words) 54 | return name 55 | 56 | 57 | def generate_random_date(): 58 | max_age = int( 59 | np.random.choice([30, 50, 100]) 60 | ) # 0-30 - 33% * (1 + 0.6 + 0.3), 31-50 - 33% * (0.4 + 0.2) 51-100 - 33% * (0.5) 61 | 62 | end_date = datetime.now() 63 | start_date = end_date - timedelta(days=365 * max_age) 64 | 65 | time_between_dates = end_date - start_date 66 | days_between_dates = time_between_dates.days 67 | 68 | random_number_of_days = random.randrange(days_between_dates) 69 | random_date = start_date + timedelta(days=random_number_of_days) 70 | random_date_str = random_date.strftime("%Y-%m-%d") 71 | return random_date_str 72 | 73 | 74 | def generate_random_boolean(true_prob): 75 | return np.random.choice([True, False], p=[true_prob, 1 - true_prob]) 76 | 77 | 78 | def generate_wishes_list(): 79 | wishes = [ 80 | "Health", 81 | "Wealth", 82 | "Happiness", 83 | "Success", 84 | "Love", 85 | "Adventure", 86 | "Wisdom", 87 | "Peace", 88 | "Creativity", 89 | "Growth", 90 | "Friendship", 91 | "Courage", 92 | "Opportunities", 93 | "Balance", 94 | "Longevity", 95 | ] 96 | choice = np.random.choice(["empty", "1-5_wishes", "6-10_wishes"], p=[0.2, 0.6, 0.2]) 97 | if choice == "empty": 98 | wishes_list = [] 99 | elif choice == "1-5_wishes": 100 | wishes_list = random.sample(wishes, np.random.randint(1, 6)) 101 | else: # '6-10_wishes' 102 | wishes_list = random.sample(wishes, np.random.randint(6, 11)) 103 | return wishes_list 104 | 105 | 106 | def generate_random_language(): 107 | return np.random.choice(list(language_locale_mapping.keys())) 108 | 109 | 110 | def generate_random_gender(): 111 | return np.random.choice( 112 | [None, "male", "female", "non-binary", "prefer not to say"], 113 | p=[0.5, 0.2, 0.2, 0.05, 0.05], 114 | ) 115 | 116 | 117 | def generate_random_relationship(): 118 | options = [ 119 | None, 120 | "child", 121 | "parent", 122 | "partner", 123 | "spouse", 124 | "sibling", 125 | "grandparent", 126 | "cousin", 127 | "friend", 128 | "colleague", 129 | "mentor", 130 | "boss", 131 | "acquaintance", 132 | ] 133 | probabilities = [0.5] + [0.5 / (len(options) - 1)] * (len(options) - 1) 134 | return np.random.choice(options, p=probabilities) 135 | 136 | 137 | def generate_random_profession(): 138 | return faker_no_langs.job() if np.random.choice([True, False]) else None 139 | 140 | 141 | def generate_random_hobby(): 142 | return np.random.choice(hobbies) if np.random.choice([True, False]) else None 143 | 144 | 145 | def generate_random_use_emojis(): 146 | return np.random.choice( 147 | [None, "no", "few", "more", "lots of"], 148 | p=[0.5, 0.1, 0.2, 0.1, 0.1], 149 | ) 150 | 151 | 152 | def generate_random_greeting_length(): 153 | return np.random.choice( 154 | [None, "short (<50 words)", "medium (~100 words)", "long (>150 words)"], 155 | p=[0.5, 0.15, 0.2, 0.15], 156 | ) 157 | 158 | 159 | def generate_random_style(): 160 | options = [ 161 | "informal", 162 | "formal", 163 | "funny", 164 | "inspirational", 165 | "romantic", 166 | "energetic", 167 | "calm", 168 | "sarcastic", 169 | "poetic", 170 | "philosophical", 171 | "spiritual", 172 | "minimalistic", 173 | ] 174 | return np.random.choice(options) if np.random.choice([True, False]) else None 175 | 176 | 177 | def generate_random_theme(): 178 | options = [ 179 | "Anime", 180 | "Cyberpunk", 181 | "Fairy Tale", 182 | "Fantasy", 183 | "Futuristic", 184 | "Gothic", 185 | "Horror", 186 | "Medieval", 187 | "Mermaid", 188 | "Nature", 189 | "Pirate", 190 | "Princess", 191 | "Retro", 192 | "Samurai", 193 | "Sci-Fi", 194 | "Space", 195 | "Sport", 196 | "Steampunk", 197 | "Travel", 198 | "Unicorn", 199 | "Victorian", 200 | "Viking", 201 | "Western", 202 | ] 203 | return np.random.choice(options) if np.random.choice([True, False]) else None 204 | 205 | 206 | def generate_random_comment(): 207 | return np.random.choice(comments) if np.random.choice([True, False]) else None 208 | 209 | 210 | @dataclasses.dataclass 211 | class PromptParams: 212 | name: str 213 | # date 214 | date_of_birth: str 215 | use_age: bool 216 | use_zodiac_sign: bool 217 | # content 218 | what_to_wish: list 219 | target_language: str 220 | # personal info 221 | gender: str 222 | relationship: str 223 | profession: str 224 | hobby: str 225 | # style 226 | use_emojis: int 227 | greeting_length: str 228 | greeting_style: str 229 | theme: str 230 | use_quotation: bool 231 | use_affirmation: bool 232 | comment: str 233 | 234 | @property 235 | def age(self): 236 | if not self.date_of_birth: 237 | return None 238 | dob = datetime.strptime(self.date_of_birth, "%Y-%m-%d") 239 | now = datetime.now() 240 | was_birthday_this_year = (now.month, now.day) >= (dob.month, dob.day) 241 | return now.year - dob.year - int(not was_birthday_this_year) 242 | 243 | _ZODIAC_SIGNS = [ 244 | (("Capricorn", "♑"), (1, 19)), 245 | (("Aquarius", "♒"), (2, 18)), 246 | (("Pisces", "♓"), (3, 20)), 247 | (("Aries", "♈"), (4, 19)), 248 | (("Taurus", "♉"), (5, 20)), 249 | (("Gemini", "♊"), (6, 20)), 250 | (("Cancer", "♋"), (7, 22)), 251 | (("Leo", "♌"), (8, 22)), 252 | (("Virgo", "♍"), (9, 22)), 253 | (("Libra", "♎"), (10, 22)), 254 | (("Scorpio", "♏"), (11, 21)), 255 | (("Sagittarius", "♐"), (12, 21)), 256 | (("Capricorn", "♑"), (12, 31)), # to handle the case of dates after Dec 21 257 | ] 258 | 259 | @property 260 | def zodiac_sign(self): 261 | if not self.date_of_birth: 262 | return None 263 | dob = datetime.strptime(self.date_of_birth, "%Y-%m-%d") 264 | month, day = dob.month, dob.day 265 | for sign, (m, d) in self._ZODIAC_SIGNS: 266 | if (month, day) <= (m, d): 267 | return f"{sign[0]} ({sign[1]})" 268 | raise ValueError("Date is invalid. That's weird. Shouldn't get here") 269 | 270 | 271 | def get_random_params_item(): 272 | return PromptParams( 273 | name=generate_random_name(), 274 | # date 275 | date_of_birth=generate_random_date(), 276 | use_age=generate_random_boolean(0.5), 277 | use_zodiac_sign=generate_random_boolean(0.5), 278 | # content 279 | what_to_wish=generate_wishes_list(), 280 | target_language=generate_random_language(), 281 | # personal details 282 | gender=generate_random_gender(), 283 | relationship=generate_random_relationship(), 284 | profession=generate_random_profession(), 285 | hobby=generate_random_hobby(), 286 | # style 287 | use_emojis=generate_random_use_emojis(), 288 | greeting_length=generate_random_greeting_length(), 289 | greeting_style=generate_random_style(), 290 | theme=generate_random_theme(), 291 | use_quotation=generate_random_boolean(0.33), 292 | use_affirmation=generate_random_boolean(0.33), 293 | comment=generate_random_comment(), 294 | ) 295 | 296 | 297 | def generate_prompt(p: PromptParams): 298 | prompt = f"I want to congratulate {p.name} on their birthday. Please create a birthday greeting.\n" 299 | 300 | if p.date_of_birth: 301 | prompt += f"{p.name} was born on {p.date_of_birth}.\n" 302 | 303 | if p.use_age: 304 | prompt += f"Their age is {p.age}.\n" 305 | else: 306 | prompt += f"Please do not mention {p.name}'s age in the greeting.\n" 307 | 308 | if p.use_zodiac_sign: 309 | prompt += f"Their zodiac sign is {p.zodiac_sign}.\n" 310 | else: 311 | prompt += f"Please do not mention {p.name}'s zodiac sign in the greeting.\n" 312 | prompt += "\n" 313 | 314 | if p.gender: 315 | prompt += f"{p.name} identifies as {p.gender}.\n" 316 | 317 | if p.relationship: 318 | prompt += f"I am {p.name}'s {p.relationship}.\n" 319 | 320 | if p.profession: 321 | prompt += f"{p.name} is a {p.profession}.\n" 322 | 323 | if p.hobby: 324 | prompt += f"{p.name} enjoys {p.hobby}.\n" 325 | 326 | prompt += "\n" 327 | 328 | if p.what_to_wish and len(p.what_to_wish) > 0: 329 | prompt += "Please include the following wishes in the greeting: " 330 | prompt += ", ".join([f'"{wish}"' for wish in p.what_to_wish]) 331 | prompt += "\n" 332 | 333 | prompt += f"The birthday greeting should be in {p.target_language} language.\n" 334 | prompt += "\n" 335 | 336 | if p.use_emojis: 337 | prompt += f"Please include {p.use_emojis} emojis in the greeting.\n" 338 | 339 | if p.greeting_length: 340 | prompt += f"The greeting should be {p.greeting_length}.\n" 341 | 342 | if p.greeting_style: 343 | prompt += f"Please use a {p.greeting_style} style for the greeting.\n" 344 | 345 | if p.theme: 346 | prompt += f"Please incorporate a {p.theme} theme into the greeting.\n" 347 | 348 | if p.use_quotation: 349 | prompt += "Please include a relevant famous quotation in the greeting.\n" 350 | 351 | if p.use_affirmation: 352 | prompt += "Please include a motivating affirmation in the greeting.\n" 353 | 354 | prompt += "\n" 355 | 356 | if p.comment: 357 | prompt += f"Additional context: {p.comment}\n" 358 | 359 | prompt = prompt.replace("\n\n\n", "\n\n").strip() 360 | return prompt 361 | 362 | 363 | def get_random_prompt(): 364 | params = get_random_params_item() 365 | return generate_prompt(params) 366 | 367 | 368 | if __name__ == "__main__": 369 | for i in range(100): 370 | print(generate_random_date()) 371 | exit(0) 372 | 373 | params = get_random_params_item() 374 | pprint(params) 375 | print() 376 | prompt = generate_prompt(params) 377 | print(prompt) 378 | -------------------------------------------------------------------------------- /training/get_training_data_from_gpt4.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | import traceback 5 | 6 | import openai 7 | import yaml 8 | from generate_prompt import get_random_prompt 9 | from tqdm import tqdm 10 | 11 | project_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 12 | 13 | 14 | with open(f"{project_root}/auth/.openai.yaml") as f: 15 | data = yaml.safe_load(f) 16 | openai.organization = data["organization"] 17 | openai.api_key = data["api_key"] 18 | 19 | 20 | def ask_chatgpt(prompt) -> str: 21 | completion = openai.ChatCompletion.create( 22 | model="gpt-3.5-turbo", 23 | messages=[{"role": "user", "content": prompt}], 24 | ) 25 | result = completion.choices[0].message.content 26 | return result 27 | 28 | 29 | def write_jsonl(prompt: str, completion: str, filename: str) -> None: 30 | with open(filename, "a") as f: 31 | json_obj = {"prompt": prompt, "completion": completion} 32 | json_line = json.dumps(json_obj) + "\n" 33 | f.write(json_line) 34 | 35 | 36 | if __name__ == "__main__": 37 | parser = argparse.ArgumentParser() 38 | parser.add_argument("num", type=int, help="Number of training data to generate") 39 | args = parser.parse_args() 40 | 41 | for i in tqdm(range(args.num), desc="Generating training data"): 42 | prompt = get_random_prompt() 43 | print(prompt) 44 | print("\n~~~~\n") 45 | 46 | try: 47 | completion = ask_chatgpt(prompt) 48 | except Exception: 49 | traceback.print_exc() 50 | continue 51 | print(completion) 52 | 53 | write_jsonl(prompt, completion, "training_data.jsonl") 54 | -------------------------------------------------------------------------------- /training/requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | numpy 3 | faker --------------------------------------------------------------------------------