├── .gitignore ├── novela-gui ├── src │ ├── boot │ │ ├── .gitkeep │ │ ├── i18n.js │ │ └── axios.js │ ├── css │ │ ├── app.scss │ │ └── quasar.variables.scss │ ├── i18n │ │ ├── index.js │ │ └── en-US │ │ │ └── index.js │ ├── assets │ │ ├── logo-3.png │ │ ├── logo-removebg.png │ │ └── quasar-logo-vertical.svg │ ├── App.vue │ ├── stores │ │ ├── store-flag.d.ts │ │ ├── example-store.js │ │ └── index.js │ ├── pages │ │ ├── ErrorNotFound.vue │ │ ├── SettingsPage.vue │ │ ├── IndexPage.vue │ │ ├── InspirationsPage.vue │ │ ├── BooksPage.vue │ │ └── BookEditorPage.vue │ ├── router │ │ ├── routes.js │ │ └── index.js │ ├── components │ │ └── EssentialLink.vue │ ├── index.template.html │ └── layouts │ │ └── MainLayout.vue ├── .dockerignore ├── quasar.extensions.json ├── .npmrc ├── public │ ├── favicon.ico │ └── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ └── favicon-128x128.png ├── .eslintignore ├── .editorconfig ├── .postcssrc.js ├── Dockerfile ├── babel.config.js ├── .vscode │ ├── extensions.json │ └── settings.json ├── .gitignore ├── README.md ├── jsconfig.json ├── nginx │ └── nginx.conf ├── package.json ├── .eslintrc.js └── quasar.config.js ├── novela_api ├── novela │ ├── __init__.py │ ├── config │ │ ├── __init__.py │ │ └── config.py │ ├── aiconnector │ │ ├── __init__.py │ │ └── aiconnector.py │ ├── bookclient.py │ └── main.py ├── start.sh ├── requirements.txt └── Dockerfile ├── homepage ├── docs │ ├── .meta.yml │ ├── assets │ │ ├── scr1.png │ │ ├── scr2.png │ │ ├── favicon.png │ │ └── images │ │ │ └── logo-3.png │ ├── index.md │ ├── getting-started.md │ └── features.md ├── .overrides │ ├── main.html │ ├── partials │ │ └── actions.html │ ├── home.html │ └── assets │ │ ├── stylesheets │ │ ├── custom.9097afc2.min.css.map │ │ └── custom.9097afc2.min.css │ │ └── javascripts │ │ └── custom.bb97d007.min.js └── mkdocs.yml ├── assets ├── logo ├── logo-2 ├── logo.png ├── logo-3.png ├── logo-3.xcf ├── readme.png └── logo-removebg.png ├── .github └── workflows │ ├── docker-image-master.yml │ └── docker-image.yml ├── docker-compose.yml ├── README.md ├── docker-compose-with-auth.yml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /novela-gui/src/boot/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /novela_api/novela/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /novela-gui/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /novela_api/novela/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /homepage/docs/.meta.yml: -------------------------------------------------------------------------------- 1 | ᴴₒᴴₒᴴₒ: true 2 | -------------------------------------------------------------------------------- /novela_api/novela/aiconnector/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /novela-gui/src/css/app.scss: -------------------------------------------------------------------------------- 1 | // app global css in SCSS form 2 | -------------------------------------------------------------------------------- /assets/logo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/assets/logo -------------------------------------------------------------------------------- /assets/logo-2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/assets/logo-2 -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/assets/logo.png -------------------------------------------------------------------------------- /novela_api/start.sh: -------------------------------------------------------------------------------- 1 | uvicorn novela.main:app --reload --port 8000 --host 0.0.0.0 2 | -------------------------------------------------------------------------------- /assets/logo-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/assets/logo-3.png -------------------------------------------------------------------------------- /assets/logo-3.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/assets/logo-3.xcf -------------------------------------------------------------------------------- /assets/readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/assets/readme.png -------------------------------------------------------------------------------- /assets/logo-removebg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/assets/logo-removebg.png -------------------------------------------------------------------------------- /novela-gui/quasar.extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "@quasar/qmarkdown": { 3 | "import_md": true 4 | } 5 | } -------------------------------------------------------------------------------- /novela_api/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.88.0 2 | immudb-py==1.4.0 3 | uvicorn==0.20.0 4 | openai==0.25.0 -------------------------------------------------------------------------------- /homepage/docs/assets/scr1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/homepage/docs/assets/scr1.png -------------------------------------------------------------------------------- /homepage/docs/assets/scr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/homepage/docs/assets/scr2.png -------------------------------------------------------------------------------- /novela-gui/.npmrc: -------------------------------------------------------------------------------- 1 | # pnpm-related options 2 | shamefully-hoist=true 3 | strict-peer-dependencies=false 4 | -------------------------------------------------------------------------------- /novela-gui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/novela-gui/public/favicon.ico -------------------------------------------------------------------------------- /novela-gui/src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import enUS from './en-US' 2 | 3 | export default { 4 | 'en-US': enUS 5 | } 6 | -------------------------------------------------------------------------------- /homepage/docs/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/homepage/docs/assets/favicon.png -------------------------------------------------------------------------------- /novela-gui/src/assets/logo-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/novela-gui/src/assets/logo-3.png -------------------------------------------------------------------------------- /homepage/docs/assets/images/logo-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/homepage/docs/assets/images/logo-3.png -------------------------------------------------------------------------------- /novela-gui/src/assets/logo-removebg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/novela-gui/src/assets/logo-removebg.png -------------------------------------------------------------------------------- /homepage/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | template: home.html 3 | title: Welcome 4 | ᴴₒᴴₒᴴₒ: true 5 | --- 6 | 7 | Welcome to novela.ink 8 | -------------------------------------------------------------------------------- /novela-gui/public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/novela-gui/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /novela-gui/public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/novela-gui/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /novela-gui/public/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/novela-gui/public/icons/favicon-96x96.png -------------------------------------------------------------------------------- /novela-gui/public/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razikus/novela/HEAD/novela-gui/public/icons/favicon-128x128.png -------------------------------------------------------------------------------- /novela-gui/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /src-bex/www 3 | /src-capacitor 4 | /src-cordova 5 | /.quasar 6 | /node_modules 7 | .eslintrc.js 8 | babel.config.js 9 | -------------------------------------------------------------------------------- /novela-gui/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /novela-gui/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /novela-gui/src/i18n/en-US/index.js: -------------------------------------------------------------------------------- 1 | // This is just an example, 2 | // so you can safely delete all default props below 3 | 4 | export default { 5 | failed: 'Action failed', 6 | success: 'Action was successful' 7 | } 8 | -------------------------------------------------------------------------------- /novela-gui/.postcssrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://github.com/michael-ciniawsky/postcss-load-config 3 | 4 | module.exports = { 5 | plugins: [ 6 | // to edit target browsers: use "browserslist" field in package.json 7 | require('autoprefixer') 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /novela-gui/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM node:14 as builder 3 | COPY . /app 4 | WORKDIR /app 5 | RUN yarn && yarn global add @quasar/cli 6 | RUN quasar build -m spa 7 | 8 | FROM nginx:1.21.6 as runner 9 | COPY --from=builder /app/dist/spa /usr/share/nginx/html 10 | COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf 11 | EXPOSE 80 -------------------------------------------------------------------------------- /novela-gui/babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | module.exports = api => { 4 | return { 5 | presets: [ 6 | [ 7 | '@quasar/babel-preset-app', 8 | api.caller(caller => caller && caller.target === 'node') 9 | ? { targets: { node: 'current' } } 10 | : {} 11 | ] 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /novela-gui/src/stores/store-flag.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 3 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 4 | import "quasar/dist/types/feature-flag"; 5 | 6 | declare module "quasar/dist/types/feature-flag" { 7 | interface QuasarFeatureFlags { 8 | store: true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /novela-gui/src/stores/example-store.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useCounterStore = defineStore('counter', { 4 | state: () => ({ 5 | counter: 0, 6 | }), 7 | getters: { 8 | doubleCount: (state) => state.counter * 2, 9 | }, 10 | actions: { 11 | increment() { 12 | this.counter++; 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /novela-gui/src/boot/i18n.js: -------------------------------------------------------------------------------- 1 | import { boot } from 'quasar/wrappers' 2 | import { createI18n } from 'vue-i18n' 3 | import messages from 'src/i18n' 4 | 5 | export default boot(({ app }) => { 6 | const i18n = createI18n({ 7 | locale: 'en-US', 8 | globalInjection: true, 9 | messages 10 | }) 11 | 12 | // Set i18n instance on app 13 | app.use(i18n) 14 | }) 15 | -------------------------------------------------------------------------------- /novela_api/novela/config/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | STATICPREFIX=os.environ.get("STATICPREFIX", "/static/") 4 | STARTING_API_KEY=os.environ.get("STARTING_API_KEY", ":)") 5 | DB_URL=os.environ.get("DB_URL", "localhost:3322") 6 | DB_USER=os.environ.get("DB_USER", "immudb") 7 | DB_PASS=os.environ.get("DB_PASS", "immudb") 8 | DB_NAME=os.environ.get("DB_NAME","defaultdb") 9 | STATEFILE=os.environ.get("STATEFILE", None) -------------------------------------------------------------------------------- /homepage/.overrides/main.html: -------------------------------------------------------------------------------- 1 | {#- 2 | This file was automatically generated - do not edit 3 | -#} 4 | {% extends "base.html" %} 5 | {% block extrahead %} 6 | 7 | {% endblock %} 8 | {% block scripts %} 9 | {{ super() }} 10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /novela-gui/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "editorconfig.editorconfig", 6 | "vue.volar", 7 | "wayou.vscode-todo-highlight" 8 | ], 9 | "unwantedRecommendations": [ 10 | "octref.vetur", 11 | "hookyqr.beautify", 12 | "dbaeumer.jshint", 13 | "ms-vscode.vscode-typescript-tslint-plugin" 14 | ] 15 | } -------------------------------------------------------------------------------- /novela-gui/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.bracketPairColorization.enabled": true, 3 | "editor.guides.bracketPairs": true, 4 | "editor.formatOnSave": true, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.codeActionsOnSave": [ 7 | "source.fixAll.eslint" 8 | ], 9 | "eslint.validate": [ 10 | "javascript", 11 | "javascriptreact", 12 | "typescript", 13 | "vue" 14 | ] 15 | } -------------------------------------------------------------------------------- /novela-gui/src/stores/index.js: -------------------------------------------------------------------------------- 1 | import { store } from 'quasar/wrappers' 2 | import { createPinia } from 'pinia' 3 | 4 | /* 5 | * If not building with SSR mode, you can 6 | * directly export the Store instantiation; 7 | * 8 | * The function below can be async too; either use 9 | * async/await or return a Promise which resolves 10 | * with the Store instance. 11 | */ 12 | 13 | export default store((/* { ssrContext } */) => { 14 | const pinia = createPinia() 15 | 16 | // You can add Pinia plugins here 17 | // pinia.use(SomePiniaPlugin) 18 | 19 | return pinia 20 | }) 21 | -------------------------------------------------------------------------------- /novela-gui/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | 5 | # Quasar core related directories 6 | .quasar 7 | /dist 8 | 9 | # Cordova related directories and files 10 | /src-cordova/node_modules 11 | /src-cordova/platforms 12 | /src-cordova/plugins 13 | /src-cordova/www 14 | 15 | # Capacitor related directories and files 16 | /src-capacitor/www 17 | /src-capacitor/node_modules 18 | 19 | # BEX related directories and files 20 | /src-bex/www 21 | /src-bex/js/core 22 | 23 | # Log files 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Editor directories and files 29 | .idea 30 | *.suo 31 | *.ntvs* 32 | *.njsproj 33 | *.sln 34 | -------------------------------------------------------------------------------- /homepage/.overrides/partials/actions.html: -------------------------------------------------------------------------------- 1 | {#- 2 | This file was automatically generated - do not edit 3 | -#} 4 | {% if page.edit_url %} 5 | {% set edit = "https://github.com/squidfunk/mkdocs-material/edit" %} 6 | {% set view = "https://raw.githubusercontent.com/squidfunk/mkdocs-material" %} 7 | 8 | {% include ".icons/material/file-edit-outline.svg" %} 9 | 10 | 11 | {% include ".icons/material/file-eye-outline.svg" %} 12 | 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /novela-gui/README.md: -------------------------------------------------------------------------------- 1 | # Novela (novela-gui) 2 | 3 | Novela Ink 4 | 5 | ## Install the dependencies 6 | ```bash 7 | yarn 8 | # or 9 | npm install 10 | ``` 11 | 12 | ### Start the app in development mode (hot-code reloading, error reporting, etc.) 13 | ```bash 14 | quasar dev 15 | ``` 16 | 17 | 18 | ### Lint the files 19 | ```bash 20 | yarn lint 21 | # or 22 | npm run lint 23 | ``` 24 | 25 | 26 | ### Format the files 27 | ```bash 28 | yarn format 29 | # or 30 | npm run format 31 | ``` 32 | 33 | 34 | 35 | ### Build the app for production 36 | ```bash 37 | quasar build 38 | ``` 39 | 40 | ### Customize the configuration 41 | See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js). 42 | -------------------------------------------------------------------------------- /novela-gui/src/pages/ErrorNotFound.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 32 | -------------------------------------------------------------------------------- /novela-gui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "src/*": [ 6 | "src/*" 7 | ], 8 | "app/*": [ 9 | "*" 10 | ], 11 | "components/*": [ 12 | "src/components/*" 13 | ], 14 | "layouts/*": [ 15 | "src/layouts/*" 16 | ], 17 | "pages/*": [ 18 | "src/pages/*" 19 | ], 20 | "assets/*": [ 21 | "src/assets/*" 22 | ], 23 | "boot/*": [ 24 | "src/boot/*" 25 | ], 26 | "stores/*": [ 27 | "src/stores/*" 28 | ], 29 | "vue$": [ 30 | "node_modules/vue/dist/vue.runtime.esm-bundler.js" 31 | ] 32 | } 33 | }, 34 | "exclude": [ 35 | "dist", 36 | ".quasar", 37 | "node_modules" 38 | ] 39 | } -------------------------------------------------------------------------------- /novela-gui/src/router/routes.js: -------------------------------------------------------------------------------- 1 | 2 | const routes = [ 3 | { 4 | path: '/', 5 | component: () => import('layouts/MainLayout.vue'), 6 | children: [ 7 | { path: '', component: () => import('pages/IndexPage.vue') }, 8 | { path: 'books', component: () => import('pages/BooksPage.vue') }, 9 | { path: 'book/:id', component: () => import('pages/BookEditorPage.vue') }, 10 | { path: 'inspire', component: () => import('pages/InspirationsPage.vue') }, 11 | { path: 'settings', component: () => import('pages/SettingsPage.vue') } 12 | ] 13 | }, 14 | 15 | // Always leave this as last one, 16 | // but you can also remove it 17 | { 18 | path: '/:catchAll(.*)*', 19 | component: () => import('pages/ErrorNotFound.vue') 20 | } 21 | ] 22 | 23 | export default routes 24 | -------------------------------------------------------------------------------- /novela-gui/src/css/quasar.variables.scss: -------------------------------------------------------------------------------- 1 | // Quasar SCSS (& Sass) Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Sass/SCSS variables found in Quasar's source Sass/SCSS files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // Your own variables (that are declared here) and Quasar's own 9 | // ones will be available out of the box in your .vue/.scss/.sass files 10 | 11 | // It's highly recommended to change the default colors 12 | // to match your app's branding. 13 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 14 | 15 | $primary : #0e6975; 16 | $secondary : #0277bd; 17 | $accent : #5841ab; 18 | 19 | $dark : #1D1D1D; 20 | $dark-page : #121212; 21 | 22 | $positive : #21BA45; 23 | $negative : #C10015; 24 | $info : #31CCEC; 25 | $warning : #F2C037; 26 | -------------------------------------------------------------------------------- /novela-gui/src/components/EssentialLink.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 50 | -------------------------------------------------------------------------------- /novela-gui/src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /novela-gui/src/router/index.js: -------------------------------------------------------------------------------- 1 | import { route } from 'quasar/wrappers' 2 | import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router' 3 | import routes from './routes' 4 | 5 | /* 6 | * If not building with SSR mode, you can 7 | * directly export the Router instantiation; 8 | * 9 | * The function below can be async too; either use 10 | * async/await or return a Promise which resolves 11 | * with the Router instance. 12 | */ 13 | 14 | export default route(function (/* { store, ssrContext } */) { 15 | const createHistory = process.env.SERVER 16 | ? createMemoryHistory 17 | : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory) 18 | 19 | const Router = createRouter({ 20 | scrollBehavior: () => ({ left: 0, top: 0 }), 21 | routes, 22 | 23 | // Leave this as is and make changes in quasar.conf.js instead! 24 | // quasar.conf.js -> build -> vueRouterMode 25 | // quasar.conf.js -> build -> publicPath 26 | history: createHistory(process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE) 27 | }) 28 | 29 | return Router 30 | }) 31 | -------------------------------------------------------------------------------- /homepage/.overrides/home.html: -------------------------------------------------------------------------------- 1 | {#- 2 | This file was automatically generated - do not edit 3 | -#} 4 | {% extends "main.html" %} 5 | {% block tabs %} 6 | {{ super() }} 7 | 8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 |

