├── .eslintrc.base.json ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── examples ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── README.md ├── package.json ├── priompt │ ├── ArvidStory │ │ └── example01.yaml │ ├── SimpleFunction │ │ └── example01.yaml │ ├── SimplePrompt │ │ └── example01.yaml │ ├── examplePrompt │ │ └── example01.yaml │ └── functionCallingPrompt │ │ └── example01.yaml ├── src │ ├── function-calling-prompt.tsx │ ├── index.ts │ ├── priompt-preview-handlers.ts │ └── prompt.tsx ├── tsconfig.json └── vitest.config.ts ├── init.sh ├── priompt-preview ├── .eslintrc.cjs ├── .gitignore ├── .npmignore ├── README.md ├── components.json ├── index.html ├── package.json ├── postcss.config.js ├── scripts │ └── serve.cjs ├── src │ ├── App.tsx │ ├── components │ │ └── ui │ │ │ ├── button.tsx │ │ │ ├── command.tsx │ │ │ ├── dialog.tsx │ │ │ └── textarea.tsx │ ├── index.css │ ├── lib │ │ └── utils.ts │ ├── main.tsx │ ├── openai.ts │ ├── openai_interfaces.ts │ └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.d.ts ├── vite.config.js └── vite.config.ts ├── priompt ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── README.md ├── package.json ├── src │ ├── __snapshots__ │ │ └── components.test.tsx.snap │ ├── base.test.tsx │ ├── components.test.tsx │ ├── components.tsx │ ├── index.ts │ ├── lib.ts │ ├── openai.ts │ ├── outputCatcher.ai.impl.ts │ ├── outputCatcher.ai.test.ts │ ├── outputCatcher.ai.ts │ ├── preview.ts │ ├── sourcemap.test.tsx │ ├── statsd.ts │ ├── tokenizer.ts │ └── types.d.ts ├── tsconfig.json └── vitest.config.ts ├── publish.sh ├── pull-from-open-source.sh ├── push-to-open-source.sh ├── rustfmt.toml └── tiktoken-node ├── .gitignore ├── .npmignore ├── Cargo.toml ├── README.md ├── build.rs ├── index.d.ts ├── index.js ├── npm ├── darwin-arm64 │ ├── README.md │ └── package.json ├── darwin-x64 │ ├── README.md │ └── package.json ├── linux-arm64-gnu │ ├── README.md │ └── package.json ├── linux-x64-gnu │ ├── README.md │ └── package.json ├── win32-arm64-msvc │ ├── README.md │ └── package.json └── win32-x64-msvc │ ├── README.md │ └── package.json ├── package.json └── src └── lib.rs /.eslintrc.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 7 | "rules": { 8 | "@typescript-eslint/require-array-sort-compare": "error", 9 | "@typescript-eslint/strict-boolean-expressions": ["error"], 10 | "@typescript-eslint/no-floating-promises": [ 11 | "error", 12 | { "ignoreVoid": false } 13 | ], 14 | "@typescript-eslint/await-thenable": "error", 15 | "@typescript-eslint/no-misused-promises": "error", 16 | "constructor-super": "error", 17 | "eqeqeq": "error", 18 | "@typescript-eslint/switch-exhaustiveness-check": "error", 19 | "@typescript-eslint/no-inferrable-types": "off", 20 | "no-buffer-constructor": "error", 21 | "no-caller": "error", 22 | "no-case-declarations": "error", 23 | "no-debugger": "error", 24 | "no-duplicate-case": "error", 25 | "no-eval": "error", 26 | "no-async-promise-executor": "error", 27 | "no-extra-semi": "error", 28 | "sonarjs/no-ignored-return": "error", 29 | "no-new-wrappers": "error", 30 | "no-redeclare": "off", 31 | "no-sparse-arrays": "error", 32 | "@typescript-eslint/no-unused-vars": "off", 33 | "@typescript-eslint/no-unused-expressions": "error", 34 | "@typescript-eslint/no-empty-function": "off", 35 | "no-throw-literal": "error", 36 | "no-constant-condition": "off", 37 | "no-unsafe-finally": "error", 38 | "no-unused-labels": "error", 39 | "no-restricted-globals": [ 40 | "warn", 41 | "name", 42 | "length", 43 | "event", 44 | "closed", 45 | "external", 46 | "status", 47 | "origin", 48 | "orientation", 49 | "context" 50 | ], 51 | "no-var": "off", 52 | "semi": "off", 53 | "@typescript-eslint/naming-convention": [ 54 | "error", 55 | { 56 | "selector": "class", 57 | "format": ["PascalCase"] 58 | } 59 | ] 60 | }, 61 | "overrides": [], 62 | "parser": "@typescript-eslint/parser", 63 | "parserOptions": { 64 | "ecmaVersion": "latest", 65 | "sourceType": "module", 66 | "project": "./tsconfig.json" 67 | }, 68 | "ignorePatterns": ["node_modules/"], 69 | "plugins": ["@typescript-eslint", "sonarjs"] 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main, publish] 7 | pull_request: 8 | 9 | env: 10 | DEBUG: "napi:*" 11 | MACOSX_DEPLOYMENT_TARGET: "10.13" 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | defaults: 17 | run: 18 | working-directory: tiktoken-node 19 | strategy: 20 | matrix: 21 | target: 22 | - x86_64-pc-windows-msvc 23 | - x86_64-unknown-linux-gnu 24 | - aarch64-unknown-linux-gnu 25 | - x86_64-apple-darwin 26 | - aarch64-apple-darwin 27 | - aarch64-pc-windows-msvc 28 | 29 | steps: 30 | - uses: actions/checkout@v3 31 | 32 | - name: Setup Node 33 | uses: actions/setup-node@v3 34 | with: 35 | node-version: 20.12.2 36 | 37 | - uses: anysphere/action-setup@0eb0e970826653e8af98de91bec007fbd58a23e0 38 | name: Install pnpm 39 | id: pnpm-install 40 | with: 41 | version: "=8.6.0" 42 | 43 | - name: Setup Rust 44 | uses: dtolnay/rust-toolchain@stable 45 | with: 46 | toolchain: stable 47 | targets: ${{ matrix.target }} 48 | 49 | - name: Run init.sh 50 | working-directory: . 51 | run: ./init.sh 52 | 53 | - uses: Swatinem/rust-cache@v2 54 | 55 | - name: Install ziglang 56 | uses: goto-bus-stop/setup-zig@v1 57 | with: 58 | version: 0.10.0 59 | 60 | - run: cargo install cargo-xwin 61 | if: matrix.target == 'x86_64-pc-windows-msvc' || matrix.target == 'aarch64-pc-windows-msvc' 62 | 63 | - name: Check formatting 64 | run: cargo fmt --all --check 65 | 66 | - name: Node install 67 | run: pnpm i 68 | 69 | - name: Build Mac and Linux 70 | if: matrix.target != 'x86_64-pc-windows-msvc' && matrix.target != 'x86_64-unknown-linux-gnu' && matrix.target != 'aarch64-pc-windows-msvc' 71 | run: pnpm run build -- --zig --target ${{ matrix.target }} 72 | 73 | - name: Build Windows 74 | if: matrix.target == 'x86_64-pc-windows-msvc' || matrix.target == 'x86_64-unknown-linux-gnu' || matrix.target == 'aarch64-pc-windows-msvc' 75 | run: pnpm run build -- --target ${{ matrix.target }} 76 | 77 | - name: Upload artifact 78 | uses: actions/upload-artifact@v3 79 | with: 80 | name: bindings-${{ matrix.target }} 81 | path: tiktoken-node/tiktoken-node.*.node 82 | if-no-files-found: error 83 | 84 | publish: 85 | if: ${{ github.repository == 'anysphere/priompt' && github.event_name == 'push' && github.ref == 'refs/heads/publish' }} 86 | runs-on: ubuntu-20.04 87 | needs: build 88 | 89 | steps: 90 | - uses: actions/checkout@v3 91 | 92 | - name: Setup Node 93 | uses: actions/setup-node@v3 94 | with: 95 | node-version: 20.12.2 96 | 97 | - uses: anysphere/action-setup@0eb0e970826653e8af98de91bec007fbd58a23e0 98 | name: Install pnpm 99 | id: pnpm-install 100 | with: 101 | version: "=8.6.0" 102 | 103 | - name: Run init.sh 104 | working-directory: . 105 | run: ./init.sh 106 | 107 | - name: Download build 108 | uses: actions/download-artifact@v3 109 | with: 110 | path: tiktoken-node/artifacts 111 | 112 | - name: LS artifacts 113 | run: ls -R tiktoken-node/artifacts 114 | shell: bash 115 | 116 | - name: Move artifacts 117 | working-directory: tiktoken-node 118 | run: pnpm artifacts 119 | 120 | - name: LS post-move 121 | run: ls -R tiktoken-node/npm 122 | shell: bash 123 | 124 | - name: npm version 125 | run: npm --version 126 | shell: bash 127 | 128 | - name: Build priompt 129 | working-directory: priompt 130 | run: pnpm build 131 | 132 | - name: Build priompt-preview 133 | working-directory: priompt-preview 134 | run: pnpm build 135 | 136 | - name: globally install napi-rs 137 | run: npm install -g @napi-rs/cli 138 | 139 | - name: Set publishing config 140 | run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}" 141 | env: 142 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 143 | 144 | - name: Publish to npm 145 | run: pnpm publish --recursive --access=public --no-git-checks 146 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | priompt-opensource 4 | commit.patch 5 | commit.template 6 | .wireit 7 | target/ 8 | *.tsbuildinfo 9 | 10 | # todo: we should figure out a good way to sync these with the internal repo instead of having to generate them with the init script 11 | Cargo.lock 12 | Cargo.toml 13 | pnpm-workspace.yaml 14 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | publish-branch=publish -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.12.2 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "directory": "examples", 5 | "changeProcessCWD": true 6 | }, 7 | { 8 | "directory": "priompt", 9 | "changeProcessCWD": true 10 | }, 11 | { 12 | "directory": "priompt-preview", 13 | "changeProcessCWD": true 14 | } 15 | ], 16 | "[javascript]": { 17 | "editor.defaultFormatter": "vscode.typescript-language-features", 18 | "editor.insertSpaces": false, 19 | "editor.formatOnSave": true 20 | }, 21 | "[typescript]": { 22 | "editor.defaultFormatter": "vscode.typescript-language-features", 23 | "editor.insertSpaces": false, 24 | "editor.formatOnSave": true 25 | }, 26 | "[jsonc]": { 27 | "editor.defaultFormatter": "esbenp.prettier-vscode", 28 | "editor.formatOnSave": true 29 | }, 30 | "files.associations": { 31 | "*.env*": "properties" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2023 Anysphere, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Priompt 2 | 3 | Priompt (_priority + prompt_) is a JSX-based prompting library. It uses priorities to decide what to include in the context window. 4 | 5 | Priompt is an attempt at a _prompt design_ library, inspired by web design libraries like React. Read more about the motivation [here](https://arvid.xyz/prompt-design). 6 | 7 | ## Installation 8 | 9 | Install from npm: 10 | 11 | ```bash 12 | npm install @anysphere/priompt && npm install -D @anysphere/priompt-preview 13 | ``` 14 | 15 | or 16 | 17 | ```bash 18 | yarn add @anysphere/priompt && yarn add --dev @anysphere/priompt-preview 19 | ``` 20 | 21 | or 22 | 23 | ```bash 24 | pnpm add @anysphere/priompt && pnpm add -D @anysphere/priompt-preview 25 | ``` 26 | 27 | ## Examples 28 | 29 | Read [examples/README.md](examples/README.md) to run the examples. 30 | 31 | ## Principles 32 | 33 | Prompts are rendered from a JSX component, which can look something like this: 34 | 35 | ```jsx 36 | function ExamplePrompt( 37 | props: PromptProps<{ 38 | name: string, 39 | message: string, 40 | history: { case: "user" | "assistant", message: string }[], 41 | }> 42 | ): PromptElement { 43 | const capitalizedName = props.name[0].toUpperCase() + props.name.slice(1); 44 | return ( 45 | <> 46 | 47 | The user's name is {capitalizedName}. Please respond to them kindly. 48 | 49 | {props.history.map((m, i) => ( 50 | 51 | {m.case === "user" ? ( 52 | {m.message} 53 | ) : ( 54 | {m.message} 55 | )} 56 | 57 | ))} 58 | {props.message} 59 | 60 | 61 | ); 62 | } 63 | ``` 64 | 65 | A component is rendered only once. Each child has a priority, where a higher priority means that the child is more important to include in the prompt. If no priority is specified, the child is included if and only if its parent is included. Absolute priorities are specified with `p` and relative ones are specified with `prel`. 66 | 67 | In the example above, we always include the system message and the latest user message, and are including as many messages from the history as possible, where later messages are prioritized over earlier messages. 68 | 69 | The key promise of the priompt renderer is: 70 | 71 | > Let $T$ be the token limit and $\text{Prompt}(p_\text{cutoff})$ be the function that creates a prompt by including all scopes with priority $p_\text{scope} \geq p_\text{cutoff}$, and no other. Then, the rendered prompt is $\text{\textbf{P}} = \text{Prompt}(p_\text{opt-cutoff})$ where $p_\text{opt-cutoff}$ is the minimum value such that $|\text{Prompt}(p_\text{opt-cutoff})| \leq T$. 72 | 73 | The building blocks of a priompt prompt are: 74 | 75 | 1. ``: this allows you to set priorities `p` for absolute or `prel` for relative. 76 | 2. ``: the first child with a sufficiently high priority will be included, and all children below it will not. This is useful for fallbacks for implementing something like "when the result is too long we want to say `(result omitted)`". 77 | 3. ``: for specifying empty space, useful for reserving tokens for generation. 78 | 4. ``: capture the output and parse it right within the prompt. 79 | 5. ``: isolate a section of the prompt with its own token limit. This is useful for guaranteeing that the start of the prompt will be the same for caching purposes. it would be nice to extend this to allow token limits like `100% - 100`. 80 | 6. `
`: force a token break at a particular location, which is useful for ensuring exact tokenization matches between two parts of a prompt (e.g. when implementing something like speculative edits). 81 | 7. ``: specify a few common configuration properties, such as `stop` token and `maxResponseTokens`, which can make the priompt dump more self-contained and help with evals. 82 | 83 | You can create components all you want, just like in React. The builtin components are: 84 | 85 | 1. ``, `` and ``: for building message-based prompts. 86 | 2. ``: for adding images into the prompt. 87 | 3. ``, ``: for specifying tools that the AI can call, either using a JSON schema or a Zod type. 88 | 89 | ## Advanced features 90 | 91 | 1. `onEject` and `onInclude`: callbacks that can be passed into any scope, which are called when the scope is either excluded or included in the final prompt. This allows you to change your logic depending on if something is too large for the prompt. 92 | 2. Sourcemaps: when setting `shouldBuildSourceMap` to `true`, the renderer computes a map between the actual characters in the prompt and the part of the JSX tree that they came from. This can be useful to figure out where cache misses are coming from in the prompt. 93 | 3. Prepend `DO_NOT_DUMP` to your priompt props key to prevent it from being dumped, which is useful for really big objects. 94 | 95 | 96 | ## Future 97 | 98 | A few things that would be cool to add: 99 | 100 | 1. A `` block: specify a `limit` on the number of tokens within a scope, but unlike ``, include the inner scopes in the global priority calculation. 101 | 2. Performance-optimized rendering of big trees: minimizing time spent tokenizing is part of it, but part of it is also working around JavaScript object allocation, and it is possible that writing the entire rendering engine in Rust, for example, would make it a lot faster. 102 | 103 | ## Caveats 104 | 105 | 1. We've discovered that adding priorities to everything is sort of an anti-pattern. It is possible that priorities are the wrong abstraction. We have found them useful though for including long files in the prompt in a line-by-line way. 106 | 2. The Priompt renderer has no builtin support for creating cacheable prompts. If you overuse priorities, it is easy to make hard-to-cache prompts, which may increase your cost or latency for LLM inference. We are interested in good solutions here, but for now it is up to the prompt designer to think about caching. 107 | 1. *Update: Priompt sourcemaps help with caching debugging!* 108 | 3. The current version of priompt only supports around 10K scopes reasonably fast (this is enough for most use cases). If you want to include a file in the prompt that is really long (>10K lines), and you split it line-by-line, you probably want to implement something like "for lines farther than 1000 lines away from the cursor position we have coarser scopes of 10 lines at a time". 109 | 4. For latency-critical prompts you want to monitor the time usage in the priompt preview dashboard. If there are too many scopes you may want to optimize for performance. 110 | 5. The Priompt renderer is not always guaranteed to produce the perfect $p_\text{opt-cutoff}$. For example, if a higher-priority child of a `` has more tokens than a lower-priority child, the currently implemented binary search renderer may return a (very slightly) incorrect result. 111 | 112 | ## Contributions 113 | 114 | Contributions are very welcome! This entire repo is MIT-licensed. 115 | -------------------------------------------------------------------------------- /examples/.env.example: -------------------------------------------------------------------------------- 1 | SERVER_PORT=8008 2 | NODE_ENV=development 3 | OPENAI_API_KEY=sk-your-openai-secret-key 4 | 5 | PRIOMPT_PREVIEW_PORT=6284 6 | PRIOMPT_PREVIEW_SERVER_PORT=$SERVER_PORT 7 | PRIOMPT_PREVIEW_OPENAI_KEY=$OPENAI_API_KEY -------------------------------------------------------------------------------- /examples/.eslintignore: -------------------------------------------------------------------------------- 1 | vitest.config.ts -------------------------------------------------------------------------------- /examples/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../.eslintrc.base"], 3 | "rules": { 4 | // additional rules specific to this config 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .env 4 | priompt/*/dumps/**/* -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Priompt examples 2 | 3 | An example showing how to use `priompt` and `priompt-preview`; somewhat useful for testing random prompts. 4 | 5 | This example uses `fastify` for the server, but any server library or framework should work 6 | 7 | ## Running 8 | 9 | First run: 10 | 11 | ```bash 12 | cd .. && ./init.sh 13 | ``` 14 | 15 | Then configure your OpenAI key in `.env`. 16 | 17 | In one terminal: 18 | 19 | ```bash 20 | pnpm priompt 21 | ``` 22 | 23 | In another: 24 | 25 | ```bash 26 | pnpm watch 27 | ``` 28 | 29 | In a third: 30 | 31 | ```bash 32 | curl 'localhost:8008/message?message=what%20is%20the%20advantage%20of%20rust%20over%20c&name=a%20curious%20explorer' 33 | ``` 34 | 35 | You should get a response within a few seconds. 36 | 37 | Go to [localhost:6284](http://localhost:6284) to see the prompt in the priompt preview. 38 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "priompt-examples", 3 | "license": "MIT", 4 | "engines": { 5 | "node": ">= 18.15.0" 6 | }, 7 | "scripts": { 8 | "watch": "npm-run-all -p watch-src watch-priompt watch-priompt-preview", 9 | "watch-src": "sleep 3 && dotenv -e .env tsx watch --clear-screen=false src", 10 | "watch-priompt": "cd ../priompt && pnpm build-watch", 11 | "watch-priompt-preview": "cd ../priompt-preview && pnpm build-watch", 12 | "lint": "tsc --noEmit && eslint .", 13 | "test": "vitest", 14 | "coverage": "vitest run --coverage", 15 | "priompt": "dotenv -e .env pnpm npx @anysphere/priompt-preview serve" 16 | }, 17 | "devDependencies": { 18 | "@anysphere/priompt-preview": "workspace:*", 19 | "@types/node": "^20.12.0", 20 | "@typescript-eslint/eslint-plugin": "^5.59.0", 21 | "@typescript-eslint/parser": "^5.59.0", 22 | "@vitest/coverage-v8": "^1.2.2", 23 | "eslint": "^8.38.0", 24 | "npm-run-all": "^4.1.5", 25 | "tsx": "^3.12.6", 26 | "typescript": "^5.2.0", 27 | "vitest": "^1.2.2" 28 | }, 29 | "dependencies": { 30 | "@anysphere/priompt": "workspace:*", 31 | "@fastify/cors": "^8.3.0", 32 | "dotenv": "^16.1.4", 33 | "dotenv-cli": "^7.2.1", 34 | "fastify": "^4.17.0", 35 | "openai-v4": "npm:openai@4.0.0-beta.6", 36 | "openai": "^3.3.0", 37 | "zod": "^3.21.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/priompt/ArvidStory/example01.yaml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/priompt/SimpleFunction/example01.yaml: -------------------------------------------------------------------------------- 1 | code: |- 2 | function x() { 3 | return z.object({ 4 | a: z.string(), 5 | b: z.number(), 6 | }); 7 | } 8 | error: '''z'' is not defined' 9 | -------------------------------------------------------------------------------- /examples/priompt/SimplePrompt/example01.yaml: -------------------------------------------------------------------------------- 1 | text: Cursor är den bästa plattformen för att skriva kod. 2 | language: swahili 3 | -------------------------------------------------------------------------------- /examples/priompt/examplePrompt/example01.yaml: -------------------------------------------------------------------------------- 1 | message: what is the advantage of rust over c 2 | name: arvid 3 | -------------------------------------------------------------------------------- /examples/priompt/functionCallingPrompt/example01.yaml: -------------------------------------------------------------------------------- 1 | message: bad the prompt buton not work 2 | includeFunctions: 3 | - insert_sql_row 4 | -------------------------------------------------------------------------------- /examples/src/function-calling-prompt.tsx: -------------------------------------------------------------------------------- 1 | import * as Priompt from "@anysphere/priompt"; 2 | import { 3 | PreviewConfig, 4 | PreviewManager, 5 | PromptElement, 6 | PromptProps, 7 | SystemMessage, 8 | UserMessage, 9 | Function, 10 | FunctionMessage, 11 | AssistantMessage, 12 | ZFunction, 13 | } from "@anysphere/priompt"; 14 | import { z } from "zod"; 15 | 16 | const FunctionCallingPromptConfig: PreviewConfig = { 17 | id: "functionCallingPrompt", 18 | prompt: FunctionCallingPrompt, 19 | }; 20 | 21 | export type FunctionCallingPromptProps = PromptProps<{ 22 | message: string; 23 | includeFunctions: string[]; 24 | causeConfusion: boolean; 25 | }>; 26 | 27 | PreviewManager.registerConfig(FunctionCallingPromptConfig); 28 | 29 | // array of 10000 integers 30 | const arr = Array.from(Array(800).keys()); 31 | 32 | export function FunctionCallingPrompt( 33 | props: FunctionCallingPromptProps, 34 | args?: { dump?: boolean } 35 | ): PromptElement { 36 | if (args?.dump === true) { 37 | PreviewManager.dump(FunctionCallingPromptConfig, props); 38 | } 39 | return ( 40 | <> 41 | {props.includeFunctions.includes("insert_sql_row") && ( 42 | 61 | )} 62 | {props.includeFunctions.includes("update_sql_row") && ( 63 | 86 | )} 87 | 88 | You are a database manager, responsible for taking the user's message 89 | and inserting it into our database. 90 | 91 | {props.causeConfusion && ( 92 | <> 93 | i love the color theme 94 | 103 | 104 | Inserted 1 row. 105 | 106 | 107 | )} 108 | 109 | {props.message} 110 | {/* {arr.map((i) => ( 111 | {props.message} 112 | ))} */} 113 | 114 | 115 | 116 | ); 117 | } 118 | 119 | // returns the new code 120 | PreviewManager.register(SimpleFunction); 121 | export function SimpleFunction( 122 | props: PromptProps< 123 | { 124 | code: string; 125 | error: string; 126 | }, 127 | | { 128 | type: "newImport"; 129 | newImport: string; 130 | } 131 | | { 132 | type: "newCode"; 133 | newCode: string; 134 | } 135 | > 136 | ) { 137 | return ( 138 | <> 139 | { 148 | return await props.onReturn({ 149 | type: "newImport", 150 | newImport: args.import, 151 | }); 152 | }} 153 | /> 154 | 155 | You are a coding assistant. The user will give you a function that has 156 | linter errors. Your job is to fix the errors. You have two options: 157 | either, you can call the `add_import` function, which adds an import 158 | statement at the top of the file, or you can rewrite the entire 159 | function. If you rewrite the function, start your message with ```. 160 | 161 | 162 | Function: 163 |
164 | ``` 165 |
166 | {props.code} 167 |
168 | ``` 169 |
170 |
171 | Errors: 172 |
173 | ``` 174 |
175 | {props.error} 176 |
177 | ``` 178 |
179 |
180 | { 182 | if (msg.content !== undefined) { 183 | return await props.onReturn({ 184 | type: "newCode", 185 | newCode: msg.content, 186 | }); 187 | } 188 | }} 189 | /> 190 | 191 | ); 192 | } 193 | -------------------------------------------------------------------------------- /examples/src/index.ts: -------------------------------------------------------------------------------- 1 | import { promptToOpenAIChatMessages, promptToOpenAIChatRequest, render, renderun } from '@anysphere/priompt'; 2 | import { handlePriomptPreview } from './priompt-preview-handlers'; 3 | import { ArvidStory, ExamplePrompt, SimplePrompt } from './prompt'; 4 | import fastifyCors from "@fastify/cors"; 5 | import Fastify, { FastifyError, FastifyLoggerOptions, FastifyReply, FastifyRequest, RawServerDefault, RouteGenericInterface } from "fastify"; 6 | import { OpenAI as OpenAIV4 } from 'openai-v4'; 7 | import { FunctionCallingPrompt, SimpleFunction } from './function-calling-prompt'; 8 | import { ChatCompletionResponseMessage, Configuration, CreateChatCompletionRequest, OpenAIApi } from 'openai'; 9 | 10 | const portString = process.env.SERVER_PORT; 11 | if (portString === undefined || Number.isNaN(parseInt(portString))) { 12 | throw new Error("SERVER_PORT is undefined. Please run the ./init.sh script to create a .env file."); 13 | } 14 | const port = parseInt(portString); 15 | 16 | const S = Fastify(); 17 | 18 | if (process.env.OPENAI_API_KEY === undefined || process.env.OPENAI_API_KEY === "" || process.env.OPENAI_API_KEY === "sk-your-openai-secret-key") { 19 | throw new Error("OPENAI_API_KEY is undefined. Please run the ./init.sh script to create a .env file, and then insert your API key in the .env file."); 20 | } 21 | 22 | const openaiV4 = new OpenAIV4({ 23 | apiKey: process.env.OPENAI_API_KEY, 24 | }); 25 | const configuration = new Configuration({ 26 | apiKey: process.env.OPENAI_API_KEY, 27 | }); 28 | const openai = new OpenAIApi(configuration); 29 | 30 | function messageAdapter(old: ChatCompletionResponseMessage[]): OpenAIV4.Chat.CompletionCreateParams.CreateChatCompletionRequestNonStreaming.Message[] { 31 | return old as OpenAIV4.Chat.CompletionCreateParams.CreateChatCompletionRequestNonStreaming.Message[]; 32 | } 33 | function messageAdapterReverse(n: OpenAIV4.Chat.CompletionCreateParams.CreateChatCompletionRequestNonStreaming.Message): ChatCompletionResponseMessage { 34 | return n as ChatCompletionResponseMessage; 35 | } 36 | function requestAdapter(old: CreateChatCompletionRequest): OpenAIV4.Chat.CompletionCreateParams.CreateChatCompletionRequestNonStreaming { 37 | return old as OpenAIV4.Chat.CompletionCreateParams.CreateChatCompletionRequestNonStreaming; 38 | } 39 | 40 | async function main() { 41 | 42 | if (process.env.NODE_ENV === "development") { 43 | await handlePriomptPreview(S); 44 | } 45 | 46 | await S.register(fastifyCors, { 47 | origin: [ 48 | `http://localhost:${process.env.PRIOMPT_PREVIEW_PORT}` 49 | ], 50 | }); 51 | 52 | // here we can add any other routes we want! this can be good for testing stuff 53 | S.get("/", (_, reply) => { 54 | return reply.type("text/plain").send(`Welcome to Priompt examples.`); 55 | }); 56 | S.get("/message", async (request, reply) => { 57 | const query = request.query as { message: string; name: string }; 58 | if (query.message === undefined || query.name === undefined) { 59 | return reply.status(400).send("Bad request; message and name are required."); 60 | } 61 | const message = query.message as string; 62 | const name = query.name as string; 63 | const prompt = ExamplePrompt({ message, name }, { dump: process.env.NODE_ENV === "development" }); 64 | const output = await render(prompt, { 65 | model: "gpt-3.5-turbo" 66 | }); 67 | 68 | const requestConfig: CreateChatCompletionRequest = { 69 | model: "gpt-3.5-turbo", 70 | messages: promptToOpenAIChatMessages(output.prompt), 71 | }; 72 | 73 | try { 74 | const openaiResult = await openai.createChatCompletion(requestConfig); 75 | 76 | const openaiOutput = openaiResult.data.choices[0].message; 77 | 78 | return reply.type("text/plain").send(openaiOutput?.content); 79 | } catch (error) { 80 | console.error(error); 81 | return reply.status(500).send("Internal server error."); 82 | } 83 | }); 84 | S.get("/database", async (request, reply) => { 85 | const query = request.query as { message: string; confuse: string | undefined; }; 86 | if (query.message === undefined) { 87 | return reply.status(400).send("Bad request; message is required."); 88 | } 89 | const message = query.message as string; 90 | const prompt = FunctionCallingPrompt({ message, includeFunctions: ["insert_sql_row", "update_sql_row"], causeConfusion: query.confuse === "true" }, { dump: process.env.NODE_ENV === "development" }); 91 | const output = await render(prompt, { 92 | model: "gpt-3.5-turbo" 93 | }); 94 | 95 | console.log(JSON.stringify(output.prompt, null, 2)); 96 | 97 | const requestConfig: CreateChatCompletionRequest = { 98 | ...promptToOpenAIChatRequest(output.prompt), 99 | model: "gpt-3.5-turbo-0613", 100 | }; 101 | 102 | // make this print all nested values in node 103 | console.log(JSON.stringify(requestConfig, null, 2)); 104 | 105 | try { 106 | const openaiResult = await openai.createChatCompletion(requestConfig); 107 | 108 | const openaiOutput = openaiResult.data.choices[0]; 109 | 110 | return reply.type("text/json").send(JSON.stringify(openaiOutput)); 111 | } catch (error) { 112 | console.error(error); 113 | return reply.status(500).send("Internal server error."); 114 | } 115 | }); 116 | 117 | S.get("/simple", async (request, reply) => { 118 | const query = request.query as { language: string; }; 119 | if (query.language === undefined) { 120 | return reply.status(400).send("Bad request; language is required."); 121 | } 122 | const language = query.language as string; 123 | const text = "Cursor är den bästa plattformen för att skriva kod."; 124 | try { 125 | const answer = await renderun({ 126 | prompt: SimplePrompt, 127 | props: { text, language }, 128 | renderOptions: { 129 | model: "gpt-3.5-turbo", 130 | }, 131 | modelCall: async (x) => { return { type: "output", value: (await openai.createChatCompletion({ ...x, model: "gpt-3.5-turbo" })).data } } 132 | }); 133 | return reply.type("text/plain").send(JSON.stringify({ answer })); 134 | } catch (error) { 135 | console.error(error); 136 | return reply.status(500).send("Internal server error."); 137 | } 138 | }); 139 | 140 | S.get("/arvidstory", async (request, reply) => { 141 | try { 142 | const answer = await renderun({ 143 | prompt: ArvidStory, 144 | props: {}, 145 | renderOptions: { 146 | model: "gpt-3.5-turbo", 147 | }, 148 | modelCall: async (x) => { 149 | const y = await openaiV4.chat.completions.create({ ...requestAdapter({ ...x, model: "gpt-3.5-turbo" }), stream: true }); 150 | return { 151 | type: "stream", 152 | value: (async function* () { 153 | for await (const message of y) { 154 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 155 | yield messageAdapterReverse(message.choices[0].delta as any); 156 | } 157 | })() 158 | } 159 | } 160 | }); 161 | let s = ""; 162 | for await (const part of answer) { 163 | s += part; 164 | console.log(part); 165 | } 166 | return reply.type("text/plain").send(JSON.stringify({ answer: s })); 167 | } catch (error) { 168 | console.error(error); 169 | return reply.status(500).send("Internal server error."); 170 | } 171 | }); 172 | 173 | S.get("/fixcode", async (request, reply) => { 174 | const query = request.query as { type: string; }; 175 | let code, error: string; 176 | if (query.type === undefined || query.type !== 'code') { 177 | code = "function x() {\n\treturn z.object({\n\t\ta: z.string(),\n\t\tb: z.number(),\n\t});\n}"; 178 | error = "'z' is not defined"; 179 | } else { 180 | code = "function x() {\n\treturn z.object({\n\t\ta: z.string(),\n\t\tb: z.umber(),\n\t});\n}"; 181 | error = "'umber' is not defined"; 182 | } 183 | try { 184 | const action = await renderun({ 185 | prompt: SimpleFunction, 186 | props: { code, error }, 187 | renderOptions: { 188 | model: "gpt-4", 189 | }, 190 | modelCall: async (x) => { return { type: 'output', value: (await openai.createChatCompletion({ ...x, model: "gpt-4" })).data } } 191 | }); 192 | return reply.type("text/plain").send(JSON.stringify(action)); 193 | } catch (error) { 194 | console.error(error); 195 | return reply.status(500).send("Internal server error."); 196 | } 197 | }); 198 | 199 | await S.listen({ host: "0.0.0.0", port }); 200 | 201 | console.log(`Server listening on port ${port}.`); 202 | } 203 | 204 | void main(); -------------------------------------------------------------------------------- /examples/src/priompt-preview-handlers.ts: -------------------------------------------------------------------------------- 1 | import { FastifyInstance } from 'fastify'; 2 | import { PreviewManager, PreviewManagerGetPromptQuery, PreviewManagerLiveModeQuery, PreviewManagerLiveModeResultQuery } from '@anysphere/priompt'; 3 | import { PreviewManagerGetPromptOutputQuery } from '@anysphere/priompt/dist/preview'; 4 | 5 | 6 | export async function handlePriomptPreview(S: FastifyInstance) { 7 | S.get("/priompt/getPreviews", async (_, reply) => { 8 | return reply.type("text/json").send(JSON.stringify(PreviewManager.getPreviews())); 9 | }); 10 | 11 | S.get('/priompt/getPrompt', async (request, reply) => { 12 | const query = request.query as PreviewManagerGetPromptQuery; 13 | return reply.type("text/json").send(JSON.stringify(await PreviewManager.getPrompt(query))); 14 | }); 15 | 16 | S.get('/priompt/getPromptOutput', async (request, reply) => { 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | const newQ = request.query as any; 19 | const query = JSON.parse(newQ.v) as PreviewManagerGetPromptOutputQuery; 20 | const stringified = JSON.stringify(await PreviewManager.getPromptOutput(query)); 21 | return reply.type("text/json").send(stringified); 22 | }); 23 | 24 | S.get('/priompt/liveMode', async (request, reply) => { 25 | const query = request.query as PreviewManagerLiveModeQuery; 26 | 27 | try { 28 | const output = await PreviewManager.liveMode(query) 29 | await reply.type("text/json").send(JSON.stringify(output)); 30 | } catch (error) { 31 | if (error.name === 'AbortError') { 32 | return reply.status(500).send({ error: 'Request aborted' }); 33 | } else { 34 | throw error; 35 | } 36 | } 37 | }); 38 | S.get("/priompt/liveModeResult", (request, reply) => { 39 | const query = request.query as PreviewManagerLiveModeResultQuery; 40 | PreviewManager.liveModeResult(query); 41 | return reply.type("text/json").send(JSON.stringify({})); 42 | }); 43 | 44 | } -------------------------------------------------------------------------------- /examples/src/prompt.tsx: -------------------------------------------------------------------------------- 1 | import * as Priompt from "@anysphere/priompt"; 2 | import { 3 | PreviewConfig, 4 | PreviewManager, 5 | PromptElement, 6 | PromptProps, 7 | SystemMessage, 8 | UserMessage, 9 | } from "@anysphere/priompt"; 10 | 11 | const ExamplePromptConfig: PreviewConfig = { 12 | id: "examplePrompt", 13 | prompt: ExamplePrompt, 14 | }; 15 | PreviewManager.registerConfig(ExamplePromptConfig); 16 | 17 | export type ExamplePromptProps = PromptProps<{ 18 | name: string; 19 | message: string; 20 | }>; 21 | 22 | export function ExamplePrompt( 23 | props: ExamplePromptProps, 24 | args?: { dump?: boolean } 25 | ): PromptElement { 26 | if (args?.dump === true) { 27 | PreviewManager.dump(ExamplePromptConfig, props); 28 | } 29 | return ( 30 | <> 31 | 32 | The user's name is {props.name}. Please always greet them in an 33 | extremely formal, medieval style, with lots of fanfare. Then seamlessly 34 | proceed to reply to their message in the most casual, 2010s, cool dude 35 | texting style. Please be over-the-top in both respects, and make the 36 | transition seem like it never happened. 37 | 38 | {props.message} 39 | 40 | 41 | ); 42 | } 43 | 44 | PreviewManager.register(SimplePrompt); 45 | export function SimplePrompt( 46 | props: PromptProps< 47 | { 48 | language: string; 49 | text: string; 50 | }, 51 | boolean 52 | > 53 | ): PromptElement { 54 | return ( 55 | <> 56 | 57 | Please determine if the following text is in {props.language}. If it is, 58 | please reply with "yes". If it is not, please reply with "no". Do not 59 | output anything else. 60 | 61 | {props.text} 62 | 63 | { 65 | if (output.content?.toLowerCase().includes("yes") === true) { 66 | return await props.onReturn(true); 67 | } else if (output.content?.toLowerCase().includes("no") === true) { 68 | return await props.onReturn(false); 69 | } 70 | // bad 71 | throw new Error(`Invalid output: ${output.content}`); 72 | }} 73 | /> 74 | 75 | ); 76 | } 77 | 78 | PreviewManager.register(ArvidStory); 79 | export function ArvidStory( 80 | props: PromptProps> 81 | ): PromptElement { 82 | return ( 83 | <> 84 | 85 | Please write a short story about a young boy named Arvid. Only a 86 | paragraph please. 87 | 88 | 89 | { 91 | // we want to replace every R with a J 92 | await props.onReturn( 93 | (async function* () { 94 | for await (const chunk of stream) { 95 | if (chunk.content === undefined) { 96 | continue; 97 | } 98 | yield chunk.content.replace(/r/g, "j"); 99 | } 100 | })() 101 | ); 102 | }} 103 | /> 104 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "outDir": "./dist", 5 | "strictNullChecks": true, 6 | "noImplicitAny": true, 7 | "declaration": true, 8 | "isolatedModules": true, 9 | "target": "es2022", 10 | "moduleResolution": "node", 11 | "jsx": "react", 12 | "jsxFactory": "Priompt.createElement", 13 | "jsxFragmentFactory": "Priompt.Fragment", 14 | "strictPropertyInitialization": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/vitest.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | test: { 3 | include: [ 4 | 'src/**/*.{test,spec}.{js,ts,jsx,tsx}', 5 | // Also include top level files 6 | 'src/*.{test,spec}.{js,ts,jsx,tsx}' 7 | ], 8 | exclude: ['build/**/*'], 9 | }, 10 | }; -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 4 | 5 | echo '{ 6 | "packages": ["priompt", "priompt-preview", "examples", "tiktoken-node"] 7 | }' > "$SCRIPT_DIR"/pnpm-workspace.yaml 8 | 9 | echo '[workspace] 10 | members = ["tiktoken-node"] 11 | resolver = "2" 12 | ' > "$SCRIPT_DIR"/Cargo.toml 13 | 14 | # copy over the examples/.env.example to examples/.env 15 | cp -f "$SCRIPT_DIR"/examples/.env.example "$SCRIPT_DIR"/examples/.env 16 | 17 | pnpm i -r 18 | -------------------------------------------------------------------------------- /priompt-preview/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, es2020: true, node: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:react-hooks/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | plugins: ['react-refresh'], 11 | rules: { 12 | 'react-refresh/only-export-components': 'warn', 13 | // disable unused vars check 14 | '@typescript-eslint/no-unused-vars': 'off', 15 | '@typescript-eslint/no-empty-interface': 'off', 16 | '@typescript-eslint/no-inferrable-types': 'off', 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /priompt-preview/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /priompt-preview/.npmignore: -------------------------------------------------------------------------------- 1 | .wireit 2 | !dist/** -------------------------------------------------------------------------------- /priompt-preview/README.md: -------------------------------------------------------------------------------- 1 | Run `pnpm build` or `pnpm build-watch` to build the library. 2 | -------------------------------------------------------------------------------- /priompt-preview/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "stone", 10 | "cssVariables": false 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /priompt-preview/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Priompt Preview 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /priompt-preview/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@anysphere/priompt-preview", 3 | "license": "MIT", 4 | "version": "0.2.1", 5 | "description": "An interactive preview of priompt prompts.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/anysphere/priompt" 9 | }, 10 | "homepage": "https://github.com/anysphere/priompt", 11 | "author": "Arvid Lunnemark", 12 | "type": "module", 13 | "scripts": { 14 | "dev": "vite", 15 | "build": "wireit", 16 | "build-watch": "nodemon --watch 'src/**/*' --ext '*' --exec 'pnpm build'", 17 | "lint": "wireit", 18 | "tsc-build": "wireit", 19 | "priompt:build": "wireit", 20 | "preview": "vite preview" 21 | }, 22 | "wireit": { 23 | "build": { 24 | "command": "vite build", 25 | "files": [ 26 | "src/**/*.ts", 27 | "src/**/*.tsx", 28 | "tsconfig.json" 29 | ], 30 | "output": [ 31 | "dist/**/*" 32 | ], 33 | "clean": "if-file-deleted", 34 | "dependencies": [ 35 | "priompt:build" 36 | ] 37 | }, 38 | "tsc-build": { 39 | "command": "tsc --build --pretty", 40 | "clean": "if-file-deleted", 41 | "files": [ 42 | "src/**/*.ts", 43 | "src/**/*.tsx", 44 | "tsconfig.json" 45 | ], 46 | "output": [ 47 | "dist/**/*" 48 | ], 49 | "dependencies": [ 50 | "priompt:build" 51 | ] 52 | }, 53 | "priompt:build": { 54 | "command": "pnpm i", 55 | "dependencies": [ 56 | "../priompt:build" 57 | ], 58 | "files": [], 59 | "output": [] 60 | }, 61 | "lint": { 62 | "command": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 63 | "files": [ 64 | "src/**/*", 65 | "tsconfig.json" 66 | ], 67 | "clean": "if-file-deleted", 68 | "dependencies": [ 69 | "priompt:build" 70 | ] 71 | } 72 | }, 73 | "dependencies": { 74 | "@radix-ui/react-dialog": "^1.0.5", 75 | "@radix-ui/react-icons": "^1.3.0", 76 | "@radix-ui/react-slot": "^1.0.2", 77 | "class-variance-authority": "^0.7.0", 78 | "clsx": "^2.0.0", 79 | "cmdk": "^0.2.0", 80 | "js-tiktoken": "^1.0.7", 81 | "lucide-react": "^0.263.1", 82 | "tailwind-merge": "^1.14.0", 83 | "tailwindcss-animate": "^1.0.6", 84 | "uuid": "^9.0.0", 85 | "@anysphere/priompt": "workspace:*" 86 | }, 87 | "devDependencies": { 88 | "@types/react": "^18.0.28", 89 | "@types/react-dom": "^18.0.11", 90 | "@types/uuid": "^9.0.1", 91 | "@typescript-eslint/eslint-plugin": "^5.57.1", 92 | "@typescript-eslint/parser": "^5.57.1", 93 | "@vitejs/plugin-react": "^4.0.0", 94 | "autoprefixer": "^10.4.14", 95 | "axios": "^0.26.1", 96 | "eslint": "^8.38.0", 97 | "eslint-plugin-react-hooks": "^4.6.0", 98 | "eslint-plugin-react-refresh": "^0.3.4", 99 | "nodemon": "^2.0.22", 100 | "postcss": "^8.4.27", 101 | "react": "^18.2.0", 102 | "react-dom": "^18.2.0", 103 | "tailwindcss": "^3.3.3", 104 | "typescript": "^5.2.0", 105 | "use-debounce": "^9.0.4", 106 | "vite": "^4.3.2", 107 | "wireit": "^0.14.0" 108 | }, 109 | "bin": { 110 | "serve": "./scripts/serve.cjs" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /priompt-preview/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /priompt-preview/scripts/serve.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-var-requires */ 4 | 5 | const http = require('http'); 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const url = require('url'); 9 | 10 | // Add these new imports 11 | const yargs = require('yargs/yargs'); 12 | const { hideBin } = require('yargs/helpers'); 13 | 14 | // Parse command line arguments 15 | const argv = yargs(hideBin(process.argv)) 16 | .option('config', { 17 | describe: 'Path to config JSON file', 18 | type: 'string' 19 | }) 20 | .argv; 21 | 22 | let config = {}; 23 | if (argv.config) { 24 | try { 25 | const configFile = fs.readFileSync(argv.config, 'utf8'); 26 | config = JSON.parse(configFile); 27 | } catch (error) { 28 | console.error(`Error reading config file: ${error.message}`); 29 | process.exit(1); 30 | } 31 | } 32 | 33 | const port = process.env.PRIOMPT_PREVIEW_PORT || 6283; 34 | const server_port = process.env.PRIOMPT_PREVIEW_SERVER_PORT; 35 | if (!server_port) { 36 | console.error('PRIOMPT_PREVIEW_SERVER_PORT is not set. it needs to be set of the port where the priompt server is run'); 37 | process.exit(1); 38 | } 39 | const distPath = path.join(path.dirname(__dirname), 'dist'); 40 | 41 | const requestListener = (req, res) => { 42 | const parsedUrl = url.parse(req.url); 43 | const filePath = path.join(distPath, parsedUrl.pathname === '/' ? 'index.html' : parsedUrl.pathname); 44 | const extname = String(path.extname(filePath)).toLowerCase(); 45 | const mimeTypes = { 46 | '.html': 'text/html', 47 | '.js': 'application/javascript', 48 | '.css': 'text/css', 49 | '.jpg': 'image/jpeg', 50 | '.jpeg': 'image/jpeg', 51 | '.png': 'image/png', 52 | '.gif': 'image/gif', 53 | '.svg': 'image/svg+xml', 54 | // Add more MIME types if needed 55 | }; 56 | 57 | const contentType = mimeTypes[extname] || 'application/octet-stream'; 58 | 59 | fs.readFile(filePath, (err, data) => { 60 | if (err) { 61 | if (err.code === 'ENOENT') { 62 | res.writeHead(404); 63 | res.end('Not found'); 64 | } else { 65 | res.writeHead(500); 66 | res.end('Error loading file'); 67 | } 68 | } else { 69 | res.writeHead(200, { 'Content-Type': contentType }); 70 | if (data.toString().includes('localhost:3000')) { 71 | data = data.toString().replace(/localhost:3000/g, `localhost:${server_port}`); 72 | } 73 | 74 | // Use config.chatModels if available, otherwise fall back to PRIOMPT_PREVIEW_MODELS 75 | if (config.chatModels) { 76 | data = data.toString().replace(/\["gpt-3.5-turbo","gpt-4"\]/, JSON.stringify(config.chatModels)); 77 | } else if (process.env.PRIOMPT_PREVIEW_MODELS) { 78 | data = data.toString().replace(/\["gpt-3.5-turbo","gpt-4"\]/, process.env.PRIOMPT_PREVIEW_MODELS); 79 | } 80 | 81 | // Use config.completionModels if available, otherwise fall back to PRIOMPT_PREVIEW_COMPLETION_MODELS 82 | if (config.completionModels) { 83 | data = data.toString().replace(/text-davinci-003,code-davinci-002/, config.completionModels.join(',')); 84 | } else if (process.env.PRIOMPT_PREVIEW_COMPLETION_MODELS) { 85 | const completionModels = process.env.PRIOMPT_PREVIEW_COMPLETION_MODELS.split(','); 86 | data = data.toString().replace(/text-davinci-003,code-davinci-002/, completionModels); 87 | } 88 | 89 | if ((extname === '.html' || extname === '.js') && data.toString().includes('PRIOMPT_PREVIEW_OPENAI_KEY')) { 90 | data = data.toString().replace(/PRIOMPT_PREVIEW_OPENAI_KEY/g, `${process.env.PRIOMPT_PREVIEW_OPENAI_KEY}`); 91 | } 92 | if ((extname === '.html' || extname === '.js') && data.toString().includes('PRIOMPT_PREVIEW_OSS_ENDPOINTS_JSON_STRING')) { 93 | data = data.toString().replace(/PRIOMPT_PREVIEW_OSS_ENDPOINTS_JSON_STRING/g, `${process.env.PRIOMPT_PREVIEW_OSS_ENDPOINTS_JSON_STRING ?? "PRIOMPT_PREVIEW_OSS_ENDPOINTS_JSON_STRING"}`); 94 | } 95 | res.end(data); 96 | } 97 | }); 98 | }; 99 | 100 | const server = http.createServer(requestListener); 101 | server.listen(port, () => { 102 | console.log(JSON.stringify({ 103 | "level": "info", 104 | "time": new Date().getTime(), 105 | "pid": process.pid, 106 | "msg": `Server is running on http://localhost:${port}` 107 | })); 108 | }); 109 | -------------------------------------------------------------------------------- /priompt-preview/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-stone-400 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-stone-800", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-stone-900 text-stone-50 shadow hover:bg-stone-900/90 dark:bg-stone-50 dark:text-stone-900 dark:hover:bg-stone-50/90", 14 | destructive: 15 | "bg-red-500 text-stone-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-red-50 dark:hover:bg-red-900/90", 16 | outline: 17 | "border border-stone-200 bg-white shadow-sm hover:bg-stone-100 hover:text-stone-900 dark:border-stone-800 dark:bg-stone-950 dark:hover:bg-stone-800 dark:hover:text-stone-50", 18 | secondary: 19 | "bg-stone-100 text-stone-900 shadow-sm hover:bg-stone-100/80 dark:bg-stone-800 dark:text-stone-50 dark:hover:bg-stone-800/80", 20 | ghost: 21 | "hover:bg-stone-100 hover:text-stone-900 dark:hover:bg-stone-800 dark:hover:text-stone-50", 22 | link: "text-stone-900 underline-offset-4 hover:underline dark:text-stone-50", 23 | }, 24 | size: { 25 | default: "h-9 px-1 py-1", 26 | sm: "h-8 rounded-md px-1 text-xs", 27 | lg: "h-10 rounded-md px-1", 28 | icon: "h-9 w-9", 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: "default", 33 | size: "default", 34 | }, 35 | } 36 | ); 37 | 38 | export interface ButtonProps 39 | extends React.ButtonHTMLAttributes, 40 | VariantProps { 41 | asChild?: boolean; 42 | } 43 | 44 | const Button = React.forwardRef( 45 | ({ className, variant, size, asChild = false, ...props }, ref) => { 46 | const Comp = asChild ? Slot : "button"; 47 | return ( 48 | 53 | ); 54 | } 55 | ); 56 | Button.displayName = "Button"; 57 | 58 | // eslint-disable-next-line react-refresh/only-export-components 59 | export { Button, buttonVariants }; 60 | -------------------------------------------------------------------------------- /priompt-preview/src/components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { DialogProps } from "@radix-ui/react-dialog" 3 | import { MagnifyingGlassIcon } from "@radix-ui/react-icons" 4 | import { Command as CommandPrimitive } from "cmdk" 5 | 6 | import { cn } from "@/lib/utils" 7 | import { Dialog, DialogContent } from "@/components/ui/dialog" 8 | 9 | const Command = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | )) 22 | Command.displayName = CommandPrimitive.displayName 23 | 24 | interface CommandDialogProps extends DialogProps {} 25 | 26 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => { 27 | return ( 28 | 29 | 30 | 31 | {children} 32 | 33 | 34 | 35 | ) 36 | } 37 | 38 | const CommandInput = React.forwardRef< 39 | React.ElementRef, 40 | React.ComponentPropsWithoutRef 41 | >(({ className, ...props }, ref) => ( 42 |
43 | 44 | 52 |
53 | )) 54 | 55 | CommandInput.displayName = CommandPrimitive.Input.displayName 56 | 57 | const CommandList = React.forwardRef< 58 | React.ElementRef, 59 | React.ComponentPropsWithoutRef 60 | >(({ className, ...props }, ref) => ( 61 | 66 | )) 67 | 68 | CommandList.displayName = CommandPrimitive.List.displayName 69 | 70 | const CommandEmpty = React.forwardRef< 71 | React.ElementRef, 72 | React.ComponentPropsWithoutRef 73 | >((props, ref) => ( 74 | 79 | )) 80 | 81 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName 82 | 83 | const CommandGroup = React.forwardRef< 84 | React.ElementRef, 85 | React.ComponentPropsWithoutRef 86 | >(({ className, ...props }, ref) => ( 87 | 95 | )) 96 | 97 | CommandGroup.displayName = CommandPrimitive.Group.displayName 98 | 99 | const CommandSeparator = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName 110 | 111 | const CommandItem = React.forwardRef< 112 | React.ElementRef, 113 | React.ComponentPropsWithoutRef 114 | >(({ className, ...props }, ref) => ( 115 | 123 | )) 124 | 125 | CommandItem.displayName = CommandPrimitive.Item.displayName 126 | 127 | const CommandShortcut = ({ 128 | className, 129 | ...props 130 | }: React.HTMLAttributes) => { 131 | return ( 132 | 139 | ) 140 | } 141 | CommandShortcut.displayName = "CommandShortcut" 142 | 143 | export { 144 | Command, 145 | CommandDialog, 146 | CommandInput, 147 | CommandList, 148 | CommandEmpty, 149 | CommandGroup, 150 | CommandItem, 151 | CommandShortcut, 152 | CommandSeparator, 153 | } 154 | -------------------------------------------------------------------------------- /priompt-preview/src/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as DialogPrimitive from "@radix-ui/react-dialog" 3 | import { Cross2Icon } from "@radix-ui/react-icons" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Dialog = DialogPrimitive.Root 8 | 9 | const DialogTrigger = DialogPrimitive.Trigger 10 | 11 | const DialogPortal = DialogPrimitive.Portal 12 | 13 | const DialogClose = DialogPrimitive.Close 14 | 15 | const DialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, ...props }, ref) => ( 19 | 27 | )) 28 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 29 | 30 | const DialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, children, ...props }, ref) => ( 34 | 35 | 36 | 44 | {children} 45 | 46 | 47 | Close 48 | 49 | 50 | 51 | )) 52 | DialogContent.displayName = DialogPrimitive.Content.displayName 53 | 54 | const DialogHeader = ({ 55 | className, 56 | ...props 57 | }: React.HTMLAttributes) => ( 58 |
65 | ) 66 | DialogHeader.displayName = "DialogHeader" 67 | 68 | const DialogFooter = ({ 69 | className, 70 | ...props 71 | }: React.HTMLAttributes) => ( 72 |
79 | ) 80 | DialogFooter.displayName = "DialogFooter" 81 | 82 | const DialogTitle = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef 85 | >(({ className, ...props }, ref) => ( 86 | 94 | )) 95 | DialogTitle.displayName = DialogPrimitive.Title.displayName 96 | 97 | const DialogDescription = React.forwardRef< 98 | React.ElementRef, 99 | React.ComponentPropsWithoutRef 100 | >(({ className, ...props }, ref) => ( 101 | 106 | )) 107 | DialogDescription.displayName = DialogPrimitive.Description.displayName 108 | 109 | export { 110 | Dialog, 111 | DialogPortal, 112 | DialogOverlay, 113 | DialogTrigger, 114 | DialogClose, 115 | DialogContent, 116 | DialogHeader, 117 | DialogFooter, 118 | DialogTitle, 119 | DialogDescription, 120 | } 121 | -------------------------------------------------------------------------------- /priompt-preview/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |