├── .cursor └── rules │ ├── cursor-rules.mdc │ ├── dev-environment.mdc │ ├── layout.mdc │ └── wordpress-coding-standards │ ├── wordpress-coding-standards-javascript.mdc │ └── wordpress-coding-standards-php.mdc ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .nvmrc ├── .wp-env.json ├── README.md ├── class-pos-settings.php ├── composer.json ├── composer.lock ├── dashboard.php ├── docs ├── CNAME ├── INSTALL.md ├── assets │ ├── notes.png │ └── todos.png ├── index.html └── todo.md ├── modules ├── bucketlist │ └── class-bucketlist-module.php ├── class-pos-module.php ├── crm │ └── class-crm-module.php ├── daily │ └── class-daily-module.php ├── evernote │ ├── README.md │ └── class-evernote-module.php ├── notes │ ├── README.md │ ├── admin-widgets.css │ ├── class-notes-module.php │ └── starter-content.php ├── openai │ ├── README.md │ ├── assets │ │ └── voice-chat.js │ ├── chat-page.php │ ├── chatgpt_routes.json │ ├── class-elevenlabs-module.php │ ├── class-ollama.php │ ├── class-openai-endpoints.php │ ├── class-openai-module.php │ ├── class-openai-tool.php │ ├── class-pos-ai-podcast-module.php │ ├── class-pos-transcription.php │ ├── class.vercel-ai-sdk.php │ ├── hype-player.js │ └── podcast-assets │ │ ├── motivation-st-1.m4a │ │ └── motivation-st-2.m4a ├── perplexity │ └── class.perplexity-module.php ├── readwise │ ├── README.md │ └── class-readwise.php ├── slack │ └── class-slack-module.php └── todo │ ├── README.md │ ├── class-ics-module.php │ ├── class-todo-module.php │ └── starter-content.php ├── package-lock.json ├── package.json ├── personalos.php ├── phpcs.xml ├── phpunit.xml.dist ├── src-chatbot ├── .eslintrc.json ├── README.md ├── app │ ├── (auth) │ │ ├── actions.ts │ │ ├── login │ │ │ └── page.tsx │ │ └── register │ │ │ └── page.tsx │ ├── (chat) │ │ ├── actions.ts │ │ ├── chat │ │ │ └── [id] │ │ │ │ └── page_disabled.tsx │ │ ├── layout.tsx │ │ ├── opengraph-image.png │ │ ├── page.tsx │ │ └── twitter-image.png │ ├── favicon.ico │ ├── globals.css │ └── layout.tsx ├── artifacts │ ├── actions.ts │ ├── code │ │ ├── client.tsx │ │ └── server.ts │ ├── image │ │ ├── client.tsx │ │ └── server.ts │ ├── sheet │ │ ├── client.tsx │ │ └── server.ts │ └── text │ │ ├── client.tsx │ │ └── server.ts ├── biome.jsonc ├── components.json ├── components │ ├── app-sidebar.tsx │ ├── artifact-actions.tsx │ ├── artifact-close-button.tsx │ ├── artifact-messages.tsx │ ├── artifact.tsx │ ├── auth-form.tsx │ ├── chat-header.tsx │ ├── chat.tsx │ ├── client-only.tsx │ ├── code-block.tsx │ ├── code-editor.tsx │ ├── console.tsx │ ├── create-artifact.tsx │ ├── data-stream-handler.tsx │ ├── diffview.tsx │ ├── document-preview.tsx │ ├── document-skeleton.tsx │ ├── document.tsx │ ├── greeting.tsx │ ├── icons.tsx │ ├── image-editor.tsx │ ├── markdown.tsx │ ├── message-actions.tsx │ ├── message-editor.tsx │ ├── message-reasoning.tsx │ ├── message.tsx │ ├── messages.tsx │ ├── model-selector.tsx │ ├── multimodal-input.tsx │ ├── preview-attachment.tsx │ ├── sheet-editor.tsx │ ├── sidebar-history-item.tsx │ ├── sidebar-history.tsx │ ├── sidebar-toggle.tsx │ ├── sidebar-user-nav.tsx │ ├── sign-out-form.tsx │ ├── submit-button.tsx │ ├── suggested-actions.tsx │ ├── suggestion.tsx │ ├── text-editor.tsx │ ├── theme-provider.tsx │ ├── toast.tsx │ ├── toolbar.tsx │ ├── ui │ │ ├── alert-dialog.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── dropdown-menu.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── textarea.tsx │ │ └── tooltip.tsx │ ├── use-scroll-to-bottom.ts │ ├── version-footer.tsx │ ├── visibility-selector.tsx │ └── weather.tsx ├── hooks │ ├── use-artifact.ts │ ├── use-chat-visibility.ts │ └── use-mobile.tsx ├── lib │ ├── ai │ │ ├── entitlements.ts │ │ ├── models.test.ts │ │ ├── models.ts │ │ ├── prompts.ts │ │ ├── providers.ts │ │ └── tools │ │ │ ├── create-document.ts │ │ │ ├── get-weather.ts │ │ │ ├── request-suggestions.ts │ │ │ └── update-document.ts │ ├── artifacts │ │ └── server.ts │ ├── constants.ts │ ├── editor │ │ ├── config.ts │ │ ├── diff.js │ │ ├── functions.tsx │ │ ├── react-renderer.tsx │ │ └── suggestions.tsx │ ├── utils.ts │ └── window.d.ts ├── next-env.d.ts ├── next.config.ts ├── package.json ├── playwright.config.ts ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public │ ├── images │ │ ├── apple-icon.png │ │ ├── demo-thumbnail.png │ │ ├── favicon.ico │ │ └── mouth of the seine, monet.jpg │ ├── manifest.json │ └── mock-chat-request.json ├── staticplan.md ├── tailwind.config.ts ├── tests │ ├── e2e │ │ ├── artifacts.test.ts │ │ ├── chat.test.ts │ │ ├── reasoning.test.ts │ │ └── session.test.ts │ ├── fixtures.ts │ ├── helpers.ts │ ├── pages │ │ ├── artifact.ts │ │ ├── auth.ts │ │ └── chat.ts │ ├── prompts │ │ ├── basic.ts │ │ ├── routes.ts │ │ └── utils.ts │ └── routes │ │ ├── chat.test.ts │ │ └── document.test.ts └── tsconfig.json ├── src ├── components │ ├── notebook-selector-tab-panel.js │ └── todo-form.js ├── index.js ├── notebooks │ ├── notebooks.js │ └── style.scss ├── notes │ ├── blocks │ │ ├── describe_img │ │ │ ├── block.json │ │ │ └── index.js │ │ └── note │ │ │ ├── block.json │ │ │ ├── edit.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── save.js │ └── plugin.js ├── openai │ └── blocks │ │ └── tool │ │ ├── block.json │ │ └── index.js ├── readwise │ └── blocks │ │ ├── book-summary │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.css │ │ ├── index.js │ │ └── save.js │ │ └── readwise │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.css │ │ ├── index.js │ │ └── save.js ├── todo │ └── todo.js └── utils │ └── notebook.js ├── tests ├── bootstrap.php ├── integration │ └── EvernoteModuleIntegrationTest.php └── unit │ ├── EvernoteModuleTest.php │ └── TodoModuleTest.php └── webpack.config.js /.cursor/rules/cursor-rules.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Include this whenever discussing cursor rules. This has important information how to set up cursor rules for the project 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Creating new Cursor rules 7 | 8 | This rule explains exactly how to create new Cursor rules for this project. 9 | 10 | ## Cursor rules location 11 | 12 | Always place rule files in PROJECT_ROOT/.cursor/rules/: 13 | 14 | IMPORTANT: never place rule files in any location outside of `.cursor/rules` 15 | 16 | ## Cursor rule conventions 17 | 18 | - Follow the below naming convention: 19 | - Use kebab-case for filenames 20 | - Always use .mdc extension 21 | - Make names descriptive of the rule's purpose 22 | - You can link to other files in the project using Markdown link prefixed with mdc: (Example: `@app/Services/SeoService.php`) 23 | - Only ever link directly to a file and not a directory 24 | 25 | ## Cursor rules file structure 26 | 27 | Cursor rules are in the format of MDC (.mdc) which is like a regular Markdown file with frontmatter context. Below is an example: 28 | 29 | ``` 30 | --- 31 | description: 32 | globs: 33 | alwaysApply: true/false (default to false unless otherwise instructed) 34 | --- 35 | # Rule Title 36 | 37 | Main content explaining the rule with markdown formatting. 38 | 39 | 1. Step-by-step instructions 40 | 2. Code examples 41 | 3. Guidelines 42 | ``` 43 | 44 | Full rule example: 45 | 46 | ``` 47 | --- 48 | description: This rule explains how to work with AI APIs like OpenAI and Anthropic 49 | globs: 50 | alwaysApply: false 51 | --- 52 | # How to handle AI integrations 53 | 54 | Whenever you use this rule, start your message with the following: 55 | 56 | "Analysing AI integrations..." 57 | 58 | - This project integrates with Anthropic and OpenAI via dedicated client classes located in @app/Clients. 59 | - Service classes, potentially found in @app/Services, may abstract interactions with these clients. 60 | - Ensure necessary API keys and configuration are present in the `.env` file (refer to `.env.example`). 61 | - Follow existing patterns within the client or service classes for making API calls. 62 | - Implement consistent error handling for API failures. 63 | ``` -------------------------------------------------------------------------------- /.cursor/rules/dev-environment.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | The dev environment is managed by the `wp-env` package. 7 | In order to run code and run tests, you need to use the proper syntax to run commands. 8 | 9 | For example: 10 | 11 | - To run unit tests, you would `npm run test:unit` 12 | - To read the debug.log you would `npm run wp-env run cli -- tail -n 100 wp-content/debug.log` 13 | 14 | Feel free to do both as often as you think is reasonable. DO NOT run local commands on this machine, as the wp-env manages the environment in a docker container. -------------------------------------------------------------------------------- /.cursor/rules/layout.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | This is a repository with a project called PersonalOS. 7 | It is a WordPress plugin that turns WordPress into a personal productivity system. 8 | 9 | Here are the different directories: 10 | 11 | - `src-chatbot` is a directory with a NextJS app that DOES NOT conform to WordPress rules. DO NOT apply the wordpress formatting rules there. This app builds to the `build/chatbot` and is included in WordPress plugin distribution 12 | - `src` are the JS components and Gutenberg blocks for the plugin 13 | - `modules` are different modules for the plugin. Each functionality is wrapped in a module inheriting `class-pos-module.php` 14 | -------------------------------------------------------------------------------- /.cursor/rules/wordpress-coding-standards/wordpress-coding-standards-javascript.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Official WordPress coding standards from https://github.com/WordPress/wpcs-docs/blob/master/wordpress-coding-standards/javascript.md 3 | globs: src/*.js,src/*.jsx,src/*.ts,src/*.tsc 4 | alwaysApply: false 5 | --- 6 | # WordPress JavaScript Coding Standards 7 | Source: https://github.com/WordPress/wpcs-docs/blob/master/wordpress-coding-standards/javascript.md 8 | 9 | The WordPress JavaScript Coding Standards adapt the jQuery JavaScript Style Guide with these key modifications: 10 | - Single quotes for strings instead of double quotes 11 | - Case statements indented within switch blocks 12 | - Consistent function content indentation 13 | - WordPress-specific whitespace rules 14 | - 100-character line limit encouraged but not enforced 15 | 16 | ## Key Formatting Rules 17 | - Indent with tabs, not spaces 18 | - Liberal use of whitespace for readability 19 | - Braces required for all blocks (if/else/for/while/try) 20 | - Each statement on its own line 21 | - Strict equality checks (`===`) over abstract equality (`==`) 22 | - Single quotes for strings 23 | 24 | ## Variable Declaration 25 | - ES2015+: Use `const` by default, `let` when reassignment needed 26 | - Pre-ES2015: Single `var` statement at top of function scope 27 | - CamelCase for variables and functions 28 | - UpperCamelCase for constructors and classes 29 | - SCREAMING_SNAKE_CASE for constants 30 | 31 | ## Code Organization 32 | - Functions should be concise and focused 33 | - Multi-line statements break after operators 34 | - Comments precede relevant code with blank line before 35 | - JSDoc format for documentation 36 | 37 | ## Best Practices 38 | - Array creation with `[]`, not `new Array()` 39 | - Object creation with `{}` unless specific prototype needed 40 | - Store loop maximum in variable rather than recalculating 41 | - Use Underscore.js collection methods for data transformations 42 | - Only use jQuery each() for jQuery collections 43 | 44 | ## Tools 45 | - JSHint for automated code quality checking 46 | - Configuration in `.jshintrc` file 47 | -------------------------------------------------------------------------------- /.cursor/rules/wordpress-coding-standards/wordpress-coding-standards-php.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Official WordPress coding standards from https://github.com/WordPress/wpcs-docs/blob/master/wordpress-coding-standards/php.md 3 | globs: *.php 4 | alwaysApply: false 5 | --- 6 | 7 | # WordPress PHP Coding Standards Summary 8 | Source: https://github.com/WordPress/wpcs-docs/blob/master/wordpress-coding-standards/php.md 9 | 10 | ## General Rules 11 | - Use full PHP tags (``, never shorthand) 12 | - Single quotes for strings without variables, double quotes when evaluating content 13 | - No parentheses for `require`/`include` statements; use `require_once` for dependencies 14 | - Files should end without closing PHP tag 15 | 16 | ## Naming Conventions 17 | - Variables, functions, action/filter hooks: lowercase with underscores (`some_function_name`) 18 | - Classes, traits, interfaces, enums: capitalized words with underscores (`WP_Error`, `Walker_Category`) 19 | - Constants: uppercase with underscores (`DOING_AJAX`) 20 | - File names: lowercase with hyphens (`my-plugin-name.php`) 21 | - Class files: prefix with `class-` (`class-wp-error.php`) 22 | 23 | ## Whitespace & Indentation 24 | - Use tabs for indentation, spaces for mid-line alignment 25 | - Space after commas and around operators 26 | - Space inside parentheses 27 | - No space between function/method name and opening parenthesis 28 | - No trailing whitespace at line ends 29 | - Array items on new lines for multi-item arrays 30 | 31 | ## Formatting 32 | - Always use braces for control structures 33 | - Arrays must use long syntax `array()` (not `[]`) 34 | - One space between closing parenthesis and colon in return type declarations 35 | - Always include trailing comma in multi-line arrays 36 | 37 | ## Object-Oriented Programming 38 | - One class/interface/trait/enum per file 39 | - Always declare visibility (`public`, `protected`, `private`) 40 | - Correct modifier order (e.g., `abstract` then `readonly` for class declarations) 41 | - Always use parentheses for object instantiation 42 | 43 | ## Control Structures 44 | - Use `elseif` (not `else if`) 45 | - Yoda conditions (`if ( true === $var )` not `if ( $var === true )`) 46 | - Use braces even for single-statement blocks 47 | 48 | ## Operators 49 | - Ternary operators should test for true, not false 50 | - Avoid error control operator (`@`) 51 | - Prefer pre-increment/decrement (`++$i`) over post-increment/decrement (`$i++`) 52 | 53 | ## Database 54 | - Avoid direct database queries when possible 55 | - Capitalize SQL keywords (`SELECT`, `UPDATE`, etc.) 56 | - Use `$wpdb->prepare()` for secure queries 57 | - Use placeholders (`%d`, `%f`, `%s`, `%i`) without quotes 58 | 59 | ## Recommendations 60 | - Use descriptive string values instead of boolean flags 61 | - Prioritize readability over cleverness 62 | - Use strict comparisons (`===` and `!==`) 63 | - No assignments in conditionals 64 | - Avoid `extract()`, `eval()`, `create_function()` and backtick operators 65 | - Use PCRE over POSIX for regular expressions 66 | 67 | ## Type Declarations 68 | - Use lowercase for scalar types (`int`, `bool`, `string`, etc.) 69 | - Ensure one space before and after type declarations 70 | - No space between nullability operator (`?`) and type 71 | - Obey PHP version compatibility constraints when using types 72 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Upload Plugin ZIP 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | build-and-zip: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout Repository 12 | uses: actions/checkout@v4 13 | 14 | - name: Set up Node.js 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 20 # Use Node.js v20 18 | 19 | - name: Install Node.js Dependencies 20 | run: npm install 21 | 22 | - name: Set up PHP and Composer 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: '8.2' # Adjust PHP version as needed 26 | extensions: mbstring, zip 27 | - name: Install Composer Dependencies (Production Only) 28 | run: composer install --no-dev --optimize-autoloader 29 | 30 | - name: Build Plugin 31 | run: npm run build 32 | 33 | - name: Create Plugin ZIP 34 | run: npm run plugin-zip 35 | 36 | - name: Upload Release Asset 37 | uses: actions/upload-release-asset@v1 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | upload_url: ${{ github.event.release.upload_url }} 42 | asset_path: ${{github.workspace}}/wp-personal-os.zip 43 | asset_name: wp-personal-os.zip 44 | asset_content_type: application/zip 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Directories/files that may be generated by this project 2 | out 3 | build 4 | node_modules 5 | vendor 6 | debug.log 7 | *.zip 8 | 9 | # Operating system specific files 10 | .DS_Store 11 | Thumbs.db 12 | 13 | # Local overrides 14 | .wp-env.override.json 15 | 16 | .phpunit.result.cache 17 | 18 | 19 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 20 | 21 | # dependencies 22 | node_modules 23 | .pnp 24 | .pnp.js 25 | 26 | # testing 27 | coverage 28 | 29 | # next.js 30 | .next/ 31 | out/ 32 | build 33 | 34 | # misc 35 | .DS_Store 36 | *.pem 37 | 38 | # debug 39 | npm-debug.log* 40 | yarn-debug.log* 41 | yarn-error.log* 42 | .pnpm-debug.log* 43 | 44 | # local env files 45 | .env.local 46 | .env.development.local 47 | .env.test.local 48 | .env.production.local 49 | 50 | # turbo 51 | .turbo 52 | 53 | .env 54 | .vercel 55 | .env*.local 56 | 57 | # Playwright 58 | src-chatbot/test-results/ 59 | src-chatbot/playwright-report/ 60 | src-chatbot/blob-report/ 61 | src-chatbot/playwright/* -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 -------------------------------------------------------------------------------- /.wp-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ "." ], 3 | "config": { 4 | "WP_DEBUG": true, 5 | "WP_DEBUG_LOG": true, 6 | "SCRIPT_DEBUG": true, 7 | "WP_DEBUG_DISPLAY": true 8 | }, 9 | "mappings": { 10 | "wp-content/debug.log": "./debug.log" 11 | }, 12 | "env": { 13 | "development": { 14 | "port": 8901 15 | }, 16 | "tests": { 17 | "port": 8902 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Personal OS is the operating system for your life installed in your digital home. 2 | 3 | A modern note-taking and productivity system built on WordPress with the latest and greatest WordPress features. 4 | 5 | ### [🚀 Open PersonalOS Playground](https://playground.wordpress.net/?networking=yes#{%22steps%22:[{%22step%22:%22installPlugin%22,%22pluginData%22:{%22resource%22:%22url%22,%22url%22:%22https://github.com/artpi/personalos/releases/latest/download/wp-personal-os.zip%22}}],%22landingPage%22:%22/wp-admin/admin.php?page=personalos-settings%22,%22login%22:true}) 6 | 7 | 8 | ### Few principles 9 | 10 | - Methodologies borrowed from Building a Second Brain, and GTD 11 | - Inspired by Roam Research, Logseq, Obsidian and Tana 12 | - Opinionated. I will ship features because I want them 13 | - Has to work with any WordPress supporting plugins 14 | - Implement things the WordPress Way™. WordPress has a ton of new APIs. 15 | 16 | ## Features 17 | 18 | I consider this a humble start. I intend to grow this feature list until it becomes my main notetaking app. 19 | 20 | - [TODOs](modules/todo/README.md) - Manage your TODOs right in WordPress or through a dedicated mobile app 21 | - [Notes](modules/notes/README.md) - Fully private "note" CPT, with embeddable nesting 22 | - [Readwise](modules/readwise/README.md) - Sync notes with Readwise 23 | - [Evernote](modules/evernote/README.md) - Sync notes with Evernote via 2-way sync 24 | - [Transcriptions](modules/openai/README.md) - Upload an mp3 and it will get transcribed and turned into a note. 25 | 26 | ## How to install 27 | 28 | [Install](./docs/INSTALL.md) -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "artpi/personalos", 3 | "description": "Manage your life", 4 | "repositories": [ 5 | { 6 | "type": "vcs", 7 | "url": "https://github.com/artpi/evernote-cloud-sdk-php" 8 | } 9 | ], 10 | "require": { 11 | "evernote/evernote-cloud-sdk-php": "dev-fix-curly-braces", 12 | "symfony/css-selector": "v5.4.2", 13 | "erusev/parsedown": "^1.7" 14 | }, 15 | "require-dev": { 16 | "squizlabs/php_codesniffer": "^3.7.1", 17 | "wp-coding-standards/wpcs": "^2.3.0", 18 | "yoast/phpunit-polyfills": "1.1.1", 19 | "wp-phpunit/wp-phpunit": "^6.5" 20 | }, 21 | "scripts": { 22 | "lint": [ 23 | "phpcs -s --standard=phpcs.xml ./" 24 | ], 25 | "phpcbf": [ 26 | "phpcbf --standard=phpcs.xml ./" 27 | ] 28 | }, 29 | "config": { 30 | "allow-plugins": { 31 | "composer/installers": true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /dashboard.php: -------------------------------------------------------------------------------- 1 | get_readme(); 8 | if ( $readme ) { 9 | $doc_tabs[] = array( 10 | 'id' => $module->id, 11 | 'name' => $module->name, 12 | 'readme' => $readme, 13 | ); 14 | } 15 | } 16 | $module_id = isset( $_GET['module'] ) ? sanitize_text_field( $_GET['module'] ) : 'notes'; 17 | $show_readme = ''; 18 | ?> 19 | 32 |