novela.ink

16 |

{{ config.site_description }}

17 | 18 | Quick start 19 | 20 |
21 |
22 |
23 |
24 | {% endblock %} 25 | {% block content %}{% endblock %} 26 | {% block footer %}{% endblock %} 27 | -------------------------------------------------------------------------------- /novela_api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11 2 | 3 | WORKDIR /app 4 | RUN wget https://nodejs.org/dist/v18.12.1/node-v18.12.1-linux-x64.tar.xz && tar -xvf node-v18.12.1-linux-x64.tar.xz && mv node-v18.12.1-linux-x64 /usr/bin/nodebin && rm -rf node-v18.12.1-linux-x64.tar.xz 5 | ENV PATH=${PATH}:/usr/bin/nodebin/bin 6 | RUN npm install -g mdpdf 7 | COPY requirements.txt /requirements.txt 8 | 9 | RUN pip install --no-cache-dir --upgrade -r /requirements.txt && rm -f /requirements.txt 10 | COPY ./novela /app/novela 11 | 12 | # Needs for mdpdf 13 | RUN apt-get update && apt-get install curl gnupg -y \ 14 | && curl --location --silent https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ 15 | && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ 16 | && apt-get update \ 17 | && apt-get install google-chrome-stable -y --no-install-recommends \ 18 | && rm -rf /var/lib/apt/lists/* 19 | 20 | RUN mkdir /static 21 | 22 | ENV STATICPREFIX=/static/ 23 | ENV STARTING_API_KEY=hehe 24 | ENV DB_URL=immudb:3322 25 | ENV DB_USER=immudb 26 | ENV DB_PASS=immudb 27 | ENV DB_NAME=defaultdb 28 | EXPOSE 8000 29 | CMD ["uvicorn", "novela.main:app", "--host", "0.0.0.0", "--port", "8000"] -------------------------------------------------------------------------------- /.github/workflows/docker-image-master.yml: -------------------------------------------------------------------------------- 1 | name: dockerci 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Set up QEMU 14 | uses: docker/setup-qemu-action@v1 15 | - 16 | name: Set up Docker Buildx 17 | uses: docker/setup-buildx-action@v1 18 | - 19 | name: Login to GitHub Container Registry 20 | uses: docker/login-action@v1 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USER }} 23 | password: ${{ secrets.DOCKERHUB_TOKEN }} 24 | 25 | - name: Checkout repository 26 | uses: actions/checkout@v2 27 | - 28 | name: Build and push 29 | uses: docker/build-push-action@v2 30 | with: 31 | context: novela_api 32 | platforms: linux/amd64 33 | push: true 34 | tags: | 35 | razikus/novela:api-latest 36 | - 37 | name: Build and push gui 38 | uses: docker/build-push-action@v2 39 | with: 40 | context: novela-gui 41 | platforms: linux/amd64 42 | push: true 43 | tags: | 44 | razikus/novela:gui-latest 45 | -------------------------------------------------------------------------------- /homepage/docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | Novela ink is your own personal AI assistant platform to create/modify/enchance your stories. 4 | 5 | With power of OpenAI it was possible to create a storywritter that can really fit into your needs. 6 | 7 | With minimal effort you can quickly create PoC of stories, books, and creations. Or even entire books! 8 | 9 | You don't need expensive graphic designers, and copywriters. 10 | 11 | It also helps with gettings an inspiration! 12 | 13 | And everything is tamperproof, and immutable thanks to [immudb](https://immudb.io), so you can't really lose your creations. 14 | 15 | You can even undo and redo in any point of time! 16 | 17 | Everything in cool markdown format. 18 | 19 | ## Video (click) 20 | 21 | [![Novela (click)](https://img.youtube.com/vi/e14Tk476YOM/0.jpg)](https://www.youtube.com/watch?v=e14Tk476YOM) 22 | 23 | 24 | ## Installation 25 | 26 | ### with docker-compose recommended { #with-docker data-toc-label="with docker" } 27 | 28 | ``` 29 | git clone https://github.com/razikus/novela 30 | 31 | docker-compose up -d 32 | ``` 33 | 34 | Your novela installation will run on http://localhost:80 35 | 36 | 37 | ## License 38 | 39 | Apache 2.0 - feel free to use 40 | 41 | If you will become rich from that tool - remember about me :)! -------------------------------------------------------------------------------- /novela-gui/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name localhost; 5 | 6 | #access_log /var/log/nginx/host.access.log main; 7 | 8 | location / { 9 | root /usr/share/nginx/html; 10 | index index.html index.htm; 11 | try_files $uri $uri/ /index.html; 12 | } 13 | 14 | #error_page 404 /404.html; 15 | 16 | # redirect server error pages to the static page /50x.html 17 | # 18 | error_page 500 502 503 504 /50x.html; 19 | location = /50x.html { 20 | root /usr/share/nginx/html; 21 | } 22 | 23 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 24 | # 25 | #location ~ \.php$ { 26 | # proxy_pass http://127.0.0.1; 27 | #} 28 | 29 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 30 | # 31 | #location ~ \.php$ { 32 | # root html; 33 | # fastcgi_pass 127.0.0.1:9000; 34 | # fastcgi_index index.php; 35 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 36 | # include fastcgi_params; 37 | #} 38 | 39 | # deny access to .htaccess files, if Apache's document root 40 | # concurs with nginx's one 41 | # 42 | #location ~ /\.ht { 43 | # deny all; 44 | #} 45 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | reverse-proxy: 4 | image: traefik:v2.9 5 | restart: always 6 | command: 7 | - "--providers.docker=true" 8 | - "--providers.docker.exposedbydefault=false" 9 | - "--entrypoints.web.address=:80" 10 | ports: 11 | - "80:80" 12 | volumes: 13 | - /var/run/docker.sock:/var/run/docker.sock 14 | novela_api: 15 | image: razikus/novela:api-1.0.0 16 | restart: always 17 | labels: 18 | - "traefik.enable=true" 19 | - "traefik.http.routers.novelaapi.rule=PathPrefix(`/api/`)" 20 | - "traefik.http.routers.novelaapi.entrypoints=web" 21 | environment: 22 | - STARTING_API_KEY=FILLHEREYOURAPIKEY 23 | - STATICPREFIX=/static/ 24 | - DB_URL=immudb:3322 25 | - DB_USER=immudb 26 | - DB_PASS=immudb 27 | - DB_NAME=defaultdb 28 | 29 | volumes: 30 | - "static:/static" 31 | novela_gui: 32 | image: razikus/novela:gui-1.0.0 33 | restart: always 34 | labels: 35 | - "traefik.enable=true" 36 | - "traefik.http.routers.novelagui.rule=PathPrefix(`/`)" 37 | - "traefik.http.routers.novelagui.entrypoints=web" 38 | 39 | immudb: 40 | image: codenotary/immudb:1.4.1 41 | restart: always 42 | volumes: 43 | - "immudb:/var/lib/immudb" 44 | 45 | volumes: 46 | static: 47 | immudb: -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is novela.ink? 2 | https://novela.ink 3 | 4 | 5 | [![Novela (click)](./assets/readme.png)](https://novela.ink/) 6 | 7 | 8 | Novela ink is your own personal AI assistant platform to create/modify/enchance your stories. 9 | 10 | With power of OpenAI it was possible to create a storywritter that can really fit into your needs. 11 | 12 | With minimal effort you can quickly create PoC of stories, books, and creations. 13 | 14 | You don't need expensive graphic designers, and copywriters. 15 | 16 | It also helps with gettings an inspiration! 17 | 18 | And everything is tamperproof, and immutable thanks to [immudb](https://immudb.io), so you can't really lose your creations. 19 | 20 | Everything in cool markdown format. 21 | 22 | # Getting started 23 | 24 | ## Video (click) 25 | 26 | [![Novela (click)](https://img.youtube.com/vi/e14Tk476YOM/0.jpg)](https://www.youtube.com/watch?v=e14Tk476YOM) 27 | 28 | 29 | ## Installation 30 | 31 | ### with docker-compose recommended { #with-docker data-toc-label="with docker" } 32 | 33 | ``` 34 | git clone https://github.com/razikus/novela 35 | 36 | docker-compose up -d 37 | ``` 38 | 39 | Your novela installation will run on http://localhost:80 40 | 41 | 42 | ## License 43 | 44 | Apache 2.0 - feel free to use 45 | 46 | If you will become rich from that tool - remember about me :)! 47 | -------------------------------------------------------------------------------- /novela-gui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "novela-gui", 3 | "version": "0.0.1", 4 | "description": "Novela Ink", 5 | "productName": "Novela", 6 | "author": "Adam Raźniewski ", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --ext .js,.vue ./", 10 | "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", 11 | "test": "echo \"No test specified\" && exit 0" 12 | }, 13 | "dependencies": { 14 | "@quasar/extras": "^1.0.0", 15 | "axios": "^0.21.1", 16 | "core-js": "^3.6.5", 17 | "pinia": "^2.0.11", 18 | "quasar": "^2.6.0", 19 | "vue": "^3.0.0", 20 | "vue-i18n": "^9.0.0", 21 | "vue-router": "^4.0.0" 22 | }, 23 | "devDependencies": { 24 | "@babel/eslint-parser": "^7.13.14", 25 | "@quasar/app-webpack": "^3.0.0", 26 | "@quasar/quasar-app-extension-qmarkdown": "^2.0.0-beta.7", 27 | "eslint": "^8.10.0", 28 | "eslint-config-prettier": "^8.1.0", 29 | "eslint-plugin-vue": "^9.0.0", 30 | "eslint-webpack-plugin": "^3.1.1", 31 | "prettier": "^2.5.1" 32 | }, 33 | "browserslist": [ 34 | "last 10 Chrome versions", 35 | "last 10 Firefox versions", 36 | "last 4 Edge versions", 37 | "last 7 Safari versions", 38 | "last 8 Android versions", 39 | "last 8 ChromeAndroid versions", 40 | "last 8 FirefoxAndroid versions", 41 | "last 10 iOS versions", 42 | "last 5 Opera versions" 43 | ], 44 | "engines": { 45 | "node": ">= 12.22.1", 46 | "npm": ">= 6.13.4", 47 | "yarn": ">= 1.21.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: dockerci 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Set up QEMU 14 | uses: docker/setup-qemu-action@v1 15 | - 16 | name: Set up Docker Buildx 17 | uses: docker/setup-buildx-action@v1 18 | - 19 | name: Login to GitHub Container Registry 20 | uses: docker/login-action@v1 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USER }} 23 | password: ${{ secrets.DOCKERHUB_TOKEN }} 24 | 25 | - name: Checkout repository 26 | uses: actions/checkout@v2 27 | - name: Extract tag name 28 | shell: bash 29 | run: echo "##[set-output name=tag;]$(echo ${GITHUB_REF#refs/tags/})" 30 | id: extract_tag 31 | - name: Extract tag name without v 32 | shell: bash 33 | run: echo "##[set-output name=tagv;]$(echo ${GITHUB_REF#refs/tags/v})" 34 | id: extract_tag_v 35 | - 36 | name: Build and push 37 | uses: docker/build-push-action@v2 38 | with: 39 | context: novela_api 40 | platforms: linux/amd64 41 | push: true 42 | tags: | 43 | razikus/novela:api-${{ steps.extract_tag.outputs.tag }} 44 | razikus/novela:api-${{ steps.extract_tag_v.outputs.tagv }} 45 | - 46 | name: Build and push gui 47 | uses: docker/build-push-action@v2 48 | with: 49 | context: novela-gui 50 | platforms: linux/amd64 51 | push: true 52 | tags: | 53 | razikus/novela:gui-${{ steps.extract_tag.outputs.tag }} 54 | razikus/novela:gui-${{ steps.extract_tag_v.outputs.tagv }} 55 | -------------------------------------------------------------------------------- /novela-gui/src/pages/SettingsPage.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 53 | 86 | -------------------------------------------------------------------------------- /docker-compose-with-auth.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | reverse-proxy: 4 | image: traefik:v2.9 5 | restart: always 6 | command: 7 | - "--providers.docker=true" 8 | - "--providers.docker.exposedbydefault=false" 9 | - "--entrypoints.web.address=:80" 10 | ports: 11 | - "80:80" 12 | volumes: 13 | - /var/run/docker.sock:/var/run/docker.sock 14 | novela_api: 15 | image: razikus/novela:api-1.0.0 16 | restart: always 17 | labels: 18 | - "traefik.enable=true" 19 | - "traefik.http.routers.novelaapi.rule=PathPrefix(`/api/`)" 20 | - "traefik.http.routers.novelaapi.entrypoints=web" 21 | - "traefik.http.routers.novelaapi.middlewares=auth" 22 | - "traefik.http.middlewares.auth.basicauth.users=user:$$apr1$$fs0ibcry$$xjoLnWEIEwlPhPFUuv3sm0" # user / user | generate the login and password with htpasswd generator (could be found online) 23 | environment: 24 | - STARTING_API_KEY=FILLHEREYOURAPIKEY 25 | - STATICPREFIX=/static/ 26 | - DB_URL=immudb:3322 27 | - DB_USER=immudb 28 | - DB_PASS=immudb 29 | - DB_NAME=defaultdb 30 | 31 | 32 | volumes: 33 | - "static:/static" 34 | novela_gui: 35 | image: razikus/novela:gui-1.0.0 36 | restart: always 37 | labels: 38 | - "traefik.enable=true" 39 | - "traefik.http.routers.novelagui.rule=PathPrefix(`/`)" 40 | - "traefik.http.routers.novelagui.entrypoints=web" 41 | - "traefik.http.routers.novelagui.middlewares=auth" 42 | - "traefik.http.middlewares.auth.basicauth.users=user:$$apr1$$fs0ibcry$$xjoLnWEIEwlPhPFUuv3sm0" # user / user | generate the login and password with htpasswd generator (could be found online) 43 | 44 | immudb: 45 | image: codenotary/immudb:1.4.1 46 | restart: always 47 | volumes: 48 | - "immudb:/var/lib/immudb" 49 | 50 | volumes: 51 | static: 52 | immudb: -------------------------------------------------------------------------------- /novela-gui/src/pages/IndexPage.vue: -------------------------------------------------------------------------------- 1 | 18 | 80 | 81 | 92 | -------------------------------------------------------------------------------- /homepage/docs/features.md: -------------------------------------------------------------------------------- 1 | # AI story completion with different setups 2 | 3 | ![Context menu](./assets/scr1.png) 4 | 5 | Select a text, and then right-click and select one of the propositions. Wait a moment and bang! A the end of selection it would generate completion. 6 | 7 | # AI story completion in-place 8 | 9 | Same as story completion, but in place - move your cursor when you want to complete story. 10 | 11 | Right-click and select completion! It would generate completion from text before and AFTER your cursor. 12 | 13 | # Image generation for selected text 14 | 15 | 16 | Select a text, and then right-click and select Generate an image for selected sentence. Wait a moment and bang! A the end image from sentence that you selected appears. 17 | 18 | # Image generation for summary 19 | 20 | 21 | Select a text, and then right-click and select Generate an image as summary. 22 | 23 | It will first try to summarize selection (generate 12 descriptive words) what happen in the text, and after that - pass to OpenAI to generare summary. 24 | 25 | # Basic book operations 26 | 27 | ![Book operations](./assets/scr2.png) 28 | 29 | You can download your book in 2 formats - PDF and Markdown. 30 | 31 | You can also save your book. 32 | 33 | 34 | # Inspirations 35 | 36 | I would encourage you to experiment yourself. 37 | 38 | This tool creates a stories and saves into Inspiration book. 39 | 40 | Write kind of story, short description (or not) and hit Create!. 41 | 42 | If you will disable full auto, then you can write in field "Story about what" everything, and AI will try to proceed! 43 | 44 | We all feels blocked sometimes! 45 | 46 | # Time Travel 47 | 48 | By using [immudb](https://immudb.io) all of your creations are safe, immutable and tamperproof. 49 | 50 | Because of that it's very easy to undo and redo content of the book. 51 | 52 | Even if you accidentialy hit save on empty book - you can always back. 53 | 54 | # Immutability 55 | 56 | Becasue of immudb your creations are always safe and immutable. That means, that it could only change, but you can't really loss data in logical way. 57 | 58 | The only way to lose your creations is to manually delete entire database, or physical drive break. 59 | -------------------------------------------------------------------------------- /novela-gui/src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 104 | -------------------------------------------------------------------------------- /novela-gui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy 3 | // This option interrupts the configuration hierarchy at this file 4 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) 5 | root: true, 6 | 7 | parserOptions: { 8 | parser: '@babel/eslint-parser', 9 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 10 | sourceType: 'module' // Allows for the use of imports 11 | }, 12 | 13 | env: { 14 | browser: true, 15 | 'vue/setup-compiler-macros': true 16 | }, 17 | 18 | // Rules order is important, please avoid shuffling them 19 | extends: [ 20 | // Base ESLint recommended rules 21 | // 'eslint:recommended', 22 | 23 | // Uncomment any of the lines below to choose desired strictness, 24 | // but leave only one uncommented! 25 | // See https://eslint.vuejs.org/rules/#available-rules 26 | 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention) 27 | // 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) 28 | // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) 29 | 30 | // https://github.com/prettier/eslint-config-prettier#installation 31 | // usage with Prettier, provided by 'eslint-config-prettier'. 32 | 'prettier' 33 | ], 34 | 35 | plugins: [ 36 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files 37 | // required to lint *.vue files 38 | 'vue', 39 | 40 | // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 41 | // Prettier has not been included as plugin to avoid performance impact 42 | // add it as an extension for your IDE 43 | 44 | ], 45 | 46 | globals: { 47 | ga: 'readonly', // Google Analytics 48 | cordova: 'readonly', 49 | __statics: 'readonly', 50 | __QUASAR_SSR__: 'readonly', 51 | __QUASAR_SSR_SERVER__: 'readonly', 52 | __QUASAR_SSR_CLIENT__: 'readonly', 53 | __QUASAR_SSR_PWA__: 'readonly', 54 | process: 'readonly', 55 | Capacitor: 'readonly', 56 | chrome: 'readonly' 57 | }, 58 | 59 | // add your custom rules here 60 | rules: { 61 | 62 | 'prefer-promise-reject-errors': 'off', 63 | 64 | // allow debugger during development only 65 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /homepage/.overrides/assets/stylesheets/custom.9097afc2.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["src/.overrides/assets/stylesheets/custom/_typeset.scss","../../../../src/.overrides/assets/stylesheets/custom.scss","src/assets/stylesheets/utilities/_break.scss","src/.overrides/assets/stylesheets/custom/layout/_banner.scss","src/.overrides/assets/stylesheets/custom/layout/_hero.scss","src/.overrides/assets/stylesheets/custom/layout/_iconsearch.scss","src/.overrides/assets/stylesheets/custom/layout/_sponsorship.scss"],"names":[],"mappings":"AA2BA,iBACE,cAIE,kBC7BF,CDgCA,QAEE,qBC/BF,CACF,CD0CE,qBACE,aCxCJ,CD4CE,uBACE,UC1CJ,CD6CI,8BAGE,QAAA,CACA,sBAAA,CAHA,iBAAA,CACA,UCzCN,CD+CI,8BAOE,WAAA,CAFA,WAAA,CAFA,MAAA,CAGA,eAAA,CALA,iBAAA,CACA,KAAA,CAEA,UC1CN,CDkDE,uBACE,2BChDJ,CDoDE,0BACE,aClDJ,CDsDE,+BACE,cAAA,CACA,uBCpDJ,CDuDI,0EAEE,WCtDN,CD0DI,oCAGE,2CAAA,CADA,gCAAA,CADA,aCtDN,CD6DE,4BACE,UAAA,CACA,uBC3DJ,CD8DI,2EAEE,SC7DN,CDqEI,wDAEE,cCnEN,CC2JI,wCF1FA,wDAMI,eClEN,CACF,CDsEI,4BACE,kBCpEN,CDyEE,wBACE,YAAA,CACA,gBCvEJ,CD0EI,4BAEE,kBAAA,CADA,WCvEN,CD+EM,sCACE,aAAA,CACA,kBC7ER,CDiFM,+BACE,aC/ER,CEnDE,mDAGE,kBFsDJ,CElDE,kBACE,kBFoDJ,CEhDE,8BACE,gBFkDJ,CEnDE,8BACE,iBFkDJ,CGlEA,eAEE,uYACE,CAFF,gBHsEF,CG3DE,4CACE,yYH6DJ,CGjDA,UAEE,gCAAA,CADA,cHqDF,CGjDE,aAEE,kBAAA,CACA,eAAA,CAFA,kBHqDJ,CCqGI,wCE3JF,aAOI,gBHmDJ,CACF,CG/CE,mBACE,mBHiDJ,CC0EI,mCE7IJ,UAwBI,mBAAA,CADA,YHiDF,CG7CE,mBAEE,iBAAA,CADA,eAAA,CAEA,mBH+CJ,CG3CE,iBACE,OAAA,CAEA,0BAAA,CADA,WH8CJ,CACF,CC0DI,sCEhGA,iBACE,0BHyCJ,CACF,CGrCE,qBAGE,gCAAA,CADA,kBAAA,CADA,gBHyCJ,CGpCI,sDAGE,0CAAA,CACA,sCAAA,CAFA,+BHuCN,CGjCI,8BAEE,2CAAA,CACA,uCAAA,CAFA,aHqCN,CI7HE,4BAEE,2CAAA,CACA,mBAAA,CACA,8BAAA,CAHA,iBAAA,CAIA,2BJgIJ,CI7HI,2EAEE,8BJ8HN,CI1HI,sCACE,qCAAA,CACA,eJ4HN,CIzHM,mEACE,kCJ2HR,CIrHE,mCAIE,kCAAA,CAAA,0BAAA,CAHA,eAAA,CACA,eAAA,CAKA,yDAAA,CADA,oBAAA,CADA,kBJwHJ,CInHI,+CACE,mBJqHN,CIjHI,sDAEE,YAAA,CADA,WJoHN,CI/GI,4DACE,oDJiHN,CI9GM,kEACE,0CJgHR,CI3GI,yCAIE,yCAAA,CACA,gBAAA,CAJA,iBAAA,CAEA,WAAA,CADA,SJgHN,CIzGI,mDAIE,aJ2GN,CI/GI,mDAIE,cJ2GN,CI/GI,yCAME,eAAA,CALA,QAAA,CAIA,SJ0GN,CIrGI,mDAIE,aJuGN,CI3GI,mDAIE,cJuGN,CI3GI,yCAME,+DAAA,CALA,QAAA,CAIA,mBJsGN,CIlGM,oDACE,kBJoGR,CIhGM,2CACE,kBJkGR,CI9FM,6CAEE,YAAA,CADA,WJiGR,CI7FQ,0FACE,gBJ+FV,CKjMI,2BACE,YAAA,CACA,iBLoMN,CKhMI,6BACE,cLkMN,CK9LI,sCACE,YAAA,CACA,cAAA,CACA,sBLgMN,CK7LM,wCACE,aAAA,CACA,aL+LR,CKtLI,mCACE,YLwLN,CKrLM,yCAEE,UAAA,CACA,UAAA,CAFA,aLyLR,CKlLI,6CAEE,UL2LN,CK7LI,6CAEE,WL2LN,CK7LI,mCAOE,kBAAA,CANA,aAAA,CAGA,aAAA,CACA,YAAA,CACA,eAAA,CAEA,kBAAA,CACA,sCACE,CAPF,YL0LN,CK/KM,kFAEE,oBLgLR,CK7KQ,0FACE,mBL+KV,CK1KM,4CAME,+CAAA,CALA,yCAAA,CAEA,eAAA,CADA,eAAA,CAEA,kBAAA,CACA,iBL6KR,CKxKM,uCACE,aAAA,CAGA,mCAAA,CADA,WAAA,CAEA,uBAAA,CAHA,UL6KR,CKpKE,oCACE,eLsKJ,CKlKE,sEAEE,eLoKJ","file":"custom.css"} -------------------------------------------------------------------------------- /novela-gui/src/pages/InspirationsPage.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 68 | 97 | -------------------------------------------------------------------------------- /novela-gui/src/boot/axios.js: -------------------------------------------------------------------------------- 1 | import { boot } from 'quasar/wrappers' 2 | import axios from 'axios' 3 | 4 | // Be careful when using SSR for cross-request state pollution 5 | // due to creating a Singleton instance here; 6 | // If any client changes this (global) instance, it might be a 7 | // good idea to move this instance creation inside of the 8 | // "export default () => {}" function below (which runs individually 9 | // for each client) 10 | const api = axios.create({ baseURL: window.location.origin }) 11 | 12 | class NovelaAPIClient { 13 | constructor(apiClient) { 14 | this.apiClient = apiClient 15 | } 16 | async getStoryCompletion(uid, storyStart, storyEnds, action) { 17 | return await this.apiClient.post(`/api/v1/book/${uid}/ai/complete`, { 18 | storyStart: storyStart, 19 | storyEnd: storyEnds, 20 | actionType: action 21 | }) 22 | } 23 | async getSummaryImage(uid, story) { 24 | return await this.apiClient.post(`/api/v1/book/${uid}/ai/summaryImage`, { 25 | sentence: story, 26 | }) 27 | 28 | } 29 | async getSentenceImage(uid, story) { 30 | return await this.apiClient.post(`/api/v1/book/${uid}/ai/image`, { 31 | sentence: story, 32 | }) 33 | } 34 | async save(uid, content) { 35 | return await this.apiClient.post(`/api/v1/book/${uid}/save`, { 36 | content: content, 37 | }) 38 | } 39 | async getContent(uid, revision) { 40 | return await this.apiClient.get(`/api/v1/book/${uid}/get`, { 41 | params: { 42 | revision: revision 43 | } 44 | }) 45 | } 46 | async setApiKey(config) { 47 | return await this.apiClient.post(`/api/v1/config`, config) 48 | } 49 | async getBookInfo(uid) { 50 | return await this.apiClient.get(`/api/v1/book/${uid}/info`) 51 | } 52 | async listBooks() { 53 | return await this.apiClient.get(`/api/v1/book/list`) 54 | } 55 | async addBook(book) { 56 | return await this.apiClient.post(`/api/v1/book/create`, book) 57 | } 58 | async deleteBook(uid) { 59 | return await this.apiClient.delete(`/api/v1/book/${uid}/delete`) 60 | } 61 | async createInspiration(req) { 62 | return await this.apiClient.post(`/api/v1/inspire`, req) 63 | } 64 | 65 | } 66 | 67 | const novelaAPI = new NovelaAPIClient(api) 68 | 69 | export default boot(({ app }) => { 70 | // for use inside Vue files (Options API) through this.$axios and this.$api 71 | 72 | app.config.globalProperties.$axios = axios 73 | // ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form) 74 | // so you won't necessarily have to import axios in each vue file 75 | 76 | app.config.globalProperties.$api = api 77 | app.config.globalProperties.$novelaAPI = novelaAPI 78 | // ^ ^ ^ this will allow you to use this.$api (for Vue Options API form) 79 | // so you can easily perform requests against your app's API 80 | }) 81 | 82 | export { api, novelaAPI } 83 | -------------------------------------------------------------------------------- /novela_api/novela/aiconnector/aiconnector.py: -------------------------------------------------------------------------------- 1 | 2 | import openai 3 | import requests 4 | import uuid 5 | from ..config.config import STATICPREFIX 6 | 7 | class AIConnector: 8 | def __init__(self, api_key): 9 | openai.api_key = api_key 10 | self.api_key = api_key 11 | self.imageClient = None 12 | self.storyCompletor = None 13 | self.summarizer = None 14 | self.initializeClients() 15 | 16 | def initializeClients(self): 17 | self.storyCompletor = AIStoryCompletor(self.api_key) 18 | self.summarizer = AiSummarizer(self.api_key) 19 | self.imageClient = AIImagerClient(self.api_key, self.summarizer) 20 | 21 | 22 | 23 | class AIImagerClient: 24 | def __init__(self, api_key, summarizer): 25 | self.api_key = api_key 26 | self.client = openai.Image() 27 | self.summarizer = summarizer 28 | 29 | def saveImage(self, fromWhat): 30 | returnUrls = [] 31 | for item in fromWhat["data"]: 32 | urlOf = item["url"] 33 | saveId = str(uuid.uuid4()) + ".png" 34 | savePath = STATICPREFIX + saveId 35 | retrieved = requests.get(urlOf) 36 | with open(savePath, "wb") as toSave: 37 | toSave.write(retrieved.content) 38 | returnUrls.append(saveId) 39 | return returnUrls 40 | 41 | 42 | def getImageForSummary(self, summary, size="256x256", withStyle = None): 43 | whatIsThat = self.summarizer.whatIsThatAbout(summary) 44 | if(not withStyle): 45 | withStyle = "" 46 | else: 47 | withStyle = f", {withStyle} style" 48 | return self.getImageForSentence(whatIsThat["choices"][0]["text"], size, withStyle) 49 | 50 | 51 | 52 | def getImageForSentence(self, sentence, size="256x256", withStyle = None): 53 | style = "" 54 | if(withStyle): 55 | style = ", " + withStyle 56 | generated = self.client.create(**{ 57 | "prompt": sentence + style, 58 | "n": 1, 59 | "size": size, 60 | }) 61 | urls = self.saveImage(generated) 62 | return urls 63 | 64 | class AIStoryCompletor: 65 | def __init__(self, api_key): 66 | self.api_key = api_key 67 | self.model = "text-davinci-003" 68 | self.client = openai.Completion() 69 | 70 | def completeStory(self, forWhat, temperature = 0.3, max_tokens=2048, suffix = None, action = "", n = 1): 71 | forWhat = f"Find a {action} continue for:\n" + forWhat 72 | print(forWhat) 73 | req = { 74 | "model": self.model, 75 | "prompt": forWhat, 76 | "max_tokens": max_tokens, 77 | "temperature": temperature, 78 | "n": n 79 | } 80 | if suffix: 81 | req["suffix"] = suffix 82 | completed = self.client.create(**req) 83 | return completed 84 | 85 | 86 | 87 | class AiSummarizer: 88 | 89 | def __init__(self, api_key): 90 | self.api_key = api_key 91 | self.model = "text-davinci-003" 92 | self.client = openai.Completion() 93 | 94 | def whatIsThatAbout(self, content, max_tokens=256): 95 | req = { 96 | "model": self.model, 97 | "prompt": "Generate a short one sentence summary that could be applicable as DALEE2 input in english that has max 12 words, about this: \n" + content, 98 | "max_tokens": max_tokens, 99 | "temperature": 0.8, 100 | "n": 1 101 | } 102 | answer = self.client.create(**req) 103 | return answer -------------------------------------------------------------------------------- /novela_api/novela/bookclient.py: -------------------------------------------------------------------------------- 1 | from immudb import ImmudbClient 2 | from immudb.datatypes import DeleteKeysRequest 3 | from immudb.rootService import PersistentRootService 4 | from pydantic import BaseModel 5 | from .config.config import STATEFILE 6 | from typing import List 7 | 8 | 9 | 10 | class BookLong(BaseModel): 11 | title: str 12 | kind: str 13 | image: str 14 | content: str 15 | author: str 16 | uid: str 17 | 18 | class BookSave(BaseModel): 19 | uid: str 20 | content: str 21 | 22 | 23 | 24 | class BookShort(BaseModel): 25 | title: str 26 | kind: str 27 | author: str 28 | description: str 29 | 30 | image: str 31 | uid: str 32 | 33 | class BookList(BaseModel): 34 | books: List[BookShort] 35 | 36 | 37 | class NewBookRequest(BaseModel): 38 | title: str 39 | kind: str 40 | author: str 41 | description: str 42 | 43 | 44 | class BookClient: 45 | def __init__(self, url): 46 | self.url = url 47 | if(STATEFILE != None): 48 | self.client = ImmudbClient(url, rs = PersistentRootService(STATEFILE), max_grpc_message_length=1024*32*1024) 49 | else: 50 | self.client = ImmudbClient(url, max_grpc_message_length=1024*32*1024) 51 | 52 | def _getBookContentKey(self, uid: str): 53 | return f"BOOKCONTENT:{uid}".encode("utf-8") 54 | 55 | def _getBookDescriptionKey(self, uid: str): 56 | return f"BOOKDESCRIPTION:{uid}".encode("utf-8") 57 | 58 | def _getBookLastActionKey(self, uid: str): 59 | return f"BOOKAILASTACTION:{uid}".encode("utf-8") 60 | 61 | def _getDefaultBookContent(self, title: str, author: str, image: str): 62 | return f"""# {title}""".encode("utf-8") 63 | 64 | def addNewBook(self, bookShort: BookShort): 65 | self.client.set(self._getBookDescriptionKey(bookShort.uid), bookShort.json().encode("utf-8")) 66 | self.client.set(self._getBookContentKey(bookShort.uid), self._getDefaultBookContent(bookShort.title, bookShort.author, bookShort.image)) 67 | return True 68 | 69 | def getBookShort(self, uid: str) -> BookShort: 70 | try: 71 | val = self.client.get(self._getBookDescriptionKey(uid)) 72 | if(val and val.value): 73 | return BookShort.parse_raw(val.value) 74 | else: 75 | return None 76 | except: 77 | return None 78 | 79 | 80 | def setLastAIAction(self, uid: str, action: bytes): 81 | self.client.set(self._getBookLastActionKey(uid), action) 82 | return True 83 | 84 | 85 | def getBookList(self): 86 | prepared = [] 87 | scanned = self.client.scan(None, b'BOOKDESCRIPTION:', True, 100) 88 | while scanned: 89 | last = None 90 | for key, value in scanned.items(): 91 | last = key 92 | prepared.append(BookShort.parse_raw(value)) 93 | scanned = self.client.scan(last, b'BOOKDESCRIPTION:', True, 100) 94 | return prepared 95 | 96 | def getBookContent(self, uid: str, revision: int): 97 | val = self.client.verifiedGet(self._getBookContentKey(uid), revision) 98 | return val 99 | 100 | def deleteBook(self, uid: str): 101 | self.client.delete(DeleteKeysRequest(keys = [self._getBookContentKey(uid), self._getBookDescriptionKey(uid)])) 102 | 103 | def saveBookContent(self, uid: str, value: str): 104 | setted = self.client.streamVerifiedSetFullValue(self._getBookContentKey(uid), value.encode("utf-8")) 105 | return True 106 | 107 | 108 | -------------------------------------------------------------------------------- /novela-gui/src/pages/BooksPage.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 64 | 65 | 118 | -------------------------------------------------------------------------------- /novela-gui/src/assets/quasar-logo-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /homepage/mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016-2022 Martin Donath 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | # IN THE SOFTWARE. 20 | 21 | # Project information 22 | site_name: novela.ink 23 | site_url: https://razikus.github.io/novela/ 24 | site_author: Adam Raźniewski 25 | site_description: >- 26 | Your friendly AI assistant in building 27 | 28 | # Repository 29 | repo_name: razikus/novela 30 | repo_url: https://github.com/razikus/novela 31 | 32 | # Copyright 33 | copyright: Copyright © 2022 - 2022 Adam Raźniewski 34 | 35 | # Configuration 36 | theme: 37 | name: material 38 | custom_dir: .overrides 39 | logo: assets/images/logo-3.png 40 | features: 41 | # - announce.dismiss 42 | - content.code.annotate 43 | # - content.tabs.link 44 | - content.tooltips 45 | # - header.autohide 46 | # - navigation.expand 47 | - navigation.indexes 48 | # - navigation.instant 49 | # - navigation.prune 50 | - navigation.sections 51 | - navigation.tabs 52 | # - navigation.tabs.sticky 53 | - navigation.top 54 | - navigation.tracking 55 | - search.highlight 56 | - search.share 57 | - search.suggest 58 | - toc.follow 59 | # - toc.integrate 60 | palette: 61 | - scheme: default 62 | primary: teal 63 | accent: teal 64 | toggle: 65 | icon: material/brightness-7 66 | name: Switch to dark mode 67 | - scheme: slate 68 | primary: indigo 69 | accent: indigo 70 | toggle: 71 | icon: material/brightness-4 72 | name: Switch to light mode 73 | font: 74 | text: Roboto 75 | code: Roboto Mono 76 | favicon: assets/favicon.png 77 | icon: 78 | logo: logo 79 | 80 | # Plugins 81 | plugins: 82 | - search 83 | - minify: 84 | minify_html: true 85 | 86 | # Customization 87 | extra: 88 | analytics: 89 | provider: google 90 | property: !ENV GOOGLE_ANALYTICS_KEY 91 | generator: false 92 | social: 93 | - icon: fontawesome/brands/github 94 | link: https://github.com/razikus 95 | - icon: fontawesome/brands/docker 96 | link: https://hub.docker.com/r/razikus/novela/ 97 | 98 | # Extensions 99 | markdown_extensions: 100 | - abbr 101 | - admonition 102 | - attr_list 103 | - def_list 104 | - footnotes 105 | - md_in_html 106 | - toc: 107 | permalink: true 108 | - pymdownx.arithmatex: 109 | generic: true 110 | - pymdownx.betterem: 111 | smart_enable: all 112 | - pymdownx.caret 113 | - pymdownx.details 114 | - pymdownx.emoji: 115 | emoji_generator: !!python/name:materialx.emoji.to_svg 116 | emoji_index: !!python/name:materialx.emoji.twemoji 117 | - pymdownx.highlight: 118 | anchor_linenums: true 119 | - pymdownx.inlinehilite 120 | - pymdownx.keys 121 | - pymdownx.magiclink: 122 | repo_url_shorthand: true 123 | user: razikus 124 | repo: novela 125 | - pymdownx.mark 126 | - pymdownx.smartsymbols 127 | - pymdownx.superfences: 128 | custom_fences: 129 | - name: mermaid 130 | class: mermaid 131 | format: !!python/name:pymdownx.superfences.fence_code_format 132 | - pymdownx.tabbed: 133 | alternate_style: true 134 | - pymdownx.tasklist: 135 | custom_checkbox: true 136 | - pymdownx.tilde 137 | 138 | # Page tree 139 | nav: 140 | - Home: index.md 141 | - Getting started: 142 | - Installation: getting-started.md 143 | - Features: features.md -------------------------------------------------------------------------------- /novela-gui/quasar.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | /* 4 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only 5 | * the ES6 features that are supported by your Node version. https://node.green/ 6 | */ 7 | 8 | // Configuration for your app 9 | // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js 10 | 11 | 12 | const ESLintPlugin = require('eslint-webpack-plugin') 13 | 14 | 15 | const { configure } = require('quasar/wrappers'); 16 | 17 | module.exports = configure(function (ctx) { 18 | return { 19 | // https://v2.quasar.dev/quasar-cli-webpack/supporting-ts 20 | supportTS: false, 21 | 22 | // https://v2.quasar.dev/quasar-cli-webpack/prefetch-feature 23 | // preFetch: true, 24 | 25 | // app boot file (/src/boot) 26 | // --> boot files are part of "main.js" 27 | // https://v2.quasar.dev/quasar-cli-webpack/boot-files 28 | boot: [ 29 | 'i18n', 30 | 'axios', 31 | ], 32 | 33 | // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css 34 | css: [ 35 | 'app.scss' 36 | ], 37 | 38 | // https://github.com/quasarframework/quasar/tree/dev/extras 39 | extras: [ 40 | // 'ionicons-v4', 41 | // 'mdi-v5', 42 | // 'fontawesome-v6', 43 | // 'eva-icons', 44 | // 'themify', 45 | // 'line-awesome', 46 | 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 47 | 48 | // 'roboto-font', // optional, you are not bound to it 49 | 'material-icons', // optional, you are not bound to it 50 | ], 51 | 52 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build 53 | build: { 54 | vueRouterMode: 'hash', // available values: 'hash', 'history' 55 | 56 | // transpile: false, 57 | // publicPath: '/', 58 | 59 | // Add dependencies for transpiling with Babel (Array of string/regex) 60 | // (from node_modules, which are by default not transpiled). 61 | // Applies only if "transpile" is set to true. 62 | // transpileDependencies: [], 63 | 64 | // rtl: true, // https://quasar.dev/options/rtl-support 65 | // preloadChunks: true, 66 | // showProgress: false, 67 | // gzip: true, 68 | // analyze: true, 69 | 70 | // Options below are automatically set depending on the env, set them if you want to override 71 | // extractCSS: false, 72 | 73 | // https://v2.quasar.dev/quasar-cli-webpack/handling-webpack 74 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 75 | 76 | chainWebpack (chain) { 77 | chain.plugin('eslint-webpack-plugin') 78 | .use(ESLintPlugin, [{ extensions: [ 'js', 'vue' ] }]) 79 | } 80 | 81 | }, 82 | 83 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-devServer 84 | devServer: { 85 | server: { 86 | type: 'http', 87 | }, 88 | proxy: { 89 | // proxy all requests starting with /api to jsonplaceholder 90 | '/api': { 91 | target: 'http://127.0.0.1:8000', 92 | changeOrigin: true, 93 | pathRewrite: { 94 | } 95 | } 96 | }, 97 | port: 8080, 98 | open: true // opens browser window automatically 99 | }, 100 | 101 | // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework 102 | framework: { 103 | config: {}, 104 | 105 | // iconSet: 'material-icons', // Quasar icon set 106 | // lang: 'en-US', // Quasar language pack 107 | 108 | // For special cases outside of where the auto-import strategy can have an impact 109 | // (like functional components as one of the examples), 110 | // you can manually specify Quasar components/directives to be available everywhere: 111 | // 112 | // components: [], 113 | // directives: [], 114 | 115 | // Quasar plugins 116 | plugins: ["Notify"] 117 | }, 118 | 119 | // animations: 'all', // --- includes all animations 120 | // https://quasar.dev/options/animations 121 | animations: [], 122 | 123 | // https://v2.quasar.dev/quasar-cli-webpack/developing-ssr/configuring-ssr 124 | ssr: { 125 | pwa: false, 126 | 127 | // manualStoreHydration: true, 128 | // manualPostHydrationTrigger: true, 129 | 130 | prodPort: 3000, // The default port that the production server should use 131 | // (gets superseded if process.env.PORT is specified at runtime) 132 | 133 | maxAge: 1000 * 60 * 60 * 24 * 30, 134 | // Tell browser when a file from the server should expire from cache (in ms) 135 | 136 | 137 | chainWebpackWebserver (chain) { 138 | chain.plugin('eslint-webpack-plugin') 139 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 140 | }, 141 | 142 | 143 | middlewares: [ 144 | ctx.prod ? 'compression' : '', 145 | 'render' // keep this as last one 146 | ] 147 | }, 148 | 149 | // https://v2.quasar.dev/quasar-cli-webpack/developing-pwa/configuring-pwa 150 | pwa: { 151 | workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest' 152 | workboxOptions: {}, // only for GenerateSW 153 | 154 | // for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts]) 155 | // if using workbox in InjectManifest mode 156 | 157 | chainWebpackCustomSW (chain) { 158 | chain.plugin('eslint-webpack-plugin') 159 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 160 | }, 161 | 162 | 163 | manifest: { 164 | name: `Novela`, 165 | short_name: `Novela`, 166 | description: `Novela Ink`, 167 | display: 'standalone', 168 | orientation: 'portrait', 169 | background_color: '#ffffff', 170 | theme_color: '#027be3', 171 | icons: [ 172 | { 173 | src: 'icons/icon-128x128.png', 174 | sizes: '128x128', 175 | type: 'image/png' 176 | }, 177 | { 178 | src: 'icons/icon-192x192.png', 179 | sizes: '192x192', 180 | type: 'image/png' 181 | }, 182 | { 183 | src: 'icons/icon-256x256.png', 184 | sizes: '256x256', 185 | type: 'image/png' 186 | }, 187 | { 188 | src: 'icons/icon-384x384.png', 189 | sizes: '384x384', 190 | type: 'image/png' 191 | }, 192 | { 193 | src: 'icons/icon-512x512.png', 194 | sizes: '512x512', 195 | type: 'image/png' 196 | } 197 | ] 198 | } 199 | }, 200 | 201 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-cordova-apps/configuring-cordova 202 | cordova: { 203 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing 204 | }, 205 | 206 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-capacitor-apps/configuring-capacitor 207 | capacitor: { 208 | hideSplashscreen: true 209 | }, 210 | 211 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-electron-apps/configuring-electron 212 | electron: { 213 | bundler: 'packager', // 'packager' or 'builder' 214 | 215 | packager: { 216 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options 217 | 218 | // OS X / Mac App Store 219 | // appBundleId: '', 220 | // appCategoryType: '', 221 | // osxSign: '', 222 | // protocol: 'myapp://path', 223 | 224 | // Windows only 225 | // win32metadata: { ... } 226 | }, 227 | 228 | builder: { 229 | // https://www.electron.build/configuration/configuration 230 | 231 | appId: 'novela-gui' 232 | }, 233 | 234 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 235 | 236 | chainWebpackMain (chain) { 237 | chain.plugin('eslint-webpack-plugin') 238 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 239 | }, 240 | 241 | 242 | 243 | chainWebpackPreload (chain) { 244 | chain.plugin('eslint-webpack-plugin') 245 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 246 | }, 247 | 248 | } 249 | } 250 | }); 251 | -------------------------------------------------------------------------------- /novela_api/novela/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Depends 2 | from .aiconnector.aiconnector import AIConnector 3 | from pydantic import BaseModel 4 | from fastapi.responses import FileResponse 5 | from .config.config import STATICPREFIX, STARTING_API_KEY,DB_URL, DB_PASS, DB_USER, DB_NAME 6 | from .bookclient import BookClient, NewBookRequest, BookShort 7 | import uuid 8 | import json 9 | import os 10 | import asyncio 11 | from starlette.background import BackgroundTask 12 | 13 | 14 | app = FastAPI(title = "Novela API", version = "0.1.0") 15 | 16 | loadedConfig = None 17 | 18 | @app.on_event("startup") 19 | async def startup(): 20 | global loadedConfig 21 | bookClient = getBookClient() 22 | configVal = bookClient.client.get(b"CONFIG") 23 | if(configVal and configVal.value): 24 | loadedConfig = ConfigRequest.parse_raw(configVal.value) 25 | else: 26 | loadedConfig = ConfigRequest(api_key=STARTING_API_KEY) 27 | 28 | async def getAIClient() -> AIConnector: 29 | connector = AIConnector(loadedConfig.api_key) 30 | return connector 31 | 32 | def getBookClient() -> BookClient: 33 | client = BookClient(DB_URL) 34 | client.client.login(DB_USER.encode("utf-8"), DB_PASS.encode("utf-8"), DB_NAME.encode("utf-8")) 35 | return client 36 | 37 | 38 | class CreateImageReq(BaseModel): 39 | sentence: str 40 | 41 | 42 | class CreateCompletionReq(BaseModel): 43 | storyStart: str 44 | storyEnd: str = None 45 | length: int = 1024 46 | actionType: str = "" 47 | 48 | class CreateInspirationReq(BaseModel): 49 | somethingAbout: str = None 50 | fullAuto: bool = False 51 | length: int = 4096 52 | kind: str = "" 53 | 54 | class BookSaveRequest(BaseModel): 55 | content: str 56 | 57 | class BookUndoRequest(BaseModel): 58 | undo: int 59 | 60 | class ConfigRequest(BaseModel): 61 | api_key: str 62 | 63 | @app.get("/api/v1/images/get/{uid}") 64 | async def get_image(uid): 65 | return FileResponse(STATICPREFIX + str(uid)) 66 | 67 | @app.post("/api/v1/config") 68 | async def setConfig(req: ConfigRequest, dbClient: BookClient = Depends(getBookClient)): 69 | global loadedConfig 70 | dbClient.client.set(b"CONFIG", req.json().encode("utf-8")) 71 | loadedConfig = req 72 | return True 73 | 74 | 75 | @app.post("/api/v1/book/create") 76 | async def create_book(req: NewBookRequest, dbClient: BookClient = Depends(getBookClient), connector: AIConnector = Depends(getAIClient)): 77 | newuuid = str(uuid.uuid4()) 78 | imageForSentence = connector.imageClient.getImageForSentence(req.description, "512x512", f", {req.kind} style") 79 | firstImage = imageForSentence[0] 80 | bookShort = BookShort(title = req.title, kind = req.kind, author = req.author, description=req.description, image = firstImage, uid = newuuid) 81 | dbClient.addNewBook(bookShort) 82 | return bookShort 83 | 84 | @app.get("/api/v1/book/list") 85 | async def list_books(dbClient: BookClient = Depends(getBookClient), connector: AIConnector = Depends(getAIClient)): 86 | return dbClient.getBookList() 87 | 88 | @app.post("/api/v1/book/{uid}/save") 89 | async def save_book(uid: str, req: BookSaveRequest, dbClient: BookClient = Depends(getBookClient)): 90 | return dbClient.saveBookContent(uid, req.content) 91 | 92 | @app.delete("/api/v1/book/{uid}/delete") 93 | async def delete_book(uid: str, dbClient: BookClient = Depends(getBookClient)): 94 | return dbClient.deleteBook(uid) 95 | 96 | 97 | @app.get("/api/v1/book/{uid}/info") 98 | async def get_book(uid: str, dbClient: BookClient = Depends(getBookClient)): 99 | try: 100 | return dbClient.getBookShort(uid) 101 | except: 102 | return None 103 | 104 | @app.get("/api/v1/book/{uid}/get") 105 | async def get_book(uid: str, revision: int = 0, dbClient: BookClient = Depends(getBookClient)): 106 | try: 107 | return dbClient.getBookContent(uid, revision) 108 | except: 109 | return None 110 | 111 | def cleanup(fileToSave, pdfPath): 112 | def clean(): 113 | os.remove(fileToSave) 114 | if(pdfPath): 115 | os.remove(pdfPath) 116 | return clean 117 | 118 | @app.get("/api/v1/book/{uid}/pdf") 119 | async def get_pdf(uid, dbClient: BookClient = Depends(getBookClient)): 120 | book = dbClient.getBookShort(uid) 121 | content = dbClient.getBookContent(uid, 0) 122 | if content and content.value: 123 | uuidOf = str(uuid.uuid4()) 124 | fileToSave = "/tmp/" + uuidOf + ".md" 125 | pdfPath = "/tmp/" + uuidOf + ".pdf" 126 | with open(fileToSave, "w") as toWrite: 127 | decoded = content.value.decode("utf-8") 128 | decoded = decoded.replace("/api/v1/images/get/", "http://localhost:8000/api/v1/images/get/") 129 | toWrite.write(decoded) 130 | 131 | proc = await asyncio.create_subprocess_shell( 132 | f"mdpdf {fileToSave} {pdfPath}", 133 | stdout=asyncio.subprocess.PIPE, 134 | stderr=asyncio.subprocess.PIPE) 135 | stdout, stderr = await proc.communicate() 136 | return FileResponse(pdfPath, background=BackgroundTask(cleanup(fileToSave, pdfPath))) 137 | return None 138 | 139 | 140 | @app.get("/api/v1/book/{uid}/md") 141 | async def get_pdf(uid, dbClient: BookClient = Depends(getBookClient)): 142 | book = dbClient.getBookShort(uid) 143 | content = dbClient.getBookContent(uid, 0) 144 | if content and content.value: 145 | uuidOf = str(uuid.uuid4()) 146 | fileToSave = "/tmp/" + uuidOf + ".md" 147 | with open(fileToSave, "wb") as toWrite: 148 | toWrite.write(content.value) 149 | 150 | return FileResponse(fileToSave, background=BackgroundTask(cleanup(fileToSave, None))) 151 | return None 152 | 153 | 154 | 155 | @app.post("/api/v1/book/{uid}/ai/complete") 156 | async def complete_it(uid, req: CreateCompletionReq, dbClient: BookClient = Depends(getBookClient), connector: AIConnector = Depends(getAIClient)): 157 | story = connector.storyCompletor.completeStory(req.storyStart, suffix = req.storyEnd, action = req.actionType) 158 | dbClient.setLastAIAction(uid, json.dumps(story).encode("utf-8")) 159 | firstChoice = story["choices"][0]["text"] 160 | return {"text": firstChoice} 161 | 162 | @app.post("/api/v1/book/{uid}/ai/summaryImage") 163 | async def generateSummaryImage(uid: str, req: CreateImageReq, dbClient: BookClient = Depends(getBookClient), connector: AIConnector = Depends(getAIClient)): 164 | book = dbClient.getBookShort(uid) 165 | story = connector.imageClient.getImageForSummary(req.sentence, size = "512x512", withStyle = book.kind) 166 | dbClient.setLastAIAction(uid, json.dumps(story).encode("utf-8")) 167 | return story 168 | 169 | 170 | @app.post("/api/v1/book/{uid}/ai/image") 171 | async def genereateImage(uid: str, req: CreateImageReq, dbClient: BookClient = Depends(getBookClient), connector: AIConnector = Depends(getAIClient)): 172 | book = dbClient.getBookShort(uid) 173 | story = connector.imageClient.getImageForSentence(req.sentence, size = "512x512", withStyle = book.kind) 174 | dbClient.setLastAIAction(uid, json.dumps(story).encode("utf-8")) 175 | return story 176 | 177 | @app.post("/api/v1/inspire") 178 | async def inspire_me(req: CreateInspirationReq, dbClient: BookClient = Depends(getBookClient), connector: AIConnector = Depends(getAIClient)): 179 | inspirationbook = "inspirationbook" 180 | book = dbClient.getBookShort(inspirationbook) 181 | if(book == None): 182 | image = connector.imageClient.getImageForSentence("Bowl of tousands of thoughs and mist, fantasy digital art", size = "512x512", withStyle = "fantasy") 183 | dbClient.addNewBook(BookShort( 184 | uid = inspirationbook, 185 | title = "Book of inspirations", 186 | kind = "fantasy", 187 | author = "Everyone", 188 | description = "Contains every inspiration that you created", 189 | image = image[0] 190 | )) 191 | 192 | if(req.fullAuto): 193 | about = "" 194 | if(req.somethingAbout and req.somethingAbout != ""): 195 | about = "about " + req.somethingAbout 196 | 197 | story = connector.storyCompletor.completeStory(f"Geneate a creative {req.kind} story or novel or poem or text {about}", temperature=0.8, max_tokens=req.length) 198 | dbClient.setLastAIAction("INSPIRATIONS", json.dumps(story).encode("utf-8")) 199 | firstChoice = story["choices"][0]["text"] 200 | dbClient.saveBookContent(inspirationbook, firstChoice) 201 | image = connector.imageClient.getImageForSummary(firstChoice, size = "512x512", withStyle = req.kind) 202 | dbClient.saveBookContent(inspirationbook, firstChoice + "\n" + f"![summary](/api/v1/images/get/{image[0]})") 203 | return {"text": firstChoice, "image": image[0]} 204 | else: 205 | 206 | story = connector.storyCompletor.completeStory(req.somethingAbout, temperature=0.8, max_tokens=req.length) 207 | dbClient.setLastAIAction("INSPIRATIONS", json.dumps(story).encode("utf-8")) 208 | firstChoice = story["choices"][0]["text"] 209 | dbClient.saveBookContent(inspirationbook, firstChoice) 210 | image = connector.imageClient.getImageForSummary(firstChoice, size = "512x512", withStyle = req.kind) 211 | dbClient.saveBookContent(inspirationbook, firstChoice + "\n" + f"![summary](/api/v1/images/get/{image[0]})") 212 | 213 | 214 | return {"text": firstChoice, "image": image[0]} -------------------------------------------------------------------------------- /homepage/.overrides/assets/stylesheets/custom.9097afc2.min.css: -------------------------------------------------------------------------------- 1 | @keyframes heart { 2 | 3 | 0%, 4 | 40%, 5 | 80%, 6 | to { 7 | transform: scale(1) 8 | } 9 | 10 | 20%, 11 | 60% { 12 | transform: scale(1.15) 13 | } 14 | } 15 | 16 | .md-typeset .twitter { 17 | color: #00acee 18 | } 19 | 20 | .md-typeset .mdx-video { 21 | width: auto 22 | } 23 | 24 | .md-typeset .mdx-video__inner { 25 | height: 0; 26 | padding-bottom: 56.138%; 27 | position: relative; 28 | width: 100% 29 | } 30 | 31 | .md-typeset .mdx-video iframe { 32 | border: none; 33 | height: 100%; 34 | left: 0; 35 | overflow: hidden; 36 | position: absolute; 37 | top: 0; 38 | width: 100% 39 | } 40 | 41 | .md-typeset .mdx-heart { 42 | animation: heart 1s infinite 43 | } 44 | 45 | .md-typeset .mdx-insiders { 46 | color: #e91e63 47 | } 48 | 49 | .md-typeset .mdx-switch button { 50 | cursor: pointer; 51 | transition: opacity .25s 52 | } 53 | 54 | .md-typeset .mdx-switch button:focus, 55 | .md-typeset .mdx-switch button:hover { 56 | opacity: .75 57 | } 58 | 59 | .md-typeset .mdx-switch button>code { 60 | background-color: var(--md-primary-fg-color); 61 | color: var(--md-primary-bg-color); 62 | display: block 63 | } 64 | 65 | .md-typeset .mdx-deprecated { 66 | opacity: .5; 67 | transition: opacity .25s 68 | } 69 | 70 | .md-typeset .mdx-deprecated:focus-within, 71 | .md-typeset .mdx-deprecated:hover { 72 | opacity: 1 73 | } 74 | 75 | .md-typeset .mdx-columns ol, 76 | .md-typeset .mdx-columns ul { 77 | column-count: 2 78 | } 79 | 80 | @media screen and (max-width:29.9375em) { 81 | 82 | .md-typeset .mdx-columns ol, 83 | .md-typeset .mdx-columns ul { 84 | columns: initial 85 | } 86 | } 87 | 88 | .md-typeset .mdx-columns li { 89 | break-inside: avoid 90 | } 91 | 92 | .md-typeset .mdx-author { 93 | display: flex; 94 | font-size: .68rem 95 | } 96 | 97 | .md-typeset .mdx-author img { 98 | border-radius: 100%; 99 | height: 2rem 100 | } 101 | 102 | .md-typeset .mdx-author p:first-child { 103 | flex-shrink: 0; 104 | margin-right: .8rem 105 | } 106 | 107 | .md-typeset .mdx-author p>span { 108 | display: block 109 | } 110 | 111 | .md-banner a, 112 | .md-banner a:focus, 113 | .md-banner a:hover { 114 | color: currentcolor 115 | } 116 | 117 | .md-banner strong { 118 | white-space: nowrap 119 | } 120 | 121 | [dir=ltr] .md-banner .twitter { 122 | margin-left: .2em 123 | } 124 | 125 | [dir=rtl] .md-banner .twitter { 126 | margin-right: .2em 127 | } 128 | 129 | .mdx-container { 130 | background: url("data:image/svg+xml;utf8,") no-repeat bottom, linear-gradient(to bottom, var(--md-primary-fg-color), #00c8ff 99%, var(--md-default-bg-color) 99%); 131 | padding-top: 1rem 132 | } 133 | 134 | [data-md-color-scheme=slate] .mdx-container { 135 | background: url("data:image/svg+xml;utf8,") no-repeat bottom, linear-gradient(to bottom, var(--md-primary-fg-color), #00c8ff 99%, var(--md-default-bg-color) 99%) 136 | } 137 | 138 | .mdx-hero { 139 | color: var(--md-primary-bg-color); 140 | margin: 0 .8rem 141 | } 142 | 143 | .mdx-hero h1 { 144 | color: currentcolor; 145 | font-weight: 700; 146 | margin-bottom: 1rem 147 | } 148 | 149 | @media screen and (max-width:29.9375em) { 150 | .mdx-hero h1 { 151 | font-size: 1.4rem 152 | } 153 | } 154 | 155 | .mdx-hero__content { 156 | padding-bottom: 6rem 157 | } 158 | 159 | @media screen and (min-width:60em) { 160 | .mdx-hero { 161 | align-items: stretch; 162 | display: flex 163 | } 164 | 165 | .mdx-hero__content { 166 | margin-top: 3.5rem; 167 | max-width: 19rem; 168 | padding-bottom: 14vw 169 | } 170 | 171 | .mdx-hero__image { 172 | order: 1; 173 | transform: translateX(4rem); 174 | width: 38rem 175 | } 176 | } 177 | 178 | @media screen and (min-width:76.25em) { 179 | .mdx-hero__image { 180 | transform: translateX(8rem) 181 | } 182 | } 183 | 184 | .mdx-hero .md-button { 185 | color: var(--md-primary-bg-color); 186 | margin-right: .5rem; 187 | margin-top: .5rem 188 | } 189 | 190 | .mdx-hero .md-button:focus, 191 | .mdx-hero .md-button:hover { 192 | background-color: var(--md-accent-fg-color); 193 | border-color: var(--md-accent-fg-color); 194 | color: var(--md-accent-bg-color) 195 | } 196 | 197 | .mdx-hero .md-button--primary { 198 | background-color: var(--md-primary-bg-color); 199 | border-color: var(--md-primary-bg-color); 200 | color: #894da8 201 | } 202 | 203 | .md-typeset .mdx-iconsearch { 204 | background-color: var(--md-default-bg-color); 205 | border-radius: .1rem; 206 | box-shadow: var(--md-shadow-z1); 207 | position: relative; 208 | transition: box-shadow 125ms 209 | } 210 | 211 | .md-typeset .mdx-iconsearch:focus-within, 212 | .md-typeset .mdx-iconsearch:hover { 213 | box-shadow: var(--md-shadow-z2) 214 | } 215 | 216 | .md-typeset .mdx-iconsearch .md-input { 217 | background: var(--md-default-bg-color); 218 | box-shadow: none 219 | } 220 | 221 | [data-md-color-scheme=slate] .md-typeset .mdx-iconsearch .md-input { 222 | background: var(--md-code-bg-color) 223 | } 224 | 225 | .md-typeset .mdx-iconsearch-result { 226 | -webkit-backface-visibility: hidden; 227 | backface-visibility: hidden; 228 | max-height: 50vh; 229 | overflow-y: auto; 230 | scrollbar-color: var(--md-default-fg-color--lighter) #0000; 231 | scrollbar-width: thin; 232 | touch-action: pan-y 233 | } 234 | 235 | .md-tooltip .md-typeset .mdx-iconsearch-result { 236 | max-height: 10.25rem 237 | } 238 | 239 | .md-typeset .mdx-iconsearch-result::-webkit-scrollbar { 240 | height: .2rem; 241 | width: .2rem 242 | } 243 | 244 | .md-typeset .mdx-iconsearch-result::-webkit-scrollbar-thumb { 245 | background-color: var(--md-default-fg-color--lighter) 246 | } 247 | 248 | .md-typeset .mdx-iconsearch-result::-webkit-scrollbar-thumb:hover { 249 | background-color: var(--md-accent-fg-color) 250 | } 251 | 252 | .md-typeset .mdx-iconsearch-result__meta { 253 | color: var(--md-default-fg-color--lighter); 254 | font-size: .64rem; 255 | position: absolute; 256 | right: .6rem; 257 | top: .4rem 258 | } 259 | 260 | [dir=ltr] .md-typeset .mdx-iconsearch-result__list { 261 | margin-left: 0 262 | } 263 | 264 | [dir=rtl] .md-typeset .mdx-iconsearch-result__list { 265 | margin-right: 0 266 | } 267 | 268 | .md-typeset .mdx-iconsearch-result__list { 269 | list-style: none; 270 | margin: 0; 271 | padding: 0 272 | } 273 | 274 | [dir=ltr] .md-typeset .mdx-iconsearch-result__item { 275 | margin-left: 0 276 | } 277 | 278 | [dir=rtl] .md-typeset .mdx-iconsearch-result__item { 279 | margin-right: 0 280 | } 281 | 282 | .md-typeset .mdx-iconsearch-result__item { 283 | border-bottom: .05rem solid var(--md-default-fg-color--lightest); 284 | margin: 0; 285 | padding: .2rem .6rem 286 | } 287 | 288 | .md-typeset .mdx-iconsearch-result__item:last-child { 289 | border-bottom: none 290 | } 291 | 292 | .md-typeset .mdx-iconsearch-result__item>* { 293 | margin-right: .6rem 294 | } 295 | 296 | .md-typeset .mdx-iconsearch-result__item img { 297 | height: .9rem; 298 | width: .9rem 299 | } 300 | 301 | [data-md-color-scheme=slate] .md-typeset .mdx-iconsearch-result__item img[src*=squidfunk] { 302 | filter: invert(1) 303 | } 304 | 305 | .md-typeset .mdx-premium p { 306 | margin: 2em 0; 307 | text-align: center 308 | } 309 | 310 | .md-typeset .mdx-premium img { 311 | height: 3.25rem 312 | } 313 | 314 | .md-typeset .mdx-premium p:last-child { 315 | display: flex; 316 | flex-wrap: wrap; 317 | justify-content: center 318 | } 319 | 320 | .md-typeset .mdx-premium p:last-child>a { 321 | display: block; 322 | flex-shrink: 0 323 | } 324 | 325 | .md-typeset .mdx-sponsorship__list { 326 | margin: 2em 0 327 | } 328 | 329 | .md-typeset .mdx-sponsorship__list:after { 330 | clear: both; 331 | content: ""; 332 | display: block 333 | } 334 | 335 | [dir=ltr] .md-typeset .mdx-sponsorship__item { 336 | float: left 337 | } 338 | 339 | [dir=rtl] .md-typeset .mdx-sponsorship__item { 340 | float: right 341 | } 342 | 343 | .md-typeset .mdx-sponsorship__item { 344 | border-radius: 100%; 345 | display: block; 346 | height: 1.6rem; 347 | margin: .2rem; 348 | overflow: hidden; 349 | transform: scale(1); 350 | transition: color 125ms, transform 125ms; 351 | width: 1.6rem 352 | } 353 | 354 | .md-typeset .mdx-sponsorship__item:focus, 355 | .md-typeset .mdx-sponsorship__item:hover { 356 | transform: scale(1.1) 357 | } 358 | 359 | .md-typeset .mdx-sponsorship__item:focus img, 360 | .md-typeset .mdx-sponsorship__item:hover img { 361 | filter: grayscale(0) 362 | } 363 | 364 | .md-typeset .mdx-sponsorship__item--private { 365 | background: var(--md-default-fg-color--lightest); 366 | color: var(--md-default-fg-color--lighter); 367 | font-size: .6rem; 368 | font-weight: 700; 369 | line-height: 1.6rem; 370 | text-align: center 371 | } 372 | 373 | .md-typeset .mdx-sponsorship__item img { 374 | display: block; 375 | filter: grayscale(100%) opacity(75%); 376 | height: auto; 377 | transition: filter 125ms; 378 | width: 100% 379 | } 380 | 381 | .md-typeset .mdx-sponsorship-button { 382 | font-weight: 400 383 | } 384 | 385 | .md-typeset .mdx-sponsorship-count, 386 | .md-typeset .mdx-sponsorship-total { 387 | font-weight: 700 388 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /novela-gui/src/pages/BookEditorPage.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 106 | 107 | 330 | -------------------------------------------------------------------------------- /homepage/.overrides/assets/javascripts/custom.bb97d007.min.js: -------------------------------------------------------------------------------- 1 | "use strict";(()=>{var An=Object.create;var Cr=Object.defineProperty;var Mn=Object.getOwnPropertyDescriptor;var In=Object.getOwnPropertyNames,Zr=Object.getOwnPropertySymbols,Cn=Object.getPrototypeOf,rt=Object.prototype.hasOwnProperty,Ln=Object.prototype.propertyIsEnumerable;var et=(e,r,t)=>r in e?Cr(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,Ke=(e,r)=>{for(var t in r||(r={}))rt.call(r,t)&&et(e,t,r[t]);if(Zr)for(var t of Zr(r))Ln.call(r,t)&&et(e,t,r[t]);return e};var Oe=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports);var Rn=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of In(r))!rt.call(e,o)&&o!==t&&Cr(e,o,{get:()=>r[o],enumerable:!(n=Mn(r,o))||n.enumerable});return e};var tt=(e,r,t)=>(t=e!=null?An(Cn(e)):{},Rn(r||!e||!e.__esModule?Cr(t,"default",{value:e,enumerable:!0}):t,e));var Ne=Oe(J=>{(function(){var e,r,t,n,o,i,a,c,u,s,f,p,l,v,h,m,b,w,S,R;R=150,s=20,S=150,u=.75,J.score=function(d,x,E){var O,y,g,T;return y=E.preparedQuery,O=E.allowErrors,O||o(d,y.core_lw,y.core_up)?(T=d.toLowerCase(),g=r(d,T,y),Math.ceil(g)):0},J.isMatch=o=function(d,x,E){var O,y,g,T,P,z,C;if(g=d.length,T=x.length,!g||T>g)return!1;for(O=-1,y=-1;++y-1)return h(d,x,me,oe,se,$,q);for(_e=new Array($),P=new Array($),Ir=w($,q),V=Math.ceil(u*$)+5,ne=V,C=!0,I=-1;++I<$;)_e[I]=0,P[I]=0;for(H=-1;++Hue&&(ue=Qe),z=0,oe[I]===Ar)if(Mr=c(H,d,x),z=T>0?T:l(d,x,me,oe,H,I,Mr),g=Tr+p(H,I,Mr,y,z),g>ue)ue=g,ne=V;else{if(de&&--ne<=0)return Math.max(ue,_e[$-1])*Ir;de=!1}Tr=Qe,T=P[I],P[I]=z,_e[I]=ue}}return ue=_e[$-1],ue*Ir},J.isWordStart=c=function(d,x,E){var O,y;return d===0?!0:(O=x[d],y=x[d-1],i(y)||O!==E[d]&&y===E[d-1])},J.isWordEnd=a=function(d,x,E,O){var y,g;return d===O-1?!0:(y=x[d],g=x[d+1],i(g)||y===E[d]&&g!==E[d+1])},i=function(d){return d===" "||d==="."||d==="-"||d==="_"||d==="/"||d==="\\"},b=function(d){var x;return dy?O:y)+10):g+R*y},J.scoreConsecutives=l=function(d,x,E,O,y,g,T){var P,z,C,H,I,q,V;for(z=d.length,H=E.length,C=z-y,I=H-g,P=C-1&&(I=c(C,d,x),I&&(y=C))),z=-1,H=0;++z1&&C>1))return t;for(y=0,V=0,ne=0,I=0,T=-1,P=-1;++P-1){V++;continue}else break;for(;++T12*P)return!1;for(g=-1;++gO)return!1;return!0}}).call(J)});var _r=Oe(De=>{(function(){var e,r,t,n,o,i,a,c,u,s;s=Ne(),i=s.isMatch,e=s.computeScore,c=s.scoreSize,u=20,t=2.5,De.score=function(f,p,l){var v,h,m,b;return h=l.preparedQuery,v=l.allowErrors,v||i(f,h.core_lw,h.core_up)?(b=f.toLowerCase(),m=e(f,b,h),m=a(f,b,m,l),Math.ceil(m)):0},a=function(f,p,l,v){var h,m,b,w,S,R,d,x,E,O;if(l===0)return 0;for(E=v.preparedQuery,O=v.useExtensionBonus,x=v.pathSeparator,S=f.length-1;f[S]===x;)S--;if(b=f.lastIndexOf(x,S),d=S-b,R=1,O&&(R+=o(p,E.ext,b,S,2),l*=R),b===-1)return l;for(w=E.depth;b>-1&&w-- >0;)b=f.lastIndexOf(x,b-1);return m=b===-1?l:R*e(f.slice(b+1,S+1),p.slice(b+1,S+1),E),h=.5*u/(u+r(f,S+1,x)),h*m+(1-h)*l*c(0,t*d)},De.countDir=r=function(f,p,l){var v,h;if(p<1)return 0;for(v=0,h=-1;++hl)))return 0;for(w=p.length,m=v-S,m0?.9*o(f,p,l,S-2,h-1):b/m}}).call(De)});var Jr=Oe((cn,pn)=>{(function(){var e,r,t,n,o,i,a,c;c=_r(),t=c.countDir,o=c.getExtension,pn.exports=e=function(){function u(s,f){var p,l,v;if(v=f!=null?f:{},p=v.optCharRegEx,l=v.pathSeparator,!(s&&s.length))return null;this.query=s,this.query_lw=s.toLowerCase(),this.core=r(s,p),this.core_lw=this.core.toLowerCase(),this.core_up=a(this.core),this.depth=t(s,s.length,l),this.ext=o(this.query_lw),this.charCodes=n(this.query_lw)}return u}(),i=/[ _\-:\/\\]/g,r=function(u,s){return s==null&&(s=i),u.replace(s,"")},a=function(u){var s,f,p,l;for(f="",p=0,l=u.length;p{(function(){var e,r,t,n,o;n=Ne(),r=_r(),e=Jr(),t=function(i){return i.candidate},o=function(i,a){return a.score-i.score},mn.exports=function(i,a,c){var u,s,f,p,l,v,h,m,b,w,S,R,d;for(m=[],f=c.key,l=c.maxResults,p=c.maxInners,S=c.usePathScoring,b=p!=null&&p>0?p:i.length+1,u=f!=null,h=S?r:n,R=0,d=i.length;R0&&(m.push({candidate:s,score:v}),!--b))));R++);return m.sort(o),i=m.map(t),l!=null&&(i=i.slice(0,l)),i}}).call(ln)});var dn=Oe(Or=>{(function(){var e,r,t,n,o,i,a,c,u,s;s=Ne(),t=s.isMatch,n=s.isWordStart,u=s.scoreConsecutives,c=s.scoreCharacter,a=s.scoreAcronyms,Or.match=o=function(f,p,l){var v,h,m,b,w,S;return v=l.allowErrors,w=l.preparedQuery,b=l.pathSeparator,v||t(f,w.core_lw,w.core_up)?(S=f.toLowerCase(),m=r(f,S,w),m.length===0||f.indexOf(b)>-1&&(h=e(f,S,w,b),m=i(m,h)),m):[]},Or.wrap=function(f,p,l){var v,h,m,b,w,S,R,d,x;if(l.wrap!=null&&(x=l.wrap,S=x.tagClass,d=x.tagOpen,R=x.tagClose),S==null&&(S="highlight"),d==null&&(d=''),R==null&&(R=""),f===p)return d+f+R;if(m=o(f,p,l),m.length===0)return f;for(b="",v=-1,w=0;++vw&&(b+=f.substring(w,h),w=h);++vw&&(b+=d,b+=f.substring(w,h),b+=R,w=h)}return w<=f.length-1&&(b+=f.substring(w)),b},e=function(f,p,l,v){var h,m,b;for(b=f.length-1;f[b]===v;)b--;if(h=f.lastIndexOf(v,b),h===-1)return[];for(m=l.depth;m-- >0;)if(h=f.lastIndexOf(v,h-1),h===-1)return[];return h++,b++,r(f.slice(h,b),p.slice(h,b),l,h)},i=function(f,p){var l,v,h,m,b,w,S;if(b=f.length,w=p.length,w===0)return f.slice();if(b===0)return p.slice();for(h=-1,m=0,v=p[m],S=[];++h0?x:u(f,p,I,q,y,g,oe),R=ne+c(y,g,oe,S,O)),se=$[g],x=E[g],V>se?z=m:(V=se,z=w),R>V?(V=R,z=h):O=0,$[g]=V,E[g]=O,de[++H]=V>0?z:b;for(y=T-1,g=C-1,H=y*C+g,d=!0,P=[];d&&y>=0&&g>=0;)switch(de[H]){case w:y--,H-=C;break;case m:g--,H--;break;case h:P.push(y+v),g--,y--,H-=C+1;break;default:d=!1}return P.reverse(),P}}).call(Or)});var Br=Oe((vn,bn)=>{(function(){var e,r,t,n,o,i,a,c;t=hn(),n=dn(),c=Ne(),i=_r(),e=Jr(),a=null,r=(typeof process!="undefined"&&process!==null?process.platform:void 0)==="win32"?"\\":"/",bn.exports={filter:function(u,s,f){return f==null&&(f={}),(s!=null?s.length:void 0)&&(u!=null?u.length:void 0)?(f=o(f,s),t(u,s,f)):[]},score:function(u,s,f){return f==null&&(f={}),(u!=null?u.length:void 0)&&(s!=null?s.length:void 0)?(f=o(f,s),f.usePathScoring?i.score(u,s,f):c.score(u,s,f)):0},match:function(u,s,f){var p,l,v;return f==null&&(f={}),u?s?u===s?function(){v=[];for(var h=0,m=u.length;0<=m?hm;0<=m?h++:h--)v.push(h);return v}.apply(this):(f=o(f,s),n.match(u,s,f)):[]:[]},wrap:function(u,s,f){return f==null&&(f={}),u?s?(f=o(f,s),n.wrap(u,s,f)):[]:[]},prepareQuery:function(u,s){return s==null&&(s={}),s=o(s,u),s.preparedQuery}},o=function(u,s){return u.allowErrors==null&&(u.allowErrors=!1),u.usePathScoring==null&&(u.usePathScoring=!0),u.useExtensionBonus==null&&(u.useExtensionBonus=!1),u.pathSeparator==null&&(u.pathSeparator=r),u.optCharRegEx==null&&(u.optCharRegEx=null),u.wrap==null&&(u.wrap=null),u.preparedQuery==null&&(u.preparedQuery=a&&a.query===s?a:a=new e(s,u)),u}}).call(vn)});/*! ***************************************************************************** 2 | Copyright (c) Microsoft Corporation. 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | ***************************************************************************** */var Lr=function(e,r){return Lr=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(t[o]=n[o])},Lr(e,r)};function j(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");Lr(e,r);function t(){this.constructor=e}e.prototype=r===null?Object.create(r):(t.prototype=r.prototype,new t)}function nt(e,r,t,n){function o(i){return i instanceof t?i:new t(function(a){a(i)})}return new(t||(t=Promise))(function(i,a){function c(f){try{s(n.next(f))}catch(p){a(p)}}function u(f){try{s(n.throw(f))}catch(p){a(p)}}function s(f){f.done?i(f.value):o(f.value).then(c,u)}s((n=n.apply(e,r||[])).next())})}function Ge(e,r){var t={label:0,sent:function(){if(i[0]&1)throw i[1];return i[1]},trys:[],ops:[]},n,o,i,a;return a={next:c(0),throw:c(1),return:c(2)},typeof Symbol=="function"&&(a[Symbol.iterator]=function(){return this}),a;function c(s){return function(f){return u([s,f])}}function u(s){if(n)throw new TypeError("Generator is already executing.");for(;t;)try{if(n=1,o&&(i=s[0]&2?o.return:s[0]?o.throw||((i=o.return)&&i.call(o),0):o.next)&&!(i=i.call(o,s[1])).done)return i;switch(o=0,i&&(s=[s[0]&2,i.value]),s[0]){case 0:case 1:i=s;break;case 4:return t.label++,{value:s[1],done:!1};case 5:t.label++,o=s[1],s=[0];continue;case 7:s=t.ops.pop(),t.trys.pop();continue;default:if(i=t.trys,!(i=i.length>0&&i[i.length-1])&&(s[0]===6||s[0]===2)){t=0;continue}if(s[0]===3&&(!i||s[1]>i[0]&&s[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(r?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,r){var t=typeof Symbol=="function"&&e[Symbol.iterator];if(!t)return e;var n=t.call(e),o,i=[],a;try{for(;(r===void 0||r-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(c){a={error:c}}finally{try{o&&!o.done&&(t=n.return)&&t.call(n)}finally{if(a)throw a.error}}return i}function U(e,r,t){if(t||arguments.length===2)for(var n=0,o=r.length,i;n1||c(l,v)})})}function c(l,v){try{u(n[l](v))}catch(h){p(i[0][3],h)}}function u(l){l.value instanceof ve?Promise.resolve(l.value.v).then(s,f):p(i[0][2],l)}function s(l){c("next",l)}function f(l){c("throw",l)}function p(l,v){l(v),i.shift(),i.length&&c(i[0][0],i[0][1])}}function it(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var r=e[Symbol.asyncIterator],t;return r?r.call(e):(e=typeof G=="function"?G(e):e[Symbol.iterator](),t={},n("next"),n("throw"),n("return"),t[Symbol.asyncIterator]=function(){return this},t);function n(i){t[i]=e[i]&&function(a){return new Promise(function(c,u){a=e[i](a),o(c,u,a.done,a.value)})}}function o(i,a,c,u){Promise.resolve(u).then(function(s){i({value:s,done:c})},a)}}function _(e){return typeof e=="function"}function Ye(e){var r=function(n){Error.call(n),n.stack=new Error().stack},t=e(r);return t.prototype=Object.create(Error.prototype),t.prototype.constructor=t,t}var Je=Ye(function(e){return function(t){e(this),this.message=t?t.length+` errors occurred during unsubscription: 15 | `+t.map(function(n,o){return o+1+") "+n.toString()}).join(` 16 | `):"",this.name="UnsubscriptionError",this.errors=t}});function fe(e,r){if(e){var t=e.indexOf(r);0<=t&&e.splice(t,1)}}var ie=function(){function e(r){this.initialTeardown=r,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var r,t,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var c=G(a),u=c.next();!u.done;u=c.next()){var s=u.value;s.remove(this)}}catch(m){r={error:m}}finally{try{u&&!u.done&&(t=c.return)&&t.call(c)}finally{if(r)throw r.error}}else a.remove(this);var f=this.initialTeardown;if(_(f))try{f()}catch(m){i=m instanceof Je?m.errors:[m]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var l=G(p),v=l.next();!v.done;v=l.next()){var h=v.value;try{at(h)}catch(m){i=i!=null?i:[],m instanceof Je?i=U(U([],W(i)),W(m.errors)):i.push(m)}}}catch(m){n={error:m}}finally{try{v&&!v.done&&(o=l.return)&&o.call(l)}finally{if(n)throw n.error}}}if(i)throw new Je(i)}},e.prototype.add=function(r){var t;if(r&&r!==this)if(this.closed)at(r);else{if(r instanceof e){if(r.closed||r._hasParent(this))return;r._addParent(this)}(this._finalizers=(t=this._finalizers)!==null&&t!==void 0?t:[]).push(r)}},e.prototype._hasParent=function(r){var t=this._parentage;return t===r||Array.isArray(t)&&t.includes(r)},e.prototype._addParent=function(r){var t=this._parentage;this._parentage=Array.isArray(t)?(t.push(r),t):t?[t,r]:r},e.prototype._removeParent=function(r){var t=this._parentage;t===r?this._parentage=null:Array.isArray(t)&&fe(t,r)},e.prototype.remove=function(r){var t=this._finalizers;t&&fe(t,r),r instanceof e&&r._removeParent(this)},e.EMPTY=function(){var r=new e;return r.closed=!0,r}(),e}();var Rr=ie.EMPTY;function Be(e){return e instanceof ie||e&&"closed"in e&&_(e.remove)&&_(e.add)&&_(e.unsubscribe)}function at(e){_(e)?e():e.unsubscribe()}var te={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var Te={setTimeout:function(e,r){for(var t=[],n=2;n0},enumerable:!1,configurable:!0}),r.prototype._trySubscribe=function(t){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,t)},r.prototype._subscribe=function(t){return this._throwIfClosed(),this._checkFinalizedStatuses(t),this._innerSubscribe(t)},r.prototype._innerSubscribe=function(t){var n=this,o=this,i=o.hasError,a=o.isStopped,c=o.observers;return i||a?Rr:(this.currentObservers=null,c.push(t),new ie(function(){n.currentObservers=null,fe(c,t)}))},r.prototype._checkFinalizedStatuses=function(t){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?t.error(i):a&&t.complete()},r.prototype.asObservable=function(){var t=new L;return t.source=this,t},r.create=function(t,n){return new ht(t,n)},r}(L);var ht=function(e){j(r,e);function r(t,n){var o=e.call(this)||this;return o.destination=t,o.source=n,o}return r.prototype.next=function(t){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,t)},r.prototype.error=function(t){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,t)},r.prototype.complete=function(){var t,n;(n=(t=this.destination)===null||t===void 0?void 0:t.complete)===null||n===void 0||n.call(t)},r.prototype._subscribe=function(t){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(t))!==null&&o!==void 0?o:Rr},r}(B);var ze={now:function(){return(ze.delegate||Date).now()},delegate:void 0};var dt=function(e){j(r,e);function r(t,n,o){t===void 0&&(t=1/0),n===void 0&&(n=1/0),o===void 0&&(o=ze);var i=e.call(this)||this;return i._bufferSize=t,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,t),i._windowTime=Math.max(1,n),i}return r.prototype.next=function(t){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,c=n._timestampProvider,u=n._windowTime;o||(i.push(t),!a&&i.push(c.now()+u)),this._trimBuffer(),e.prototype.next.call(this,t)},r.prototype._subscribe=function(t){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(t),o=this,i=o._infiniteTimeWindow,a=o._buffer,c=a.slice(),u=0;u0?e.prototype.requestAsyncId.call(this,t,n,o):(t.actions.push(this),t._scheduled||(t._scheduled=Ie.requestAnimationFrame(function(){return t.flush(void 0)})))},r.prototype.recycleAsyncId=function(t,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,t,n,o);var a=t.actions;n!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(Ie.cancelAnimationFrame(n),t._scheduled=void 0)},r}(er);var yt=function(e){j(r,e);function r(){return e!==null&&e.apply(this,arguments)||this}return r.prototype.flush=function(t){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;t=t||o.shift();do if(i=t.execute(t.state,t.delay))break;while((t=o[0])&&t.id===n&&o.shift());if(this._active=!1,i){for(;(t=o[0])&&t.id===n&&o.shift();)t.unsubscribe();throw i}},r}(rr);var Wr=new yt(xt);var pe=new L(function(e){return e.complete()});function tr(e){return e&&_(e.schedule)}function zr(e){return e[e.length-1]}function Ce(e){return _(zr(e))?e.pop():void 0}function ae(e){return tr(zr(e))?e.pop():void 0}function gt(e,r){return typeof zr(e)=="number"?e.pop():r}var Le=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function nr(e){return _(e==null?void 0:e.then)}function or(e){return _(e[Me])}function ir(e){return Symbol.asyncIterator&&_(e==null?void 0:e[Symbol.asyncIterator])}function ar(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function jn(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var sr=jn();function ur(e){return _(e==null?void 0:e[sr])}function fr(e){return ot(this,arguments,function(){var t,n,o,i;return Ge(this,function(a){switch(a.label){case 0:t=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,ve(t.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,ve(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,ve(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return t.releaseLock(),[7];case 10:return[2]}})})}function cr(e){return _(e==null?void 0:e.getReader)}function F(e){if(e instanceof L)return e;if(e!=null){if(or(e))return qn(e);if(Le(e))return $n(e);if(nr(e))return Nn(e);if(ir(e))return wt(e);if(ur(e))return Dn(e);if(cr(e))return Qn(e)}throw ar(e)}function qn(e){return new L(function(r){var t=e[Me]();if(_(t.subscribe))return t.subscribe(r);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function $n(e){return new L(function(r){for(var t=0;t0&&(f=new xe({next:function(E){return x.next(E)},error:function(E){m=!0,b(),p=Qr(w,o,E),x.error(E)},complete:function(){h=!0,b(),p=Qr(w,a),x.complete()}}),F(R).subscribe(f))})(s)}}function Qr(e,r){for(var t=[],n=2;n{let r=yr();return typeof r!="undefined"?e.contains(r):!1}),he(e===yr()),le())}function Dt(e){return{x:e.scrollLeft,y:e.scrollTop}}function Qt(e){return N(K(e,"scroll"),K(window,"resize")).pipe(Vr(0,Wr),k(()=>Dt(e)),he(Dt(e)))}var Gt=function(){if(typeof Map!="undefined")return Map;function e(r,t){var n=-1;return r.some(function(o,i){return o[0]===t?(n=i,!0):!1}),n}return function(){function r(){this.__entries__=[]}return Object.defineProperty(r.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),r.prototype.get=function(t){var n=e(this.__entries__,t),o=this.__entries__[n];return o&&o[1]},r.prototype.set=function(t,n){var o=e(this.__entries__,t);~o?this.__entries__[o][1]=n:this.__entries__.push([t,n])},r.prototype.delete=function(t){var n=this.__entries__,o=e(n,t);~o&&n.splice(o,1)},r.prototype.has=function(t){return!!~e(this.__entries__,t)},r.prototype.clear=function(){this.__entries__.splice(0)},r.prototype.forEach=function(t,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!Gr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),vo?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!Gr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(r){var t=r.propertyName,n=t===void 0?"":t,o=ho.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Yt=function(e,r){for(var t=0,n=Object.keys(r);t0},e}(),Bt=typeof WeakMap!="undefined"?new WeakMap:new Gt,Xt=function(){function e(r){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var t=bo.getInstance(),n=new Ao(r,t,this);Bt.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Xt.prototype[e]=function(){var r;return(r=Bt.get(this))[e].apply(r,arguments)}});var Mo=function(){return typeof gr.ResizeObserver!="undefined"?gr.ResizeObserver:Xt}(),Zt=Mo;var Io=new B,Gp=je(()=>Z(new Zt(e=>{for(let r of e)Io.next(r)}))).pipe(D(e=>N(qe,Z(e)).pipe(Ee(()=>e.disconnect()))),Se(1));function en(e){return{width:e.offsetWidth,height:e.offsetHeight}}function rn(e){return{width:e.scrollWidth,height:e.scrollHeight}}var Co=new B,nl=je(()=>Z(new IntersectionObserver(e=>{for(let r of e)Co.next(r)},{threshold:0}))).pipe(D(e=>N(qe,Z(e)).pipe(Ee(()=>e.disconnect()))),Se(1));function tn(e,r=16){return Qt(e).pipe(k(({y:t})=>{let n=en(e),o=rn(e);return t>=o.height-n.height-r}),le())}var ll={drawer:ee("[data-md-toggle=drawer]"),search:ee("[data-md-toggle=search]")};function nn(){return new URL(location.href)}function on(e,r){if(typeof r=="string"||typeof r=="number")e.innerHTML+=r.toString();else if(r instanceof Node)e.appendChild(r);else if(Array.isArray(r))for(let t of r)on(e,t)}function re(e,r,...t){let n=document.createElement(e);if(r)for(let o of Object.keys(r))typeof r[o]!="undefined"&&(typeof r[o]!="boolean"?n.setAttribute(o,r[o]):n.setAttribute(o,""));for(let o of t)on(n,o);return n}function an(e){if(e>999){let r=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(r)}k`}else return e.toString()}function Lo(e,r={credentials:"same-origin"}){return X(fetch(`${e}`,r)).pipe(br(()=>pe),D(t=>t.status!==200?Ur(()=>new Error(t.statusText)):Z(t)))}function Sr(e,r){return Lo(e,r).pipe(D(t=>t.json()),Se(1))}function He(e,r=document){return ee(`[data-mdx-component=${e}]`,r)}function Yr(e,r=document){return qt(`[data-mdx-component=${e}]`,r)}var Ro=ee("#__config"),$e=JSON.parse(Ro.textContent);$e.base=`${new URL($e.base,nn())}`;function sn(){return $e}function un(e,r){return typeof r!="undefined"?$e.translations[e].replace("#",r.toString()):$e.translations[e]}function fn(e){let r=Nt(e),t=N(K(e,"keyup"),K(e,"focus").pipe(Dr(1))).pipe(k(()=>e.value),he(e.value),le());return r.pipe(we(n=>!n),Fe(t)).subscribe(([,n])=>{let o=document.location.pathname;typeof ga=="function"&&n.length&&ga("send","pageview",`${o}?q=[icon]+${n}`)}),Re([t,r]).pipe(k(([n,o])=>({ref:e,value:n,focus:o})))}var Xr=tt(Br());var yn=tt(Br());function xn(e,r){return(0,yn.wrap)(e.shortcode,r,{wrap:{tagOpen:"",tagClose:""}})}function gn(e,r,t){return re("li",{class:"mdx-iconsearch-result__item"},re("span",{class:"twemoji"},re("img",{src:e.url})),re("button",{class:"md-clipboard--inline",title:un("clipboard.copy"),"data-clipboard-text":t?e.shortcode:`:${e.shortcode}:`},re("code",null,t?xn(e,r):`:${xn(e,r)}:`)))}function wn(e){let r=`@${e.name}`;return re("a",{href:e.url,title:r,class:"mdx-sponsorship__item"},re("img",{src:e.image}))}function En(e){return re("a",{href:"https://github.com/sponsors/squidfunk",class:"mdx-sponsorship__item mdx-sponsorship__item--private"},"+",e)}function Po(e,{index$:r,query$:t}){switch(e.getAttribute("data-mdx-mode")){case"file":return Re([t.pipe(xr("value")),r.pipe(k(({icons:n})=>Object.values(n.data).map(o=>o.replace(/\.svg$/,""))))]).pipe(k(([{value:n},o])=>(0,Xr.filter)(o,n)),D(n=>r.pipe(k(({icons:o})=>({data:n.map(i=>({shortcode:i,url:[o.base,i,".svg"].join("")}))})))));default:return Re([t.pipe(xr("value")),r.pipe(k(({icons:n,emojis:o})=>[...Object.keys(n.data),...Object.keys(o.data)]))]).pipe(k(([{value:n},o])=>(0,Xr.filter)(o,n)),D(n=>r.pipe(k(({icons:o,emojis:i})=>({data:n.map(a=>{let c=a in o.data?o:i;return{shortcode:a,url:[c.base,c.data[a]].join("")}})})))))}}function Sn(e,{index$:r,query$:t}){let n=new B,o=tn(e).pipe(we(Boolean)),i=ee(":scope > :first-child",e);n.pipe(Fe(t)).subscribe(([{data:u},{value:s}])=>{if(s)switch(u.length){case 0:i.textContent="No matches";break;case 1:i.textContent="1 match";break;default:i.textContent=`${an(u.length)} matches`}else i.textContent="Type to start searching"});let a=e.getAttribute("data-mdx-mode")==="file",c=ee(":scope > :last-child",e);return n.pipe(Pe(()=>c.innerHTML=""),D(({data:u})=>N(Z(...u.slice(0,10)),Z(...u.slice(10)).pipe(jr(10),Kr(o),D(([s])=>s)))),Fe(t)).subscribe(([u,{value:s}])=>c.appendChild(gn(u,s,a))),Po(e,{query$:t,index$:r}).pipe(Pe(u=>n.next(u)),Ee(()=>n.complete()),k(u=>Ke({ref:e},u)))}function _n(e){let r=sn(),t=Sr(new URL("assets/javascripts/iconsearch_index.json",r.base)),n=He("iconsearch-query",e),o=He("iconsearch-result",e),i=fn(n),a=Sn(o,{index$:t,query$:i});return N(i,a)}function On(e){let r=Sr("https://3if8u9o552.execute-api.us-east-1.amazonaws.com/_/"),t=He("sponsorship-count"),n=He("sponsorship-total");return r.subscribe(o=>{e.removeAttribute("hidden");let i=ee(":scope > :first-child",e);for(let a of o.sponsors)a.type==="public"&&i.appendChild(wn(a.user));i.appendChild(En(o.sponsors.filter(({type:a})=>a==="private").length)),t.innerText=`${o.sponsors.length}`,n.innerText=`$ ${o.total.toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")} a month`}),r.pipe(k(o=>Ke({ref:e},o)))}function Tn(){let{origin:e}=new URL(location.href);K(document.body,"click").subscribe(r=>{if(r.target instanceof HTMLElement){let t=r.target.closest("a");t&&t.origin!==e&&ga("send","event","outbound","click",t.href)}})}Tn();var Fo=document$.pipe(D(()=>N(...Yr("iconsearch").map(e=>_n(e)),...Yr("sponsorship").map(e=>On(e)))));Fo.subscribe();})(); 17 | //# sourceMappingURL=custom.bb97d007.min.js.map 18 | 19 | --------------------------------------------------------------------------------