PersonalOS

33 |

PersonalOS is a personal operating system for managing your life, all based on WordPress.

34 |

PersonalOS provides the following modules:

35 | 46 |
47 | 48 |
49 |
50 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | personalos.net -------------------------------------------------------------------------------- /docs/INSTALL.md: -------------------------------------------------------------------------------- 1 | # Get PersonalOS 2 | 3 | ## I have a WordPress site 4 | 5 | ## Getting a WordPress site 6 | 7 | ## Get a WordPress on your laptop 8 | 9 | -------------------------------------------------------------------------------- /docs/assets/notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artpi/PersonalOS/60b0d6f69268509db080dab3c589b74bfff7cd12/docs/assets/notes.png -------------------------------------------------------------------------------- /docs/assets/todos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artpi/PersonalOS/60b0d6f69268509db080dab3c589b74bfff7cd12/docs/assets/todos.png -------------------------------------------------------------------------------- /modules/bucketlist/class-bucketlist-module.php: -------------------------------------------------------------------------------- 1 | activate(); 11 | 12 | } 13 | 14 | public function add_admin_menu(): void { 15 | add_submenu_page( 'personalos', 'Bucketlist', 'Bucketlist', 'read', 'pos-bucketlist', array( $this, 'render_admin_page' ) ); 16 | } 17 | 18 | public function render_admin_page(): void { 19 | ?> 20 |
21 |

22 |
23 |
24 | { window.renderNotebookAdmin( document.getElementById( "bucketlist-root" ), { view: { filters: [ { field: "flags", operator: "isAny", value: ["bucketlist"] } ] } } ); } );', 'after' ); 28 | wp_add_inline_script( 'pos', 'wp.domReady( () => { window.renderTodoAdmin( document.getElementById( "bucketlist-root" ), {} ); } );', 'after' ); 29 | 30 | } 31 | 32 | public function add_bucketlist_flag( $flags ) { 33 | $flags[] = array( 34 | 'id' => 'bucketlist', 35 | 'name' => 'Bucketlist', 36 | 'label' => 'This notebook is a Bucketlist Item', 37 | ); 38 | return $flags; 39 | } 40 | 41 | public function activate() { 42 | if ( ! term_exists( 'Bucketlist', 'notebook' ) ) { 43 | wp_insert_term( 44 | 'Bucketlist', 45 | 'notebook', 46 | array( 47 | 'slug' => 'bucketlist', 48 | ) 49 | ); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /modules/crm/class-crm-module.php: -------------------------------------------------------------------------------- 1 | ID ); 20 | $phone = $custom['phone'][0]; 21 | $email = $custom['email'][0]; 22 | $address = $custom['address'][0]; 23 | ?> 24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 | id . '_person', 42 | array( 43 | 'labels' => array( 44 | 'name' => 'People', 45 | 'singular_name' => 'Person', 46 | 'add_new' => 'Add New Person', 47 | 'add_new_item' => 'Add New Person', 48 | ), 49 | //'show_in_menu' => 'personalos', 50 | 'register_meta_box_cb' => array( $this, 'add_meta_box' ), 51 | 'show_in_rest' => true, 52 | 'public' => false, 53 | 'show_ui' => true, 54 | 'has_archive' => false, 55 | 'rest_namespace' => 'pos/' . $this->id, 56 | 'supports' => array( 'title', 'editor', 'revisions', 'custom-fields' ), 57 | ) 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /modules/daily/class-daily-module.php: -------------------------------------------------------------------------------- 1 | notes = $notes; 10 | $this->register(); 11 | } 12 | 13 | public function register() {} 14 | 15 | public function get_daily_note_for_date( $date = null ) { 16 | if ( ! $date ) { 17 | $date = time(); 18 | } 19 | // @TODO This happens to be the case for my daily notes coming from Evernote, but we need to make this different when we have native daily notes. 20 | return $this->notes->get_notes( 21 | array( 22 | 'title' => gmdate( 'M jS, Y', $date ), 23 | ) 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modules/evernote/README.md: -------------------------------------------------------------------------------- 1 | placeholder -------------------------------------------------------------------------------- /modules/notes/README.md: -------------------------------------------------------------------------------- 1 | # Notes Module 2 | 3 | WordPress is very close to a great note-taking app out of the box. PersonalOS makes sure the experience is ideal: 4 | 5 | - `note` is a fully **private** Custom Post Type 6 | - You can embed one note in the other via the Note Block 7 | - You can link notes using WordPress completer (`[[`) 8 | - Notes are easily accessible in the sidebar while writing a post 9 | - **Notebook** taxonomy to organize your notes. They are also used for TODOs. 10 | 11 | Notes are used as a base for [Readwise](../readwise) and [Evernote](../evernote) sync. 12 | 13 | ### Note block 14 | 15 | 16 | 17 | ### Notes sidebar 18 | 19 | ### Organizing with Notebooks 20 | 21 | Notebooks are used to organize your notes and TODOs. Any Note or TODO can be assigned to multiple notebooks. By default, it will end up in the "INBOX" notebook. 22 | Notebooks show up in several places: 23 | 24 | - In the [Notebooks taxonomy](edit-tags.php?taxonomy=notebook&post_type=notes). 25 | - While editing a Note or TODO. 26 | - In the WP-TODO mobile app sidebar. 27 | - Starred notebooks show up in the sidebar. 28 | - Starred notebooks show up in the dashboard. 29 | - Notes synced via [Readwise](../readwise) or [Evernote](../evernote) modules will be automatically added to the corresponding notebooks. 30 | 31 | #### Notebook Flags 32 | 33 | - `starred` - Starred notebooks show up in the sidebar, in the app and in the dashboard. 34 | - `project` - This is marked as a currently **active** project. This will be used to feed AI the list of your active projects. 35 | 36 | You can edit notebook flags while editing a notebook. 37 | 38 | ![notebook-flags](https://github.com/user-attachments/assets/26fa7660-947d-45e5-ac15-5b8526cfeb29) 39 | 40 | When you star a notebook, you will get a meta box in your WordPress admin dashboard: 41 | 42 | ![Dashboard](https://github.com/user-attachments/assets/58fb2ac4-3bec-4dc7-bf08-ce6e88112c7c) 43 | 44 | Also, you will see them in sidebar for easy access: 45 | 46 | ![sidebar-starred](https://github.com/user-attachments/assets/0fe7406f-d670-433a-a92f-8d451ec82f80) 47 | 48 | -------------------------------------------------------------------------------- /modules/notes/admin-widgets.css: -------------------------------------------------------------------------------- 1 | .pos_admin_widget_notes li { 2 | margin: 0 3 | } 4 | .pos_admin_widget_notes li a { 5 | display:block; 6 | padding: 0.5em; 7 | border-top: 1px solid #e5e5e5; 8 | } 9 | .pos_admin_widget_notes li:hover { 10 | background-color: #efefef; 11 | } 12 | 13 | .pos_admin_widget_notes h5 { 14 | margin: 0 5px 0 0; 15 | } 16 | 17 | .pos_admin_widget_notes time { 18 | color:#646970; 19 | display: block; 20 | text-align: right; 21 | } 22 | 23 | .pos_admin_widget_notes p { 24 | color:gray; 25 | font-style:italic; 26 | margin-top: 0.1em; 27 | margin-bottom: 0; 28 | } 29 | 30 | .pos_admin_widget_todos li { 31 | display:flex; 32 | flex-direction: row; 33 | height: 25px; 34 | } 35 | .pos_admin_widget_todos li a { 36 | line-height: 25px; 37 | height: 25px; 38 | display:flex; 39 | } 40 | .pos_admin_widget_todos li svg { 41 | margin-right: 10px; 42 | } -------------------------------------------------------------------------------- /modules/openai/README.md: -------------------------------------------------------------------------------- 1 | # OpenAI Modules 2 | 3 | ## Custom GPT 4 | 5 | PersonalOS integrates with OpenAI's Custom GPT. 6 | - You can ask GPT to create and browse notes 7 | - You can ask GPT to create and browse todos 8 | - You can ask GPT to create and browse notebooks 9 | 10 | [Copy paste these values into your ChatGPT configuration](tools.php?page=pos-custom-gpt) 11 | 12 | ## Transcription 13 | -------------------------------------------------------------------------------- /modules/openai/class-elevenlabs-module.php: -------------------------------------------------------------------------------- 1 | array( 9 | 'type' => 'text', 10 | 'name' => 'Eleven labs API Key', 11 | 'label' => '', 12 | ), 13 | ); 14 | 15 | public function is_configured() { 16 | return ! empty( $this->settings['api_key'] ); 17 | } 18 | 19 | public function get_voices() { 20 | $response = $this->api_call( 'https://api.elevenlabs.io/v1/voices', array() ); 21 | return $response; 22 | } 23 | 24 | public function api_call( $url, $data ) { 25 | $api_key = $this->get_setting( 'api_key' ); 26 | 27 | $args = array( 28 | 'timeout' => 120, 29 | 'headers' => array( 30 | 'xi-api-key' => $api_key, 31 | 'Content-Type' => 'application/json', 32 | ), 33 | ); 34 | 35 | if ( ! empty( $data ) ) { 36 | $args['body'] = wp_json_encode( $data ); 37 | $args['method'] = 'POST'; 38 | } 39 | 40 | $response = wp_remote_get( 41 | $url, 42 | $args, 43 | ); 44 | $body = wp_remote_retrieve_body( $response ); 45 | if ( is_wp_error( $response ) ) { 46 | return $response; 47 | } 48 | return json_decode( $body ); 49 | } 50 | 51 | public function tts( $text, $voice = '', $data = array() ) { 52 | $api_key = $this->get_setting( 'api_key' ); 53 | $file_name = 'speech-' . uniqid() . '.mp3'; 54 | 55 | $response = wp_remote_post( 56 | 'https://api.elevenlabs.io/v1/text-to-speech/' . $voice, 57 | array( 58 | 'timeout' => 360, 59 | 'headers' => array( 60 | 'xi-api-key' => $api_key, 61 | 'Content-Type' => 'application/json', 62 | ), 63 | 'body' => wp_json_encode( 64 | array( 65 | 'model_id' => 'eleven_turbo_v2_5', 66 | 'text' => $text, 67 | 'voice_settings' => array( 68 | 'stability' => 0.1, 69 | 'use_speaker_boost' => true, 70 | 'similarity_boost' => 0, 71 | ), 72 | ) 73 | ), 74 | ) 75 | ); 76 | 77 | if ( is_wp_error( $response ) ) { 78 | return $response; 79 | } 80 | 81 | if ( is_wp_error( $response ) ) { 82 | return $response; 83 | } 84 | 85 | require_once ABSPATH . 'wp-admin/includes/media.php'; 86 | require_once ABSPATH . 'wp-admin/includes/file.php'; 87 | require_once ABSPATH . 'wp-admin/includes/image.php'; 88 | 89 | $tempfile = wp_tempnam(); 90 | global $wp_filesystem; 91 | WP_Filesystem(); 92 | $wp_filesystem->put_contents( $tempfile, wp_remote_retrieve_body( $response ) ); 93 | 94 | $file = array( 95 | 'name' => wp_hash( time() ) . '-' . $file_name, // This hash is used to obfuscate the file names which should NEVER be exposed. 96 | 'type' => 'audio/mpeg', 97 | 'tmp_name' => $tempfile, 98 | 'error' => 0, 99 | 'size' => filesize( $tempfile ), 100 | ); 101 | 102 | $data['post_content'] = $text; 103 | $data['post_status'] = 'private'; 104 | 105 | $media_id = media_handle_sideload( $file, 0, null, $data ); 106 | 107 | return $media_id; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /modules/openai/class-openai-tool.php: -------------------------------------------------------------------------------- 1 | name = $name; 13 | $this->description = $description; 14 | $this->parameters = $parameters; 15 | if ( $callback ) { 16 | $this->callback = $callback; 17 | } else { 18 | $this->callback = function ( $arguments ) { 19 | return new WP_Error( 'tool-not-callable', 'Tool not callable ' . $this->name ); 20 | }; 21 | } 22 | } 23 | 24 | public static function get_tools( $include_writeable = true ) { 25 | $tools = apply_filters( 'pos_openai_tools', array() ); 26 | if ( ! $include_writeable ) { 27 | $tools = array_filter( 28 | $tools, 29 | function( $tool ) { 30 | return ! $tool->writeable; 31 | } 32 | ); 33 | } 34 | return array_values( $tools ); 35 | } 36 | 37 | public static function get_tool( string $name ) { 38 | $matching = array_filter( 39 | self::get_tools(), 40 | function( $tool ) use ( $name ) { 41 | return $tool->name === $name; 42 | } 43 | ); 44 | return array_shift( $matching ); 45 | } 46 | 47 | public function get_function_signature() { 48 | return array( 49 | 'type' => 'function', 50 | 'function' => array( 51 | 'name' => $this->name, 52 | 'strict' => $this->strict, 53 | 'description' => $this->description, 54 | 'parameters' => array( 55 | 'type' => 'object', 56 | 'properties' => (object) $this->parameters, 57 | 'required' => array_keys( $this->parameters ), 58 | 'additionalProperties' => false, 59 | ), 60 | ), 61 | ); 62 | } 63 | 64 | public function get_function_signature_for_realtime_api() { 65 | $signature = array( 66 | 'type' => 'function', 67 | 'name' => $this->name, 68 | 'description' => $this->description, 69 | ); 70 | if ( ! empty( $this->parameters ) ) { 71 | $signature['parameters'] = array( 72 | 'type' => 'object', 73 | 'properties' => $this->parameters, 74 | 'required' => array_keys( $this->parameters ), 75 | 'additionalProperties' => false, 76 | ); 77 | } 78 | return $signature; 79 | } 80 | 81 | public function invoke( array $arguments ) { 82 | return call_user_func( $this->callback, $arguments ); 83 | } 84 | 85 | public function invoke_for_function_call( array $arguments ): string { 86 | $result = $this->invoke( $arguments ); 87 | if ( is_wp_error( $result ) ) { 88 | return $result->get_error_message(); 89 | } elseif ( is_string( $result ) ) { 90 | return $result; 91 | } else { 92 | return wp_json_encode( $result ); 93 | } 94 | } 95 | } 96 | 97 | // phpcs:ignore 98 | class OpenAI_Tool_Writeable extends OpenAI_Tool { 99 | public bool $writeable = true; 100 | } 101 | -------------------------------------------------------------------------------- /modules/openai/podcast-assets/motivation-st-1.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artpi/PersonalOS/60b0d6f69268509db080dab3c589b74bfff7cd12/modules/openai/podcast-assets/motivation-st-1.m4a -------------------------------------------------------------------------------- /modules/openai/podcast-assets/motivation-st-2.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artpi/PersonalOS/60b0d6f69268509db080dab3c589b74bfff7cd12/modules/openai/podcast-assets/motivation-st-2.m4a -------------------------------------------------------------------------------- /modules/readwise/README.md: -------------------------------------------------------------------------------- 1 | # Readwise Module 2 | 3 | Notes sync with Readwise. 4 | 5 | - Preserve tags 6 | - Every highlight turned into Readwise block with link to original -------------------------------------------------------------------------------- /modules/todo/README.md: -------------------------------------------------------------------------------- 1 | # TODO Module 2 | 3 | 4 | ### Recommended Noteboook setup 5 | 6 | Here is how I recommend setting up your notebooks in a nested structure: 7 | 8 | - 1 Status 9 | - INBOX - Default notebook for new notes and TODOs. 10 | - NOW - TODOs from this Notebook are treated as the tasks to do right now. You should mark this notebook as "starred" so it shows up in the sidebar, your WP-Admin dashboard and on top in the WP-TODO mobile app. 11 | - LATER - TODOs from this Notebook are next in line to be done. 12 | - FollowUp - TODOs you need to follow up on - they are "handed off" to somebody right now. 13 | - 2 Projects 14 | - Your project 1 15 | - Your project 2 16 | - 3 Areas 17 | - Home improvements 18 | - Blogging 19 | 20 | -------------------------------------------------------------------------------- /modules/todo/starter-content.php: -------------------------------------------------------------------------------- 1 | list( array(), 'starter-content' ); 3 | foreach ( $todos as $note ) { 4 | wp_delete_post( $note->ID ); 5 | } 6 | 7 | 8 | $this->create( 9 | array( 10 | 'post_title' => 'Regular TODO in NOW with action', 11 | 'post_excerpt' => 'This TODO has an action.', 12 | 'meta_input' => array( 13 | 'url' => 'tel://1234567890', 14 | ), 15 | ), 16 | array( 'starter-content', 'now' ) 17 | ); 18 | 19 | $blocking = $this->create( 20 | array( 21 | 'post_title' => 'Blocking todo', 22 | 'post_excerpt' => 'This is blocking another todo.', 23 | ), 24 | array( 'starter-content', 'now' ) 25 | ); 26 | 27 | $blocked = $this->create( 28 | array( 29 | 'post_title' => 'Blocked todo', 30 | 'post_excerpt' => 'This is blocked by the blocking todo. It will move to NOW when the blocking todo is completed.', 31 | 'meta_input' => array( 32 | 'pos_blocked_by' => $blocking, 33 | 'pos_blocked_pending_term' => 'now', 34 | ), 35 | ), 36 | array( 'starter-content' ) 37 | ); 38 | 39 | $tomorrow = gmdate( 'Y-m-d H:i:s', strtotime( '+1 day' ) ); 40 | $this->create( 41 | array( 42 | 'post_title' => 'Scheduled TODO', 43 | 'post_excerpt' => 'This is a scheduled todo. It will move to NOW on ' . $tomorrow, 44 | 'post_date' => $tomorrow, 45 | 'meta_input' => array( 46 | 'pos_blocked_pending_term' => 'now', 47 | ), 48 | ), 49 | array( 'starter-content' ) 50 | ); 51 | 52 | $this->create( 53 | array( 54 | 'post_title' => 'Recuring TODO', 55 | 'post_excerpt' => 'This is a recurring TODO. It will automatically schedule a copy of itself 2 days after completion.', 56 | 'meta_input' => array( 57 | 'pos_blocked_pending_term' => 'now', 58 | 'pos_recurring_days' => 2, 59 | ), 60 | ), 61 | array( 'starter-content' ) 62 | ); 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-personal-os", 3 | "version": "0.2.4", 4 | "description": "WordPress plugin to manage your life.", 5 | "author": "Artpi", 6 | "license": "GPL-2.0-or-later", 7 | "main": "build/index.js", 8 | "files": [ 9 | "build", 10 | "modules", 11 | "settings.php", 12 | "personalos.php", 13 | "dashboard.php", 14 | "vendor", 15 | "class-pos-settings.php" 16 | ], 17 | "scripts": { 18 | "build": "wp-scripts build", 19 | "format": "wp-scripts format", 20 | "lint:css": "wp-scripts lint-style", 21 | "lint:js": "wp-scripts lint-js", 22 | "packages-update": "wp-scripts packages-update", 23 | "plugin-zip": "wp-scripts plugin-zip", 24 | "start": "wp-scripts start", 25 | "wp-env": "wp-env", 26 | "dev": "wp-scripts start", 27 | "test:unit": "wp-env run tests-cli --env-cwd=wp-content/plugins/personalos ./vendor/bin/phpunit --testsuite=unit", 28 | "test:integration": "wp-env run tests-cli --env-cwd=wp-content/plugins/personalos ./vendor/bin/phpunit --testsuite=integration" 29 | }, 30 | "dependencies": { 31 | "@wordpress/compose": "^6.27.0", 32 | "@wordpress/data": "^9.20.0", 33 | "@wordpress/edit-post": "^7.27.2", 34 | "@wordpress/icons": "^10.15.1", 35 | "@wordpress/plugins": "^6.18.0", 36 | "@wordpress/dataviews": "^4.10.0" 37 | }, 38 | "devDependencies": { 39 | "@wordpress/env": "^9.2.0", 40 | "@wordpress/scripts": "^30.7.0", 41 | "eslint": "^8.56.0", 42 | "prettier": "npm:wp-prettier@3.0.3", 43 | "@wordpress/dependency-extraction-webpack-plugin": "^6.14.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | */vendor/* 8 | */node_modules/* 9 | */build/* 10 | */tests/* 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | tests/unit/ 14 | 15 | 16 | tests/integration/ 17 | 18 | 19 | -------------------------------------------------------------------------------- /src-chatbot/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "plugin:import/recommended", 5 | "plugin:import/typescript", 6 | "prettier", 7 | "plugin:tailwindcss/recommended" 8 | ], 9 | "plugins": ["tailwindcss"], 10 | "rules": { 11 | "tailwindcss/no-custom-classname": "off", 12 | "tailwindcss/classnames-order": "off" 13 | }, 14 | "settings": { 15 | "import/resolver": { 16 | "typescript": { 17 | "alwaysTryTypes": true 18 | } 19 | } 20 | }, 21 | "ignorePatterns": ["**/components/ui/**"] 22 | } 23 | -------------------------------------------------------------------------------- /src-chatbot/app/(auth)/actions.ts: -------------------------------------------------------------------------------- 1 | // 'use server'; // Disabled for static export 2 | 3 | import { z } from 'zod'; 4 | 5 | // import { createUser, getUser } from '@/lib/db/queries'; // Removed as DB is disabled 6 | 7 | // import { signIn } from './auth'; // Removed as ./auth.ts is deleted 8 | 9 | const authFormSchema = z.object({ 10 | email: z.string().email(), 11 | password: z.string().min(6), 12 | }); 13 | 14 | export interface LoginActionState { 15 | status: 'idle' | 'in_progress' | 'success' | 'failed' | 'invalid_data'; 16 | } 17 | 18 | export const login = async ( 19 | _: LoginActionState, 20 | formData: FormData, 21 | ): Promise => { 22 | try { 23 | const validatedData = authFormSchema.parse({ 24 | email: formData.get('email'), 25 | password: formData.get('password'), 26 | }); 27 | 28 | // await signIn('credentials', { // Removed as ./auth.ts is deleted 29 | // email: validatedData.email, 30 | // password: validatedData.password, 31 | // redirect: false, 32 | // }); 33 | console.warn('signIn call in login() disabled as auth system is removed.'); 34 | 35 | return { status: 'success' }; 36 | } catch (error) { 37 | if (error instanceof z.ZodError) { 38 | return { status: 'invalid_data' }; 39 | } 40 | 41 | return { status: 'failed' }; 42 | } 43 | }; 44 | 45 | export interface RegisterActionState { 46 | status: 47 | | 'idle' 48 | | 'in_progress' 49 | | 'success' 50 | | 'failed' 51 | | 'user_exists' 52 | | 'invalid_data'; 53 | } 54 | 55 | export const register = async ( 56 | _: RegisterActionState, 57 | formData: FormData, 58 | ): Promise => { 59 | try { 60 | const validatedData = authFormSchema.parse({ 61 | email: formData.get('email'), 62 | password: formData.get('password'), 63 | }); 64 | 65 | // const [user] = await getUser(validatedData.email); // DB call disabled for static export 66 | // if (user) { // Logic dependent on DB call disabled for static export 67 | // return { status: 'user_exists' } as RegisterActionState; 68 | // } 69 | // await createUser(validatedData.email, validatedData.password); // DB call disabled for static export 70 | console.warn('User check & creation in register() disabled for static export'); 71 | 72 | // await signIn('credentials', { // Removed as ./auth.ts is deleted 73 | // email: validatedData.email, 74 | // password: validatedData.password, 75 | // redirect: false, 76 | // }); 77 | console.warn('signIn call in register() disabled as auth system is removed.'); 78 | 79 | return { status: 'success' }; 80 | } catch (error) { 81 | if (error instanceof z.ZodError) { 82 | return { status: 'invalid_data' }; 83 | } 84 | 85 | return { status: 'failed' }; 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /src-chatbot/app/(auth)/login/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Link from 'next/link'; 4 | import { useRouter } from 'next/navigation'; 5 | import { useActionState, useEffect, useState } from 'react'; 6 | import { toast } from '@/components/toast'; 7 | 8 | import { AuthForm } from '@/components/auth-form'; 9 | import { SubmitButton } from '@/components/submit-button'; 10 | 11 | import { login, type LoginActionState } from '../actions'; 12 | // import { useSession } from 'next-auth/react'; // Removed as next-auth is uninstalled 13 | 14 | export default function Page() { 15 | const router = useRouter(); 16 | 17 | const [email, setEmail] = useState(''); 18 | const [isSuccessful, setIsSuccessful] = useState(false); 19 | 20 | const [state, formAction] = useActionState( 21 | login, 22 | { 23 | status: 'idle', 24 | }, 25 | ); 26 | 27 | // const { update: updateSession } = useSession(); // Removed 28 | 29 | useEffect(() => { 30 | if (state.status === 'failed') { 31 | toast({ 32 | type: 'error', 33 | description: 'Invalid credentials!', 34 | }); 35 | } else if (state.status === 'invalid_data') { 36 | toast({ 37 | type: 'error', 38 | description: 'Failed validating your submission!', 39 | }); 40 | } else if (state.status === 'success') { 41 | setIsSuccessful(true); 42 | // updateSession(); // Removed as next-auth is uninstalled 43 | console.warn('updateSession call removed from login page due to next-auth removal.'); 44 | router.refresh(); 45 | } 46 | // }, [state.status]); 47 | // state.status is the only dependency from the original code. router and updateSession are stable. 48 | }, [state.status, router]); // Added router to dependency array as per eslint exhaustive-deps common practice, though not strictly necessary if it's stable. 49 | 50 | const handleSubmit = (formData: FormData) => { 51 | setEmail(formData.get('email') as string); 52 | formAction(formData); 53 | }; 54 | 55 | return ( 56 |
57 |
58 |
59 |

Sign In

60 |

61 | Use your email and password to sign in 62 |

63 |
64 | 65 | Sign in 66 |

67 | {"Don't have an account? "} 68 | 72 | Sign up 73 | 74 | {' for free.'} 75 |

76 |
77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src-chatbot/app/(auth)/register/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Link from 'next/link'; 4 | import { useRouter } from 'next/navigation'; 5 | import { useActionState, useEffect, useState } from 'react'; 6 | 7 | import { AuthForm } from '@/components/auth-form'; 8 | import { SubmitButton } from '@/components/submit-button'; 9 | 10 | import { register, type RegisterActionState } from '../actions'; 11 | import { toast } from '@/components/toast'; 12 | // import { useSession } from 'next-auth/react'; // Removed as next-auth is uninstalled 13 | 14 | export default function Page() { 15 | const router = useRouter(); 16 | 17 | const [email, setEmail] = useState(''); 18 | const [isSuccessful, setIsSuccessful] = useState(false); 19 | 20 | const [state, formAction] = useActionState( 21 | register, 22 | { 23 | status: 'idle', 24 | }, 25 | ); 26 | 27 | // const { update: updateSession } = useSession(); // Removed 28 | 29 | useEffect(() => { 30 | if (state.status === 'user_exists') { 31 | toast({ type: 'error', description: 'Account already exists!' }); 32 | } else if (state.status === 'failed') { 33 | toast({ type: 'error', description: 'Failed to create account!' }); 34 | } else if (state.status === 'invalid_data') { 35 | toast({ 36 | type: 'error', 37 | description: 'Failed validating your submission!', 38 | }); 39 | } else if (state.status === 'success') { 40 | toast({ type: 'success', description: 'Account created successfully!' }); 41 | 42 | setIsSuccessful(true); 43 | // updateSession(); // Removed as next-auth is uninstalled 44 | console.warn('updateSession call removed from register page due to next-auth removal.'); 45 | router.refresh(); 46 | } 47 | // }, [state]); 48 | // state is the only dependency in original. router and updateSession are stable. 49 | }, [state, router]); // Added router to dependency array as per eslint exhaustive-deps common practice 50 | 51 | const handleSubmit = (formData: FormData) => { 52 | setEmail(formData.get('email') as string); 53 | formAction(formData); 54 | }; 55 | 56 | return ( 57 |
58 |
59 |
60 |

Sign Up

61 |

62 | Create an account with your email and password 63 |

64 |
65 | 66 | Sign Up 67 |

68 | {'Already have an account? '} 69 | 73 | Sign in 74 | 75 | {' instead.'} 76 |

77 |
78 |
79 |
80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /src-chatbot/app/(chat)/actions.ts: -------------------------------------------------------------------------------- 1 | // 'use server'; // Disabled for static export 2 | 3 | import { generateText, type UIMessage } from 'ai'; 4 | // import { cookies } from 'next/headers'; // Disabled for static export 5 | /* // DB function imports disabled for static export 6 | import { 7 | deleteMessagesByChatIdAfterTimestamp, 8 | getMessageById, 9 | updateChatVisiblityById, 10 | } from '@/lib/db/queries'; 11 | */ 12 | import type { VisibilityType } from '@/components/visibility-selector'; 13 | import { myProvider } from '@/lib/ai/providers'; 14 | 15 | export async function saveChatModelAsCookie(model: string) { 16 | // const cookieStore = await cookies(); // Disabled for static export 17 | // cookieStore.set('chat-model', model); // Disabled for static export 18 | console.warn('saveChatModelAsCookie is disabled for static export'); 19 | } 20 | 21 | export async function generateTitleFromUserMessage({ 22 | message, 23 | }: { 24 | message: UIMessage; 25 | }) { 26 | const { text: title } = await generateText({ 27 | model: myProvider.languageModel('title-model'), 28 | system: `\n 29 | - you will generate a short title based on the first message a user begins a conversation with 30 | - ensure it is not more than 80 characters long 31 | - the title should be a summary of the user's message 32 | - do not use quotes or colons`, 33 | prompt: JSON.stringify(message), 34 | }); 35 | 36 | return title; 37 | } 38 | 39 | export async function deleteTrailingMessages({ id }: { id: string }) { 40 | // const [message] = await getMessageById({ id }); // DB call disabled 41 | // await deleteMessagesByChatIdAfterTimestamp({ // DB call disabled 42 | // chatId: message.chatId, 43 | // timestamp: message.createdAt, 44 | // }); 45 | console.warn('deleteTrailingMessages in app/(chat)/actions.ts disabled for static export'); 46 | } 47 | 48 | export async function updateChatVisibility({ 49 | chatId, 50 | visibility, 51 | }: { 52 | chatId: string; 53 | visibility: VisibilityType; 54 | }) { 55 | // await updateChatVisiblityById({ chatId, visibility }); // DB call disabled 56 | console.warn('updateChatVisibility in app/(chat)/actions.ts disabled for static export'); 57 | } 58 | -------------------------------------------------------------------------------- /src-chatbot/app/(chat)/layout.tsx: -------------------------------------------------------------------------------- 1 | // import { cookies } from 'next/headers'; // Removed for static export 2 | 3 | import { AppSidebar } from '@/components/app-sidebar'; 4 | import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; 5 | // import { auth } from '../(auth)/auth'; // auth() call disabled for static export 6 | // import { type UserType } from '../(auth)/auth'; // Import UserType -> ../(auth)/auth.ts deleted 7 | // import type { Session } from 'next-auth'; // Removed as next-auth is uninstalled 8 | import Script from 'next/script'; 9 | 10 | // Define UserType locally as ../(auth)/auth.ts was deleted 11 | export type UserType = 'guest' | 'regular'; 12 | 13 | // Define a local mock session type 14 | interface MockSessionUser { 15 | id: string; 16 | type: UserType; 17 | name?: string | null; 18 | email?: string | null; 19 | image?: string | null; 20 | } 21 | 22 | // interface MockSession { // Not strictly needed here as only session.user is used directly 23 | // user: MockSessionUser; 24 | // expires: string; 25 | // } 26 | 27 | export const experimental_ppr = true; 28 | 29 | export default async function Layout({ 30 | children, 31 | }: { 32 | children: React.ReactNode; 33 | }) { 34 | // const sessionFromAuth = await auth(); // cookies() call removed, auth() call disabled 35 | console.warn('auth() call in app/(chat)/layout.tsx disabled. Using mock session.'); 36 | 37 | const mockUser: MockSessionUser = { 38 | id: 'static-guest-id', 39 | type: 'guest' as UserType, 40 | name: 'Static Guest', 41 | email: 'guest@static.local', 42 | image: null, 43 | }; 44 | // const session: MockSession = { // Full session object not strictly needed if only user is passed down 45 | // user: mockUser, 46 | // expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), 47 | // }; 48 | 49 | const isCollapsed = true; // Default to collapsed for static export 50 | console.warn('Sidebar cookie state in app/(chat)/layout.tsx disabled for static export. Defaulting to collapsed.'); 51 | 52 | return ( 53 | <> 54 |