├── .dockerignore ├── .gitattributes ├── .github └── workflows │ └── docker.yml ├── .gitignore ├── .npmrc ├── Dockerfile ├── LICENSE ├── README.md ├── data ├── .gitignore └── diffusers │ ├── .gitattributes │ ├── .gitignore │ ├── index │ ├── args.json │ ├── docstore.json │ └── hnswlib.index │ └── metadata.json ├── docker-compose.yml ├── example-quick-question.png ├── lerna.json ├── package.json ├── packages ├── quick-question-indexer │ ├── .gitignore │ ├── .mocharc.json │ ├── package.json │ ├── src │ │ ├── cli.ts │ │ ├── index.ts │ │ ├── parser.ts │ │ └── treesitter-parser.ts │ ├── tests │ │ ├── data │ │ │ ├── java.java │ │ │ ├── kotlin.kt │ │ │ ├── python.py │ │ │ ├── tsx.tsx │ │ │ └── typescript.ts │ │ └── parser.spec.ts │ ├── tsconfig.json │ └── typings │ │ ├── tree-sitter-java │ │ └── index.d.ts │ │ ├── tree-sitter-kotlin │ │ └── index.d.ts │ │ ├── tree-sitter-python │ │ └── index.d.ts │ │ └── tree-sitter-typescript │ │ └── index.d.ts └── quick-question │ ├── .env.sample │ ├── .eslintrc.json │ ├── .gitignore │ ├── components │ ├── Layout.tsx │ └── Text.tsx │ ├── next.config.js │ ├── package.json │ ├── pages │ ├── [...slug].tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ └── search.ts │ └── index.tsx │ ├── public │ └── favicon.ico │ ├── services │ └── RepositoryManager.ts │ ├── styles │ ├── Home.module.css │ ├── globals.css │ └── markdown.css │ └── tsconfig.json ├── scripts └── deploy.sh └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | packages/quick-question/.env.* 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | example-quick-question.png filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Create and publish docker image 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | paths-ignore: 7 | - 'README.md' 8 | pull_request: 9 | branches: [ "main" ] 10 | 11 | env: 12 | # Use docker.io for Docker Hub if empty 13 | REGISTRY: ghcr.io 14 | # github.repository as / 15 | IMAGE_NAME: ${{ github.repository }} 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | packages: write 24 | # This is used to complete the identity challenge 25 | # with sigstore/fulcio when running outside of PRs. 26 | id-token: write 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v3 31 | 32 | # Workaround: https://github.com/docker/build-push-action/issues/461 33 | - name: Setup Docker buildx 34 | uses: docker/setup-buildx-action@v2.0.0 35 | 36 | # Login against a Docker registry except on PR 37 | # https://github.com/docker/login-action 38 | - name: Log into registry ${{ env.REGISTRY }} 39 | if: github.event_name != 'pull_request' 40 | uses: docker/login-action@v2.0.0 41 | with: 42 | registry: ${{ env.REGISTRY }} 43 | username: ${{ github.actor }} 44 | password: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | # Extract metadata (tags, labels) for Docker 47 | # https://github.com/docker/metadata-action 48 | - name: Extract Docker metadata 49 | id: meta 50 | uses: docker/metadata-action@v4.0.1 51 | with: 52 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 53 | 54 | # Build and push Docker image with Buildx (don't push on PR) 55 | # https://github.com/docker/build-push-action 56 | - name: Build and push Docker image 57 | id: build-and-push 58 | uses: docker/build-push-action@v3.1.1 59 | with: 60 | file: Dockerfile 61 | context: . 62 | push: ${{ github.event_name != 'pull_request' }} 63 | tags: ${{ steps.meta.outputs.tags }} 64 | labels: ${{ steps.meta.outputs.labels }} 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | openai_api_key.txt 3 | fly.toml 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package.json ./ 6 | COPY yarn.lock ./ 7 | COPY lerna.json ./ 8 | 9 | 10 | COPY packages/quick-question/package.json ./packages/quick-question/ 11 | COPY packages/quick-question-indexer/package.json ./packages/quick-question-indexer/ 12 | 13 | RUN yarn 14 | 15 | COPY packages packages 16 | 17 | RUN yarn lerna run build 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2023] [TabbyML] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ⁉️ QuickQuestion 2 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 3 | ![Docker build status](https://img.shields.io/github/actions/workflow/status/TabbyML/quick-question/docker.yml?label=docker%20image%20build) 4 | 5 | ## 🤔 What is this? 6 | 7 | An incubating AI-powered Q&A for your codebase. 8 | 9 | [Live Demo](https://quick-question.fly.dev) 10 | 11 | ![Example Quick Question](example-quick-question.png) 12 | 13 | ## 🚀 Deployment 14 | Make sure [git-lfs](https://git-lfs.com/) is installed. 15 | 16 | 1. Clone the repository. 17 | ```bash 18 | git clone https://github.com/TabbyML/quick-question.git 19 | ``` 20 | 21 | 2. Save your OPENAI_API_KEY to file as secret. 22 | ```bash 23 | echo YOUR_OPENAI_API_KEY > openai_api_key.txt 24 | ``` 25 | 26 | 3. **Optional** Run Quick Question on your github project. 27 | 1. Create new directory for you project under `/data`, e.g `/data/quick-question`. 28 | 2. Add a new `metadata.json` file in your project directory. 29 | Here is a templete of file content, replace `{GITHUB_PROJECT}` with your own project name, e.g `TabbyML/quick-question`. 30 | ```json 31 | { 32 | "name": "{GITHUB_PROJECT}", 33 | "exampleQueries": ["How to ...?"] 34 | } 35 | ``` 36 | 37 | > See [./data/diffusers/metadata.json](./data/diffusers/metadata.json) for a complete example. 38 | 39 | 4. Start container. 40 | ``` 41 | docker-compose up 42 | ``` 43 | 44 | ## 🛠️ Development 45 | 1. Make sure [git-lfs](https://git-lfs.com/) is installed. 46 | 2. Clone the repository, runs `yarn` to install dependencies. 47 | 3. Run `yarn lerna run build`. 48 | 4. Switch workdir to `./packages/quick-question`. 49 | 3. Copy `.env.sample` to `.env.local`, and set your `OPENAI_API_KEY`. 50 | 4. Run `yarn dev` to start local development 51 | 52 | ## ❤️ Acknowledgement 53 | 54 | Many thanks to WizAI for contributing with [code-search](https://github.com/wizi-ai/code-search), a project that QuickQuestion branched from. 55 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !/diffusers 4 | -------------------------------------------------------------------------------- /data/diffusers/.gitattributes: -------------------------------------------------------------------------------- 1 | *.index filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /data/diffusers/.gitignore: -------------------------------------------------------------------------------- 1 | repository 2 | -------------------------------------------------------------------------------- /data/diffusers/index/args.json: -------------------------------------------------------------------------------- 1 | {"space":"ip","numDimensions":1536} -------------------------------------------------------------------------------- /data/diffusers/index/hnswlib.index: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9c48115dfb79987b732cfcb1e4cbe43925b936521ae099cddbf9abccbdbd8ff5 3 | size 16939564 4 | -------------------------------------------------------------------------------- /data/diffusers/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "huggingface/diffusers", 3 | "exampleQueries": [ 4 | "How to run stable diffusion pipeline?", 5 | "How to do img2img with stable diffusion pipeline?", 6 | "How to use dreambooth for finetuning?" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | secrets: 4 | openai_api_key: 5 | file: ./openai_api_key.txt 6 | 7 | services: 8 | app: 9 | build: 10 | context: . 11 | dockerfile: Dockerfile 12 | working_dir: /usr/src/app 13 | volumes: 14 | - ./data:/usr/src/app/data 15 | command: /bin/bash -c 'OPENAI_API_KEY=$$(cat /run/secrets/openai_api_key) yarn lerna run start --scope=quick-question' 16 | ports: 17 | - 3000:3000 18 | secrets: 19 | - openai_api_key 20 | environment: 21 | - REPO_DIR=/usr/src/app/data 22 | -------------------------------------------------------------------------------- /example-quick-question.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:90c609857a9b472cfdaba908710c48b0b850b47aa4dbe1fdda0bd172564275f8 3 | size 220155 4 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "useWorkspaces": true, 4 | "version": "0.0.0" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/quick-question", 6 | "packages/quick-question-indexer" 7 | ], 8 | "devDependencies": { 9 | "lerna": "^6.5.1", 10 | "syncpack": "^9.8.4" 11 | }, 12 | "engines": { 13 | "node": ">= 16.0.0 < 17.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "node-option": [ 3 | "experimental-specifier-resolution=node", 4 | "loader=ts-node/esm" 5 | ], 6 | "extensions": ["ts", "tsx"], 7 | "spec": ["tests/**/*.spec.*"], 8 | "watch-files": ["src", "tests"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quick-question-indexer", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "bin": { 6 | "quick-question-indexer": "dist/lib/cli.js" 7 | }, 8 | "main": "dist/lib/index.js", 9 | "typings": "dist/types/index.d.ts", 10 | "scripts": { 11 | "prebuild": "rimraf dist", 12 | "build": "tsc --module es2020", 13 | "test": "mocha", 14 | "test:watch": "mocha --watch" 15 | }, 16 | "dependencies": { 17 | "@dqbd/tiktoken": "^0.3.0", 18 | "commander": "^10.0.0", 19 | "hnswlib-node": "^1.4.2", 20 | "langchain": "0.0.33", 21 | "simple-git": "^3.17.0", 22 | "tree-sitter": "^0.20.1", 23 | "tree-sitter-java": "^0.19.1", 24 | "tree-sitter-kotlin": "^0.2.11", 25 | "tree-sitter-python": "^0.20.1", 26 | "tree-sitter-typescript": "^0.20.1", 27 | "typescript": "4.9.5" 28 | }, 29 | "devDependencies": { 30 | "@types/mocha": "^10.0.1", 31 | "@types/node": "18.14.0", 32 | "mocha": "^10.2.0", 33 | "rimraf": "^4.4.0", 34 | "ts-node": "^10.9.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/src/cli.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "commander"; 2 | import { buildIndex } from "./index"; 3 | 4 | const program = new Command(); 5 | 6 | program 7 | .requiredOption("-i, --input ", "Configuration file") 8 | .option("--no-dryrun") 9 | .action(buildIndex); 10 | 11 | program.parse(); 12 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/src/index.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | 4 | import { simpleGit } from "simple-git"; 5 | 6 | import { HNSWLib } from "langchain/vectorstores"; 7 | import { OpenAIEmbeddings } from "langchain/embeddings"; 8 | import { Document } from "langchain/document"; 9 | import { encoding_for_model } from "@dqbd/tiktoken"; 10 | 11 | import { parseFile } from "./parser"; 12 | 13 | async function* walk(dir: string): AsyncIterable { 14 | for await (const d of await fs.promises.opendir(dir)) { 15 | const entry = path.join(dir, d.name); 16 | if (d.isDirectory()) yield* walk(entry); 17 | else if (d.isFile()) yield entry; 18 | } 19 | } 20 | 21 | async function indexRepo(input: string, output: string, dryrun: boolean) { 22 | const MAX_DOC_LENGTH = 1600; 23 | const allDocuments = []; 24 | 25 | for await (const p of walk(input)) { 26 | const chunks = await parseFile(p); 27 | for (const { language, code, range } of chunks) { 28 | const document = new Document({ 29 | pageContent: code.slice(0, MAX_DOC_LENGTH), 30 | metadata: { 31 | language, 32 | source: path.relative(input, p), 33 | range, 34 | }, 35 | }); 36 | allDocuments.push(document); 37 | } 38 | } 39 | 40 | let totalTokens = 0; 41 | const enc = encoding_for_model("text-embedding-ada-002"); 42 | 43 | allDocuments.map((doc) => { 44 | totalTokens += enc.encode(doc.pageContent).length; 45 | }); 46 | const approximateOpenAICostForIndexing = ( 47 | (totalTokens / 1000) * 48 | 0.0004 49 | ).toPrecision(4); 50 | 51 | console.log("Total docs: ", allDocuments.length); 52 | console.log("Total tokens: ", totalTokens); 53 | console.log( 54 | "Approximate OpenAI cost for indexing: $", 55 | approximateOpenAICostForIndexing 56 | ); 57 | 58 | if (dryrun) { 59 | return; 60 | } 61 | 62 | console.log("\nBuilding index..."); 63 | 64 | const vectorStore = await HNSWLib.fromDocuments( 65 | allDocuments, 66 | new OpenAIEmbeddings() 67 | ); 68 | 69 | // Monkey patch console.log 70 | const log = console.log; 71 | console.log = () => {}; 72 | 73 | await vectorStore.save(output); 74 | } 75 | 76 | async function gitCloneRepository(name: string, repositoryDir: string) { 77 | // Clone github 78 | var isClone = false; 79 | if (!fs.existsSync(repositoryDir)) { 80 | isClone = true; 81 | fs.mkdirSync(repositoryDir); 82 | } 83 | const git = simpleGit(repositoryDir); 84 | if (isClone) { 85 | console.log("Git repository does not exists, cloning..."); 86 | const githubUrl = `https://github.com/${name}`; 87 | await git.clone(githubUrl, repositoryDir, { "--depth": 1 }); 88 | } else { 89 | console.log("Git repository exists, updating..."); 90 | await git.pull(); 91 | } 92 | return { 93 | revision: await git.revparse("HEAD"), 94 | }; 95 | } 96 | 97 | interface IndexParams { 98 | input: string; 99 | dryrun: boolean; 100 | } 101 | 102 | export interface IndexMetadata { 103 | revision: string; 104 | } 105 | 106 | export async function buildIndex({ input, dryrun }: IndexParams) { 107 | input = path.resolve(input); 108 | console.log("Processing metadata", input); 109 | const metadata = JSON.parse(fs.readFileSync(input, "utf-8")); 110 | 111 | // Setup directories. 112 | const baseDir = path.dirname(input); 113 | const repositoryDir = path.join(baseDir, "repository"); 114 | const indexDir = path.join(baseDir, "index"); 115 | 116 | const { revision } = await gitCloneRepository( 117 | metadata.name, 118 | repositoryDir 119 | ); 120 | 121 | // Build index. 122 | await indexRepo(repositoryDir, indexDir, dryrun); 123 | 124 | // Save index metadata 125 | const indexMetadata: IndexMetadata = { 126 | revision 127 | }; 128 | await fs.writeFileSync( 129 | path.join(indexDir, "metadata.json"), 130 | JSON.stringify(indexMetadata) 131 | ); 132 | } 133 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/src/parser.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { 4 | PythonParser, 5 | TsxParser, 6 | JavaParser, 7 | KotlinParser, 8 | } from "./treesitter-parser"; 9 | 10 | export interface Location { 11 | column: number; 12 | row: number; 13 | } 14 | 15 | export interface Chunk { 16 | language: string; 17 | code: string; 18 | range: { 19 | start: Location; 20 | end: Location; 21 | }; 22 | } 23 | 24 | export interface CodeParser { 25 | parse(code: string): Promise>; 26 | } 27 | 28 | interface LanguageInfo { 29 | languageName: string; 30 | parser: CodeParser; 31 | extensions: Array; 32 | } 33 | 34 | const LanguageInfos: Array = [ 35 | { 36 | languageName: "python", 37 | parser: new PythonParser(), 38 | extensions: [".py"], 39 | }, 40 | { 41 | languageName: "javascript", 42 | parser: new TsxParser({ languageName: "javascript" }), 43 | extensions: [".js", ".jsm", ".cjs", ".mjs"], 44 | }, 45 | { 46 | languageName: "typescript", 47 | parser: new TsxParser({ languageName: "typescript" }), 48 | extensions: [".ts"], 49 | }, 50 | { 51 | languageName: "jsx", 52 | parser: new TsxParser({ languageName: "jsx" }), 53 | extensions: [".jsx"], 54 | }, 55 | { 56 | languageName: "tsx", 57 | parser: new TsxParser({ languageName: "tsx" }), 58 | extensions: [".tsx"], 59 | }, 60 | { 61 | languageName: "java", 62 | parser: new JavaParser(), 63 | extensions: [".java"], 64 | }, 65 | { 66 | languageName: "kotlin", 67 | parser: new KotlinParser(), 68 | extensions: [".kt"], 69 | }, 70 | ]; 71 | 72 | export async function parseFile(file: string): Promise { 73 | const ext = path.extname(file); 74 | const langInfo = LanguageInfos.find((x) => x.extensions.includes(ext)); 75 | if (!langInfo) { 76 | return []; 77 | } 78 | 79 | const code = fs.readFileSync(file, "utf-8"); 80 | 81 | return await langInfo.parser.parse(code); 82 | } 83 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/src/treesitter-parser.ts: -------------------------------------------------------------------------------- 1 | import TreeSitter, { SyntaxNode } from "tree-sitter"; 2 | import TreeSitterPython from "tree-sitter-python"; 3 | import TreeSitterTypescript from "tree-sitter-typescript"; 4 | import TreeSitterJava from "tree-sitter-java"; 5 | import TreeSitterKotlin from "tree-sitter-kotlin"; 6 | import { Location, Chunk, CodeParser } from "./parser"; 7 | 8 | interface TreeSitterParserOptions { 9 | languageName?: string; 10 | patterns?: Array>; 11 | maxLevel?: number; 12 | minLoc?: number; 13 | } 14 | 15 | class TreeSitterParser implements CodeParser { 16 | language: any; 17 | languageName: string = ""; 18 | patterns = [ 19 | ["comment", "function_declaration"], 20 | ["function_declaration"], 21 | ["comment", "class_declaration"], 22 | ["class_declaration"], 23 | ]; 24 | maxLevel = 1; 25 | minLoc = 4; 26 | 27 | constructor(options?: TreeSitterParserOptions) { 28 | this.languageName = options?.languageName ?? this.languageName; 29 | this.patterns = options?.patterns ?? this.patterns; 30 | this.maxLevel = options?.maxLevel ?? this.maxLevel; 31 | this.minLoc = options?.minLoc ?? this.minLoc; 32 | } 33 | 34 | match(nodeTypes: string[]): string[] | null { 35 | for (const pattern of this.patterns) { 36 | if ( 37 | pattern.length <= nodeTypes.length && 38 | pattern.every((type, index) => type === nodeTypes[index]) 39 | ) { 40 | return pattern; 41 | } 42 | } 43 | return null; 44 | } 45 | 46 | async *traverse( 47 | nodes: SyntaxNode[], 48 | level: number 49 | ): AsyncIterable<{ 50 | start: { index: number; location: Location }; 51 | end: { index: number; location: Location }; 52 | }> { 53 | if (level > this.maxLevel) return; 54 | const nodeTypes = nodes.map((node) => node.type); 55 | let index = 0; 56 | while (index < nodeTypes.length) { 57 | const matched = this.match(nodeTypes.slice(index)); 58 | if (matched) { 59 | const startNode = nodes[index]; 60 | const endNode = nodes[index + matched.length - 1]; 61 | const result = { 62 | start: { 63 | index: startNode.startIndex, 64 | location: startNode.startPosition, 65 | }, 66 | end: { 67 | index: endNode.endIndex, 68 | location: endNode.endPosition, 69 | }, 70 | }; 71 | yield result; 72 | index += matched.length; 73 | } else { 74 | yield* this.traverse(nodes[index].children, level + 1); 75 | index += 1; 76 | } 77 | } 78 | } 79 | 80 | async parse(code: string): Promise> { 81 | const parser = new TreeSitter(); 82 | parser.setLanguage(this.language); 83 | const tree = parser.parse(code); 84 | const ranges = this.traverse([tree.rootNode], 0); 85 | const result: Array = []; 86 | for await (const range of ranges) { 87 | if (range.end.location.row - range.start.location.row + 1 < this.minLoc) { 88 | continue; 89 | } 90 | result.push({ 91 | language: this.languageName, 92 | code: code.slice(range.start.index, range.end.index), 93 | range: { start: range.start.location, end: range.end.location }, 94 | }); 95 | } 96 | return result; 97 | } 98 | } 99 | 100 | export class PythonParser extends TreeSitterParser { 101 | language = TreeSitterPython; 102 | languageName = "python"; 103 | patterns = [["function_definition"], ["class_definition"]]; 104 | maxLevel = 3; 105 | minLoc = 4; 106 | } 107 | 108 | export class TsxParser extends TreeSitterParser { 109 | language = TreeSitterTypescript.tsx; 110 | } 111 | 112 | export class JavaParser extends TreeSitterParser { 113 | language = TreeSitterJava; 114 | } 115 | 116 | export class KotlinParser extends TreeSitterParser { 117 | language = TreeSitterKotlin; 118 | } 119 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/tests/data/java.java: -------------------------------------------------------------------------------- 1 | package tests.data; 2 | 3 | /** 4 | * Simple class printing messages. 5 | */ 6 | @AnyAnnotation("this") 7 | public class Greeter { 8 | String hello = "Hello "; 9 | 10 | /** 11 | * Print greeting message. 12 | * @param message 13 | */ 14 | public void greet(String message) { 15 | System.out.println(hello + message); 16 | } 17 | } 18 | 19 | public class Ignore { 20 | } -------------------------------------------------------------------------------- /packages/quick-question-indexer/tests/data/kotlin.kt: -------------------------------------------------------------------------------- 1 | package tests.data; 2 | 3 | // Print words with space 4 | fun printWithSpace(words: List) { 5 | words.forEach { 6 | print(it) 7 | print(" ") 8 | } 9 | } 10 | 11 | /** 12 | * Simple class printing messages. 13 | */ 14 | class Greeter(message: String) { 15 | val words = listOf(hello, message) 16 | 17 | fun greet() { 18 | printWithSpace(words); 19 | } 20 | 21 | companion object { 22 | val hello = "Hello" 23 | } 24 | } 25 | 26 | /** 27 | * Class with annotation. 28 | */ 29 | @AnyAnnotation("this") 30 | class Foo() { 31 | } -------------------------------------------------------------------------------- /packages/quick-question-indexer/tests/data/python.py: -------------------------------------------------------------------------------- 1 | # Function for nth Fibonacci number 2 | def fibonacci(n): 3 | 4 | # Check if input is 0 then it will 5 | # print incorrect input 6 | if n < 0: 7 | print("Incorrect input") 8 | 9 | # Check if n is 0 10 | # then it will return 0 11 | elif n == 0: 12 | return 0 13 | 14 | # Check if n is 1,2 15 | # it will return 1 16 | elif n == 1 or n == 2: 17 | return 1 18 | 19 | else: 20 | return fibonacci(n-1) + fibonacci(n-2) 21 | 22 | class Foo(object): 23 | def __init__(self): 24 | pass 25 | 26 | def print(): 27 | print("hello world") 28 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/tests/data/tsx.tsx: -------------------------------------------------------------------------------- 1 | function FooComponent(props: { children: any }) { 2 | return
{props.children}
; 3 | } 4 | 5 | interface BarProps { 6 | name: string; 7 | children?: any; 8 | } 9 | 10 | function BarComponent(props: BarProps): any { 11 | return 12 |

13 | {props.children} 14 |

15 |
16 | } 17 | 18 | class ContainerComponent { 19 | props: {children: any }; 20 | 21 | constructor(props: { children: any }) { 22 | this.props = props; 23 | } 24 | render() { 25 | return ( 26 |
{this.props.children}
27 | ); 28 | } 29 | } 30 | 31 | export {}; 32 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/tests/data/typescript.ts: -------------------------------------------------------------------------------- 1 | function logWithDividing(object: any) { 2 | 3 | function log(object: any) { 4 | // Nested funciton should not be indexed. 5 | console.log(object); 6 | } 7 | 8 | const div: string = "------"; 9 | log(div); 10 | log(object); 11 | } 12 | 13 | function shortFunction() { 14 | // Too short to be indexed. 15 | } 16 | 17 | class Greeter { 18 | greeting: string; 19 | 20 | constructor(message: string) { 21 | this.greeting = message; 22 | } 23 | 24 | greet(): string { 25 | return "Hello, " + this.greeting; 26 | } 27 | } 28 | 29 | /** 30 | * This comment should be included. 31 | */ 32 | function functionWithComment() { 33 | let greeter = new Greeter("world"); 34 | logWithDividing(greeter.greet()); 35 | } 36 | // 37 | functionWithComment(); 38 | 39 | export {}; 40 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/tests/parser.spec.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import assert from "assert"; 3 | import { parseFile } from "../src/parser"; 4 | 5 | describe("parseFile: python", function () { 6 | it("should success", async function () { 7 | const chunks = await parseFile("./tests/data/python.py"); 8 | assert.equal(chunks.length, 2); 9 | assert.match(chunks[0].code, /def fib.*/); 10 | assert.match(chunks[1].code, /class Foo.*/); 11 | }); 12 | }); 13 | 14 | describe("parseFile: typescript", function () { 15 | it("should success", async function () { 16 | const chunks = await parseFile("./tests/data/typescript.ts"); 17 | assert.equal(chunks.length, 3); 18 | assert.match(chunks[0].code, /function logWithDividing.*/); 19 | assert.match(chunks[1].code, /class Greeter.*/); 20 | assert.ok( 21 | chunks[2].code.startsWith( 22 | "/**\n * This comment should be included.\n */\nfunction functionWithComment" 23 | ) 24 | ); 25 | assert.ok(chunks[2].code.endsWith(";\n}")); 26 | }); 27 | }); 28 | 29 | describe("parseFile: tsx", function () { 30 | it("should success", async function () { 31 | const chunks = await parseFile("./tests/data/tsx.tsx"); 32 | assert.equal(chunks.length, 2); 33 | assert.match(chunks[0].code, /function BarComponent.*/); 34 | assert.match(chunks[1].code, /class ContainerComponent.*/); 35 | }); 36 | }); 37 | 38 | describe("parseFile: java", function () { 39 | it("should success", async function () { 40 | const chunks = await parseFile("./tests/data/java.java"); 41 | assert.equal(chunks.length, 1); 42 | assert.ok( 43 | chunks[0].code.startsWith( 44 | '/**\n * Simple class printing messages.\n */\n@AnyAnnotation("this")\npublic class Greeter' 45 | ) 46 | ); 47 | assert.ok(chunks[0].code.endsWith("}")); 48 | }); 49 | }); 50 | 51 | describe("parseFile: kotlin", function () { 52 | it("should success", async function () { 53 | const chunks = await parseFile("./tests/data/kotlin.kt"); 54 | assert.equal(chunks.length, 3); 55 | assert.ok( 56 | chunks[0].code.startsWith("// Print words with space\nfun printWithSpace") 57 | ); 58 | assert.ok(chunks[0].code.endsWith("}")); 59 | assert.ok( 60 | chunks[1].code.startsWith( 61 | "/**\n * Simple class printing messages.\n */\nclass Greeter" 62 | ) 63 | ); 64 | assert.ok(chunks[1].code.endsWith("}")); 65 | assert.ok( 66 | chunks[2].code.startsWith( 67 | '/**\n * Class with annotation.\n */\n@AnyAnnotation("this")\nclass Foo' 68 | ) 69 | ); 70 | assert.ok(chunks[2].code.endsWith("}")); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "ts-node/node16/tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es2020", 5 | "declaration": true, 6 | "declarationDir": "dist/types", 7 | "outDir": "dist/lib", 8 | "typeRoots": ["../../node_modules/@types", "./typings"] 9 | }, 10 | "exclude": ["tests"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/quick-question-indexer/typings/tree-sitter-java/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "tree-sitter-java"; -------------------------------------------------------------------------------- /packages/quick-question-indexer/typings/tree-sitter-kotlin/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "tree-sitter-kotlin"; -------------------------------------------------------------------------------- /packages/quick-question-indexer/typings/tree-sitter-python/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "tree-sitter-python"; -------------------------------------------------------------------------------- /packages/quick-question-indexer/typings/tree-sitter-typescript/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "tree-sitter-typescript"; -------------------------------------------------------------------------------- /packages/quick-question/.env.sample: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | REPO_DIR=../../data 3 | -------------------------------------------------------------------------------- /packages/quick-question/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /packages/quick-question/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | # vercel 40 | .vercelignore 41 | -------------------------------------------------------------------------------- /packages/quick-question/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | import { useRouter } from "next/router"; 5 | 6 | import { styled } from "@mui/material/styles"; 7 | import MuiDrawer from "@mui/material/Drawer"; 8 | import Box from "@mui/material/Box"; 9 | import Container from "@mui/material/Container"; 10 | import Toolbar from "@mui/material/Toolbar"; 11 | import List from "@mui/material/List"; 12 | import Divider from "@mui/material/Divider"; 13 | import ListItemButton from "@mui/material/ListItemButton"; 14 | import ListItemIcon from "@mui/material/ListItemIcon"; 15 | import ListItemText from "@mui/material/ListItemText"; 16 | 17 | import { FaGithub, FaCodepen } from "react-icons/fa"; 18 | 19 | import Text from "components/Text"; 20 | 21 | const drawerWidth = 80; 22 | 23 | const Drawer = styled(MuiDrawer, { 24 | shouldForwardProp: (prop) => prop !== "open", 25 | })(({ theme, open }) => ({ 26 | "& .MuiDrawer-paper": { 27 | whiteSpace: "nowrap", 28 | width: drawerWidth, 29 | transition: theme.transitions.create("width", { 30 | easing: theme.transitions.easing.sharp, 31 | duration: theme.transitions.duration.enteringScreen, 32 | }), 33 | boxSizing: "border-box", 34 | ...(!open && { 35 | overflowX: "hidden", 36 | transition: theme.transitions.create("width", { 37 | easing: theme.transitions.easing.sharp, 38 | duration: theme.transitions.duration.leavingScreen, 39 | }), 40 | width: theme.spacing(7), 41 | [theme.breakpoints.up("sm")]: { 42 | width: theme.spacing(9), 43 | }, 44 | }), 45 | }, 46 | })); 47 | 48 | interface LayoutProps { 49 | children: React.ReactNode; 50 | displayNavList?: boolean; 51 | } 52 | 53 | export default function Layout(props: LayoutProps) { 54 | const router = useRouter(); 55 | return ( 56 | 64 | 65 | 71 | 75 | 85 | 86 | 87 | 88 | 89 | 93 | 94 | 101 | 102 | 103 | 104 | 105 | 106 | Projects 107 | 108 | 109 | 110 | 111 | 112 | 113 |
114 | 115 | {props.children} 116 | 117 |
118 |
119 |
120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /packages/quick-question/components/Text.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Typography from "@mui/material/Typography"; 3 | 4 | interface TextProps { 5 | variant?: 6 | | "h1" 7 | | "h2" 8 | | "h3" 9 | | "h4" 10 | | "h5" 11 | | "h6" 12 | | "subtitle1" 13 | | "subtitle2" 14 | | "body1" 15 | | "body2" 16 | | "caption" 17 | | "button" 18 | | "overline" 19 | | "inherit" 20 | | undefined; 21 | type: "header" | "text"; 22 | color?: string; 23 | children?: React.ReactNode; 24 | customStyles?: React.CSSProperties; 25 | } 26 | 27 | export default function Text(props: TextProps) { 28 | return ( 29 | 36 | {props.children} 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /packages/quick-question/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | webpack: (config, { isServer }) => { 5 | if (!isServer) { 6 | // don't resolve 'fs' module on the client to prevent this error on build --> Error: Can't resolve 'fs' 7 | config.resolve.fallback = { 8 | fs: false, 9 | }; 10 | } 11 | 12 | return config; 13 | }, 14 | }; 15 | 16 | export default nextConfig; 17 | -------------------------------------------------------------------------------- /packages/quick-question/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quick-question", 3 | "type": "module", 4 | "version": "0.1.0", 5 | "private": true, 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@dqbd/tiktoken": "^0.3.0", 14 | "@emotion/react": "^11.10.6", 15 | "@emotion/styled": "^11.10.6", 16 | "@mui/icons-material": "^5.11.9", 17 | "@mui/lab": "^5.0.0-alpha.120", 18 | "@mui/material": "^5.11.10", 19 | "@next/font": "13.1.6", 20 | "@types/node": "18.14.0", 21 | "@types/react": "18.0.28", 22 | "@types/react-dom": "18.0.11", 23 | "@types/react-syntax-highlighter": "^15.5.6", 24 | "dotenv-cli": "^7.1.0", 25 | "eslint": "8.34.0", 26 | "eslint-config-next": "13.1.6", 27 | "glob": "^9.3.0", 28 | "hnswlib-node": "^1.4.2", 29 | "langchain": "0.0.33", 30 | "next": "13.1.6", 31 | "openai": "^3.1.0", 32 | "quick-question-indexer": "*", 33 | "react": "18.2.0", 34 | "react-dom": "18.2.0", 35 | "react-icons": "^4.7.1", 36 | "react-markdown": "^8.0.5", 37 | "react-syntax-highlighter": "^15.5.0", 38 | "typescript": "4.9.5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/quick-question/pages/[...slug].tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, ReactNode } from "react"; 2 | import { useRouter } from "next/router"; 3 | 4 | import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; 5 | import { atomDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; 6 | 7 | import Alert from "@mui/material/Alert"; 8 | import Chip from "@mui/material/Chip"; 9 | import TextField from "@mui/material/TextField"; 10 | import Grid from "@mui/material/Grid"; 11 | import LoadingButton from "@mui/lab/LoadingButton"; 12 | import LaunchIcon from "@mui/icons-material/Launch"; 13 | 14 | import Text from "components/Text"; 15 | import Layout from "components/Layout"; 16 | 17 | import ReactMarkdown from "react-markdown"; 18 | 19 | import { ProjectInfo, getRepositoryManager } from "services/RepositoryManager"; 20 | 21 | interface CodeSnippetMeta { 22 | source: string; 23 | language: string; 24 | score: number; 25 | summary: string; 26 | lineNumber: number; 27 | } 28 | 29 | interface CodeSnippet { 30 | pageContent: string; 31 | metadata: CodeSnippetMeta; 32 | } 33 | 34 | interface HomeProps { 35 | project: ProjectInfo; 36 | } 37 | 38 | const HomeContainer = (props: { children: ReactNode }) => ( 39 |
40 | {props.children} 41 |
42 | ); 43 | 44 | export default function Home({ project }: HomeProps) { 45 | const { metadata, indexingStatus, indexMetadata } = project; 46 | 47 | const [searchQuery, setSearchQuery] = useState( 48 | metadata.exampleQueries[0] 49 | ); 50 | 51 | // One of: initial, loading, loaded 52 | const [status, setStatus] = useState('initial'); 53 | const [matches, setMatches] = useState([]); 54 | 55 | const getSearchResults = async () => { 56 | setStatus('loading'); 57 | 58 | const response = await fetch("/api/search", { 59 | method: "POST", 60 | headers: { 61 | "Content-Type": "application/json", 62 | }, 63 | body: JSON.stringify({ 64 | query: searchQuery, 65 | project: project.metadata.name, 66 | }), 67 | }); 68 | const data = await response.json(); 69 | setMatches(data); 70 | setStatus('loaded'); 71 | }; 72 | 73 | const InfoContainer = (props: { children: ReactNode }) => ( 74 | 75 |
76 |

77 | Selected repo:{" "} 78 | {metadata.name} 79 |

80 | {props.children} 81 |
82 |
83 | ); 84 | 85 | const router = useRouter(); 86 | useEffect(() => { 87 | if (indexingStatus !== "failed" && indexingStatus !== "success") { 88 | const id = setTimeout(() => { 89 | router.reload(); 90 | }, 20000); 91 | return () => { 92 | clearTimeout(id); 93 | }; 94 | } 95 | }); 96 | 97 | if (indexingStatus === "failed") { 98 | return ( 99 | 100 | 101 | Indexing job failed, please check server logging for information 102 | 103 | 104 | ); 105 | } 106 | 107 | if (indexingStatus !== "success") { 108 | return ( 109 | 110 | 111 | Repository needs to be indexed before it is able to search it. 112 | Indexing usually takes less than 5 minutes. Feel free to grab some 113 | coffee! 114 | 115 | 116 | Indexing 117 | 118 | 119 | ); 120 | } 121 | 122 | const revision = indexMetadata?.revision || "main"; 123 | 124 | return ( 125 | 126 | 135 | 136 | 137 | 138 | Ask a question about {project.metadata.name} ... 139 | 140 | 141 | 142 | setSearchQuery(event.currentTarget.value)} 150 | onKeyDown={(event) => { 151 | if (event.keyCode === 13) getSearchResults(); 152 | }} 153 | /> 154 | 155 | 156 | 157 | {metadata.exampleQueries.map((x, i) => ( 158 | 159 | setSearchQuery(x)} /> 160 | 161 | ))} 162 | 163 | 164 | 165 | 179 | 184 | Search 185 | 186 | 187 | 188 | {status === 'loaded' && 189 | 190 | 191 | Top matches 192 | 193 | 194 | {matches.length == 0 && ( 195 | 196 |
(No matches)
197 |
198 | )} 199 | {matches.map((match, it) => ( 200 | 201 | 202 | 203 | 211 | 222 | 223 | 224 | { }} 235 | deleteIcon={} 236 | /> 237 | 238 | 239 | 240 | 241 | 242 | {match.metadata.summary} 243 | 244 | 245 | 246 | 254 | {match.pageContent} 255 | 256 | 257 | 258 | 259 | ))} 260 |
} 261 |
262 | ); 263 | } 264 | 265 | export async function getServerSideProps(context: any) { 266 | const slug = context.params.slug as string[]; 267 | const server = (context.res.socket as any).server; 268 | const repository = getRepositoryManager(server); 269 | return { 270 | props: { 271 | project: repository.fetchProjectInfo(slug.join("/")), 272 | }, 273 | }; 274 | } 275 | -------------------------------------------------------------------------------- /packages/quick-question/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "@/styles/globals.css"; 2 | import type { AppProps } from "next/app"; 3 | import Head from "next/head"; 4 | 5 | import { createTheme, ThemeProvider } from "@mui/material/styles"; 6 | import CssBaseline from "@mui/material/CssBaseline"; 7 | 8 | export default function App({ Component, pageProps }: AppProps) { 9 | const mdTheme = createTheme({ 10 | palette: { 11 | mode: "dark", 12 | text: { 13 | primary: "#fff", 14 | secondary: "#888", 15 | disabled: "#888", 16 | }, 17 | primary: { 18 | main: "#FDFDBD", 19 | }, 20 | secondary: { 21 | main: "#fff", 22 | }, 23 | background: { 24 | default: "#000", // background: #000 25 | paper: "#000", // use #111 for secondary 26 | }, 27 | divider: "#333", // borders: #333 28 | info: { 29 | main: "#006ff3", 30 | }, 31 | error: { 32 | main: "#ff0000", 33 | }, 34 | warning: { 35 | main: "#ffff00", 36 | }, 37 | }, 38 | typography: { 39 | fontWeightLight: 200, 40 | fontWeightRegular: 400, 41 | fontWeightMedium: 400, 42 | fontWeightBold: 600, 43 | h1: { 44 | fontWeight: 600, 45 | fontSize: "2.4rem", 46 | }, 47 | h2: { 48 | fontWeight: 600, 49 | fontSize: "2.4rem", 50 | }, 51 | h3: { 52 | fontWeight: 600, 53 | fontSize: "1.5rem", 54 | }, 55 | h4: { 56 | fontWeight: 600, 57 | fontSize: "1.5rem", 58 | }, 59 | h5: { 60 | fontWeight: 600, 61 | fontSize: "1.2rem", 62 | }, 63 | h6: { 64 | fontWeight: 600, 65 | fontSize: "1.2rem", 66 | }, 67 | subtitle1: { 68 | fontWeight: 600, 69 | fontSize: "1rem", 70 | }, 71 | subtitle2: { 72 | fontWeight: 600, 73 | fontSize: "0.875rem", 74 | }, 75 | body1: { 76 | fontWeight: 400, 77 | fontSize: "1rem", 78 | }, 79 | body2: { 80 | fontWeight: 400, 81 | fontSize: "0.875rem", 82 | }, 83 | button: { 84 | textTransform: "none", 85 | fontWeight: 600, 86 | fontSize: "0.9rem", 87 | }, 88 | fontFamily: [ 89 | "Inter", 90 | "Roboto", 91 | "-apple-system", 92 | "BlinkMacSystemFont", 93 | "Segoe UI", 94 | ].join(","), 95 | }, 96 | }); 97 | return ( 98 | <> 99 | 100 | QuickQuestion by TabbyML 101 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | ); 113 | } 114 | -------------------------------------------------------------------------------- /packages/quick-question/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 | 12 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/quick-question/pages/api/search.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from "next"; 2 | 3 | import path from "path"; 4 | 5 | import { HNSWLib } from "langchain/vectorstores"; 6 | import { OpenAIEmbeddings } from "langchain/embeddings"; 7 | import { OpenAIChat } from "langchain/llms"; 8 | import { PromptTemplate } from "langchain/prompts"; 9 | 10 | import { getRepositoryManager } from "services/RepositoryManager"; 11 | 12 | const NUM_RESULTS = 3; 13 | 14 | export default async function handler( 15 | req: NextApiRequest, 16 | res: NextApiResponse 17 | ) { 18 | const { query, project } = req.body; 19 | const repository = getRepositoryManager((res.socket! as any).server); 20 | switch (req.method) { 21 | case "POST": { 22 | const log = console.log; 23 | console.log = () => {}; 24 | const vectorStore = await HNSWLib.load( 25 | path.join(repository.getProject(project).projectDir, "index"), 26 | new OpenAIEmbeddings() 27 | ); 28 | 29 | const llm = new OpenAIChat({ temperature: 0.2, cache: true }); 30 | const queryResult = await vectorStore.similaritySearchWithScore( 31 | query, 32 | NUM_RESULTS 33 | ); 34 | 35 | const formattedResults = queryResult.map(async (result: any[]) => { 36 | const code = result[0].pageContent; 37 | const language = result[0].metadata.language; 38 | const prompt = await CodeTemplate.format({ language, query, code }); 39 | return { 40 | pageContent: code, 41 | metadata: { 42 | language: language, 43 | source: result[0].metadata.source, 44 | score: 1.0 - result[1], 45 | summary: await llm.call(prompt), 46 | lineNumber: result[0].metadata.range.start.row + 1, 47 | }, 48 | }; 49 | }); 50 | 51 | const results = (await Promise.all(formattedResults)).filter( 52 | ({ metadata }) => !metadata.summary.includes("NOT HELPFUL") 53 | ); 54 | return res.status(200).json(results); 55 | } 56 | default: { 57 | res.setHeader("Allow", ["POST"]); 58 | return res.status(405).end(`Method ${req.method} Not Allowed`); 59 | } 60 | } 61 | } 62 | 63 | const CodeTemplate = new PromptTemplate({ 64 | template: `Given the following {language} code and a question, create a concise answer in markdown. 65 | If the snippet is not really helpful, just say "NOT HELPFUL". 66 | ========= 67 | {code} 68 | ========= 69 | 70 | QUESTION: {query} 71 | FINAL ANSWER:`, 72 | inputVariables: ["language", "query", "code"], 73 | }); 74 | -------------------------------------------------------------------------------- /packages/quick-question/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, ReactNode } from "react"; 2 | 3 | import { useRouter } from "next/router"; 4 | import Link from "next/link"; 5 | 6 | import Chip from "@mui/material/Chip"; 7 | import Grid from "@mui/material/Grid"; 8 | 9 | import Text from "components/Text"; 10 | import Layout from "components/Layout"; 11 | 12 | import { ProjectInfo, getRepositoryManager } from "services/RepositoryManager"; 13 | 14 | interface HomeProps { 15 | projects: ProjectInfo[]; 16 | } 17 | export default function Home({ projects }: HomeProps) { 18 | return ( 19 |
20 | 21 | {projects.map((x, i) => ( 22 | 23 | ))} 24 | 25 |
26 | ); 27 | } 28 | 29 | interface RowProps { 30 | project: ProjectInfo; 31 | } 32 | 33 | function Row({ project }: RowProps) { 34 | const label = project.indexingStatus === "success" ? "indexed" : "pending"; 35 | const color = label === "indexed" ? "primary" : "secondary"; 36 | 37 | return ( 38 | 53 | 54 | 55 | 56 | 57 | {project.metadata.name} 58 | 59 | 60 | 61 | 62 | 63 | 70 | 71 | 72 | ); 73 | } 74 | 75 | export async function getServerSideProps(context: any) { 76 | const server = (context.res.socket as any).server; 77 | const repository = getRepositoryManager(server); 78 | return { 79 | props: { 80 | projects: repository.collectProjectInfos(), 81 | }, 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /packages/quick-question/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TabbyML/quick-question/bbdf94957910f7901729b595c4ad88e0940e0d23/packages/quick-question/public/favicon.ico -------------------------------------------------------------------------------- /packages/quick-question/services/RepositoryManager.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | 4 | import { globSync } from "glob"; 5 | import { buildIndex, IndexMetadata } from "quick-question-indexer"; 6 | 7 | export interface Metadata { 8 | name: string; 9 | exampleQueries: string[]; 10 | } 11 | 12 | export type IndexingStatus = "init" | "success" | "pending" | "failed"; 13 | 14 | export interface ProjectInfo { 15 | metadata: Metadata; 16 | indexingStatus: IndexingStatus; 17 | indexMetadata: IndexMetadata | null; 18 | } 19 | 20 | class Project { 21 | readonly projectDir: string; 22 | readonly metadata: Metadata; 23 | indexingStatus: IndexingStatus = "init"; 24 | indexingJob?: Promise; 25 | indexMetadata?: IndexMetadata; 26 | 27 | constructor(metadataFile: string) { 28 | this.projectDir = path.dirname(metadataFile); 29 | this.metadata = JSON.parse(fs.readFileSync(metadataFile, "utf-8")); 30 | } 31 | 32 | fetchIndexingStatus(dryrun: boolean): IndexingStatus { 33 | if (fs.existsSync(path.join(this.projectDir, "index/docstore.json"))) { 34 | return "success"; 35 | } 36 | 37 | if (!this.indexingJob && !dryrun) { 38 | this.indexingStatus = "pending"; 39 | this.indexingJob = createIndexingJob(this.projectDir).then((status) => { 40 | this.indexingStatus = status; 41 | }); 42 | } 43 | 44 | return this.indexingStatus; 45 | } 46 | 47 | fetchIndexMetadata(): IndexMetadata | null { 48 | if (this.indexingStatus !== "success") { 49 | return null; 50 | } 51 | const indexMetadataFile = path.join(this.projectDir, "index/metadata.json"); 52 | if (fs.existsSync(indexMetadataFile)) { 53 | this.indexMetadata = JSON.parse( 54 | fs.readFileSync(indexMetadataFile, "utf-8") 55 | ) as IndexMetadata; 56 | } 57 | return this.indexMetadata || null; 58 | } 59 | } 60 | 61 | async function createIndexingJob(projectDir: string): Promise { 62 | try { 63 | await buildIndex({ 64 | input: path.join(projectDir, "metadata.json"), 65 | dryrun: false, 66 | }); 67 | return "success"; 68 | } catch (err) { 69 | console.error("Failed indexing", err); 70 | return "failed"; 71 | } 72 | } 73 | 74 | class RepositoryManager { 75 | private readonly projects: Project[]; 76 | 77 | constructor() { 78 | this.projects = globSync( 79 | path.join(process.env.REPO_DIR!, "*", "metadata.json") 80 | ).map((x) => new Project(x)); 81 | } 82 | 83 | collectProjectInfos(): ProjectInfo[] { 84 | return this.projects.map((x) => ({ 85 | metadata: x.metadata, 86 | indexingStatus: x.fetchIndexingStatus(true), 87 | indexMetadata: x.fetchIndexMetadata(), 88 | })); 89 | } 90 | 91 | fetchProjectInfo(name: string): ProjectInfo { 92 | for (const x of this.projects) { 93 | if (x.metadata.name === name) 94 | return { 95 | metadata: x.metadata, 96 | indexingStatus: x.fetchIndexingStatus(false), 97 | indexMetadata: x.fetchIndexMetadata(), 98 | }; 99 | } 100 | 101 | throw new Error("Invalid project name " + name); 102 | } 103 | 104 | getProject(name: string): Project { 105 | for (const x of this.projects) { 106 | if (x.metadata.name === name) return x; 107 | } 108 | 109 | throw new Error("Invalid project name " + name); 110 | } 111 | } 112 | 113 | export function getRepositoryManager(ctx: any): RepositoryManager { 114 | if (!ctx.repositoryManager) { 115 | ctx.repositoryManager = new RepositoryManager(); 116 | } 117 | return ctx.repositoryManager; 118 | } 119 | -------------------------------------------------------------------------------- /packages/quick-question/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | align-items: center; 6 | padding: 6rem; 7 | min-height: 100vh; 8 | } 9 | 10 | .description { 11 | display: inherit; 12 | justify-content: inherit; 13 | align-items: inherit; 14 | font-size: 0.85rem; 15 | max-width: var(--max-width); 16 | width: 100%; 17 | z-index: 2; 18 | font-family: var(--font-mono); 19 | } 20 | 21 | .description a { 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | gap: 0.5rem; 26 | } 27 | 28 | .description p { 29 | position: relative; 30 | margin: 0; 31 | padding: 1rem; 32 | background-color: rgba(var(--callout-rgb), 0.5); 33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3); 34 | border-radius: var(--border-radius); 35 | } 36 | 37 | .code { 38 | font-weight: 700; 39 | font-family: var(--font-mono); 40 | } 41 | 42 | .grid { 43 | display: grid; 44 | grid-template-columns: repeat(4, minmax(25%, auto)); 45 | width: var(--max-width); 46 | max-width: 100%; 47 | } 48 | 49 | .card { 50 | padding: 1rem 1.2rem; 51 | border-radius: var(--border-radius); 52 | background: rgba(var(--card-rgb), 0); 53 | border: 1px solid rgba(var(--card-border-rgb), 0); 54 | transition: background 200ms, border 200ms; 55 | } 56 | 57 | .card span { 58 | display: inline-block; 59 | transition: transform 200ms; 60 | } 61 | 62 | .card h2 { 63 | font-weight: 600; 64 | margin-bottom: 0.7rem; 65 | } 66 | 67 | .card p { 68 | margin: 0; 69 | opacity: 0.6; 70 | font-size: 0.9rem; 71 | line-height: 1.5; 72 | max-width: 30ch; 73 | } 74 | 75 | .center { 76 | display: flex; 77 | justify-content: center; 78 | align-items: center; 79 | position: relative; 80 | padding: 4rem 0; 81 | } 82 | 83 | .center::before { 84 | background: var(--secondary-glow); 85 | border-radius: 50%; 86 | width: 480px; 87 | height: 360px; 88 | margin-left: -400px; 89 | } 90 | 91 | .center::after { 92 | background: var(--primary-glow); 93 | width: 240px; 94 | height: 180px; 95 | z-index: -1; 96 | } 97 | 98 | .center::before, 99 | .center::after { 100 | content: ''; 101 | left: 50%; 102 | position: absolute; 103 | filter: blur(45px); 104 | transform: translateZ(0); 105 | } 106 | 107 | .logo, 108 | .thirteen { 109 | position: relative; 110 | } 111 | 112 | .thirteen { 113 | display: flex; 114 | justify-content: center; 115 | align-items: center; 116 | width: 75px; 117 | height: 75px; 118 | padding: 25px 10px; 119 | margin-left: 16px; 120 | transform: translateZ(0); 121 | border-radius: var(--border-radius); 122 | overflow: hidden; 123 | box-shadow: 0px 2px 8px -1px #0000001a; 124 | } 125 | 126 | .thirteen::before, 127 | .thirteen::after { 128 | content: ''; 129 | position: absolute; 130 | z-index: -1; 131 | } 132 | 133 | /* Conic Gradient Animation */ 134 | .thirteen::before { 135 | animation: 6s rotate linear infinite; 136 | width: 200%; 137 | height: 200%; 138 | background: var(--tile-border); 139 | } 140 | 141 | /* Inner Square */ 142 | .thirteen::after { 143 | inset: 0; 144 | padding: 1px; 145 | border-radius: var(--border-radius); 146 | background: linear-gradient( 147 | to bottom right, 148 | rgba(var(--tile-start-rgb), 1), 149 | rgba(var(--tile-end-rgb), 1) 150 | ); 151 | background-clip: content-box; 152 | } 153 | 154 | /* Enable hover only on non-touch devices */ 155 | @media (hover: hover) and (pointer: fine) { 156 | .card:hover { 157 | background: rgba(var(--card-rgb), 0.1); 158 | border: 1px solid rgba(var(--card-border-rgb), 0.15); 159 | } 160 | 161 | .card:hover span { 162 | transform: translateX(4px); 163 | } 164 | } 165 | 166 | @media (prefers-reduced-motion) { 167 | .thirteen::before { 168 | animation: none; 169 | } 170 | 171 | .card:hover span { 172 | transform: none; 173 | } 174 | } 175 | 176 | /* Mobile */ 177 | @media (max-width: 700px) { 178 | .content { 179 | padding: 4rem; 180 | } 181 | 182 | .grid { 183 | grid-template-columns: 1fr; 184 | margin-bottom: 120px; 185 | max-width: 320px; 186 | text-align: center; 187 | } 188 | 189 | .card { 190 | padding: 1rem 2.5rem; 191 | } 192 | 193 | .card h2 { 194 | margin-bottom: 0.5rem; 195 | } 196 | 197 | .center { 198 | padding: 8rem 0 6rem; 199 | } 200 | 201 | .center::before { 202 | transform: none; 203 | height: 300px; 204 | } 205 | 206 | .description { 207 | font-size: 0.8rem; 208 | } 209 | 210 | .description a { 211 | padding: 1rem; 212 | } 213 | 214 | .description p, 215 | .description div { 216 | display: flex; 217 | justify-content: center; 218 | position: fixed; 219 | width: 100%; 220 | } 221 | 222 | .description p { 223 | align-items: center; 224 | inset: 0 0 auto; 225 | padding: 2rem 1rem 1.4rem; 226 | border-radius: 0; 227 | border: none; 228 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); 229 | background: linear-gradient( 230 | to bottom, 231 | rgba(var(--background-start-rgb), 1), 232 | rgba(var(--callout-rgb), 0.5) 233 | ); 234 | background-clip: padding-box; 235 | backdrop-filter: blur(24px); 236 | } 237 | 238 | .description div { 239 | align-items: flex-end; 240 | pointer-events: none; 241 | inset: auto 0 0; 242 | padding: 2rem; 243 | height: 200px; 244 | background: linear-gradient( 245 | to bottom, 246 | transparent 0%, 247 | rgb(var(--background-end-rgb)) 40% 248 | ); 249 | z-index: 1; 250 | } 251 | } 252 | 253 | /* Tablet and Smaller Desktop */ 254 | @media (min-width: 701px) and (max-width: 1120px) { 255 | .grid { 256 | grid-template-columns: repeat(2, 50%); 257 | } 258 | } 259 | 260 | @media (prefers-color-scheme: dark) { 261 | .vercelLogo { 262 | filter: invert(1); 263 | } 264 | 265 | .logo, 266 | .thirteen img { 267 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); 268 | } 269 | } 270 | 271 | @keyframes rotate { 272 | from { 273 | transform: rotate(360deg); 274 | } 275 | to { 276 | transform: rotate(0deg); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /packages/quick-question/styles/globals.css: -------------------------------------------------------------------------------- 1 | @import "markdown.css"; 2 | 3 | :root { 4 | --max-width: 1100px; 5 | --border-radius: 12px; 6 | --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 7 | 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', 8 | 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; 9 | 10 | --foreground-rgb: 255, 255, 255; 11 | --background-start-rgb: 0, 0, 0; 12 | --background-end-rgb: 0, 0, 0; 13 | 14 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); 15 | --secondary-glow: linear-gradient( 16 | to bottom right, 17 | rgba(1, 65, 255, 0), 18 | rgba(1, 65, 255, 0), 19 | rgba(1, 65, 255, 0.3) 20 | ); 21 | 22 | --tile-start-rgb: 2, 13, 46; 23 | --tile-end-rgb: 2, 5, 19; 24 | --tile-border: conic-gradient( 25 | #ffffff80, 26 | #ffffff40, 27 | #ffffff30, 28 | #ffffff20, 29 | #ffffff10, 30 | #ffffff10, 31 | #ffffff80 32 | ); 33 | 34 | --callout-rgb: 20, 20, 20; 35 | --callout-border-rgb: 108, 108, 108; 36 | --card-rgb: 100, 100, 100; 37 | --card-border-rgb: 200, 200, 200; 38 | } 39 | 40 | * { 41 | box-sizing: border-box; 42 | padding: 0; 43 | margin: 0; 44 | } 45 | 46 | html, 47 | body { 48 | max-width: 100vw; 49 | overflow-x: hidden; 50 | } 51 | 52 | body { 53 | color: rgb(var(--foreground-rgb)); 54 | background: linear-gradient( 55 | to bottom, 56 | transparent, 57 | rgb(var(--background-end-rgb)) 58 | ) 59 | rgb(var(--background-start-rgb)); 60 | } 61 | 62 | a { 63 | color: inherit; 64 | text-decoration: none; 65 | } 66 | 67 | .summary { 68 | padding: 8px; 69 | border-radius: 4px; 70 | line-height: 1.6; 71 | background: #244; 72 | margin-bottom: 1em; 73 | } 74 | 75 | @media (max-width: 1280px) { 76 | .menu { 77 | display: none; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/quick-question/styles/markdown.css: -------------------------------------------------------------------------------- 1 | .markdown-body { 2 | color-scheme: dark; 3 | --color-prettylights-syntax-comment: #bdc4cc; 4 | --color-prettylights-syntax-constant: #91cbff; 5 | --color-prettylights-syntax-entity: #dbb7ff; 6 | --color-prettylights-syntax-storage-modifier-import: #f0f3f6; 7 | --color-prettylights-syntax-entity-tag: #72f088; 8 | --color-prettylights-syntax-keyword: #ff9492; 9 | --color-prettylights-syntax-string: #addcff; 10 | --color-prettylights-syntax-variable: #ffb757; 11 | --color-prettylights-syntax-brackethighlighter-unmatched: #ff6a69; 12 | --color-prettylights-syntax-invalid-illegal-text: #ffffff; 13 | --color-prettylights-syntax-invalid-illegal-bg: #e82a2f; 14 | --color-prettylights-syntax-carriage-return-text: #ffffff; 15 | --color-prettylights-syntax-carriage-return-bg: #ff4445; 16 | --color-prettylights-syntax-string-regexp: #72f088; 17 | --color-prettylights-syntax-markup-list: #fbd669; 18 | --color-prettylights-syntax-markup-heading: #409eff; 19 | --color-prettylights-syntax-markup-italic: #f0f3f6; 20 | --color-prettylights-syntax-markup-bold: #f0f3f6; 21 | --color-prettylights-syntax-markup-deleted-text: #ffdedb; 22 | --color-prettylights-syntax-markup-deleted-bg: #cc1421; 23 | --color-prettylights-syntax-markup-inserted-text: #acf7b6; 24 | --color-prettylights-syntax-markup-inserted-bg: #007728; 25 | --color-prettylights-syntax-markup-changed-text: #ffe1b4; 26 | --color-prettylights-syntax-markup-changed-bg: #a74c00; 27 | --color-prettylights-syntax-markup-ignored-text: #f0f3f6; 28 | --color-prettylights-syntax-markup-ignored-bg: #318bf8; 29 | --color-prettylights-syntax-meta-diff-range: #dbb7ff; 30 | --color-prettylights-syntax-brackethighlighter-angle: #bdc4cc; 31 | --color-prettylights-syntax-sublimelinter-gutter-mark: #7a828e; 32 | --color-prettylights-syntax-constant-other-reference-link: #addcff; 33 | --color-fg-default: #f0f3f6; 34 | --color-fg-muted: #f0f3f6; 35 | --color-fg-subtle: #9ea7b3; 36 | --color-canvas-default: #0a0c10; 37 | --color-canvas-subtle: #272b33; 38 | --color-border-default: #7a828e; 39 | --color-border-muted: #7a828e; 40 | --color-neutral-muted: rgba(158,167,179,0.4); 41 | --color-accent-fg: #71b7ff; 42 | --color-accent-emphasis: #409eff; 43 | --color-attention-subtle: rgba(224,155,19,0.15); 44 | --color-danger-fg: #ff6a69; 45 | } 46 | 47 | .markdown-body { 48 | -ms-text-size-adjust: 100%; 49 | -webkit-text-size-adjust: 100%; 50 | margin: 0; 51 | color: var(--color-fg-default); 52 | background-color: var(--color-canvas-default); 53 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; 54 | font-size: 16px; 55 | line-height: 1.5; 56 | word-wrap: break-word; 57 | } 58 | 59 | .markdown-body .octicon { 60 | display: inline-block; 61 | fill: currentColor; 62 | vertical-align: text-bottom; 63 | } 64 | 65 | .markdown-body h1:hover .anchor .octicon-link:before, 66 | .markdown-body h2:hover .anchor .octicon-link:before, 67 | .markdown-body h3:hover .anchor .octicon-link:before, 68 | .markdown-body h4:hover .anchor .octicon-link:before, 69 | .markdown-body h5:hover .anchor .octicon-link:before, 70 | .markdown-body h6:hover .anchor .octicon-link:before { 71 | width: 16px; 72 | height: 16px; 73 | content: ' '; 74 | display: inline-block; 75 | background-color: currentColor; 76 | -webkit-mask-image: url("data:image/svg+xml,"); 77 | mask-image: url("data:image/svg+xml,"); 78 | } 79 | 80 | .markdown-body details, 81 | .markdown-body figcaption, 82 | .markdown-body figure { 83 | display: block; 84 | } 85 | 86 | .markdown-body summary { 87 | display: list-item; 88 | } 89 | 90 | .markdown-body [hidden] { 91 | display: none !important; 92 | } 93 | 94 | .markdown-body a { 95 | background-color: transparent; 96 | color: var(--color-accent-fg); 97 | text-decoration: none; 98 | } 99 | 100 | .markdown-body abbr[title] { 101 | border-bottom: none; 102 | text-decoration: underline dotted; 103 | } 104 | 105 | .markdown-body b, 106 | .markdown-body strong { 107 | font-weight: var(--base-text-weight-semibold, 600); 108 | } 109 | 110 | .markdown-body dfn { 111 | font-style: italic; 112 | } 113 | 114 | .markdown-body h1 { 115 | margin: .67em 0; 116 | font-weight: var(--base-text-weight-semibold, 600); 117 | padding-bottom: .3em; 118 | font-size: 2em; 119 | border-bottom: 1px solid var(--color-border-muted); 120 | } 121 | 122 | .markdown-body mark { 123 | background-color: var(--color-attention-subtle); 124 | color: var(--color-fg-default); 125 | } 126 | 127 | .markdown-body small { 128 | font-size: 90%; 129 | } 130 | 131 | .markdown-body sub, 132 | .markdown-body sup { 133 | font-size: 75%; 134 | line-height: 0; 135 | position: relative; 136 | vertical-align: baseline; 137 | } 138 | 139 | .markdown-body sub { 140 | bottom: -0.25em; 141 | } 142 | 143 | .markdown-body sup { 144 | top: -0.5em; 145 | } 146 | 147 | .markdown-body img { 148 | border-style: none; 149 | max-width: 100%; 150 | box-sizing: content-box; 151 | background-color: var(--color-canvas-default); 152 | } 153 | 154 | .markdown-body code, 155 | .markdown-body kbd, 156 | .markdown-body pre, 157 | .markdown-body samp { 158 | font-family: monospace; 159 | font-size: 1em; 160 | } 161 | 162 | .markdown-body figure { 163 | margin: 1em 40px; 164 | } 165 | 166 | .markdown-body hr { 167 | box-sizing: content-box; 168 | overflow: hidden; 169 | background: transparent; 170 | border-bottom: 1px solid var(--color-border-muted); 171 | height: .25em; 172 | padding: 0; 173 | margin: 24px 0; 174 | background-color: var(--color-border-default); 175 | border: 0; 176 | } 177 | 178 | .markdown-body input { 179 | font: inherit; 180 | margin: 0; 181 | overflow: visible; 182 | font-family: inherit; 183 | font-size: inherit; 184 | line-height: inherit; 185 | } 186 | 187 | .markdown-body [type=button], 188 | .markdown-body [type=reset], 189 | .markdown-body [type=submit] { 190 | -webkit-appearance: button; 191 | } 192 | 193 | .markdown-body [type=checkbox], 194 | .markdown-body [type=radio] { 195 | box-sizing: border-box; 196 | padding: 0; 197 | } 198 | 199 | .markdown-body [type=number]::-webkit-inner-spin-button, 200 | .markdown-body [type=number]::-webkit-outer-spin-button { 201 | height: auto; 202 | } 203 | 204 | .markdown-body [type=search]::-webkit-search-cancel-button, 205 | .markdown-body [type=search]::-webkit-search-decoration { 206 | -webkit-appearance: none; 207 | } 208 | 209 | .markdown-body ::-webkit-input-placeholder { 210 | color: inherit; 211 | opacity: .54; 212 | } 213 | 214 | .markdown-body ::-webkit-file-upload-button { 215 | -webkit-appearance: button; 216 | font: inherit; 217 | } 218 | 219 | .markdown-body a:hover { 220 | text-decoration: underline; 221 | } 222 | 223 | .markdown-body ::placeholder { 224 | color: var(--color-fg-subtle); 225 | opacity: 1; 226 | } 227 | 228 | .markdown-body hr::before { 229 | display: table; 230 | content: ""; 231 | } 232 | 233 | .markdown-body hr::after { 234 | display: table; 235 | clear: both; 236 | content: ""; 237 | } 238 | 239 | .markdown-body table { 240 | border-spacing: 0; 241 | border-collapse: collapse; 242 | display: block; 243 | width: max-content; 244 | max-width: 100%; 245 | overflow: auto; 246 | } 247 | 248 | .markdown-body td, 249 | .markdown-body th { 250 | padding: 0; 251 | } 252 | 253 | .markdown-body details summary { 254 | cursor: pointer; 255 | } 256 | 257 | .markdown-body details:not([open])>*:not(summary) { 258 | display: none !important; 259 | } 260 | 261 | .markdown-body a:focus, 262 | .markdown-body [role=button]:focus, 263 | .markdown-body input[type=radio]:focus, 264 | .markdown-body input[type=checkbox]:focus { 265 | outline: 2px solid var(--color-accent-fg); 266 | outline-offset: -2px; 267 | box-shadow: none; 268 | } 269 | 270 | .markdown-body a:focus:not(:focus-visible), 271 | .markdown-body [role=button]:focus:not(:focus-visible), 272 | .markdown-body input[type=radio]:focus:not(:focus-visible), 273 | .markdown-body input[type=checkbox]:focus:not(:focus-visible) { 274 | outline: solid 1px transparent; 275 | } 276 | 277 | .markdown-body a:focus-visible, 278 | .markdown-body [role=button]:focus-visible, 279 | .markdown-body input[type=radio]:focus-visible, 280 | .markdown-body input[type=checkbox]:focus-visible { 281 | outline: 2px solid var(--color-accent-fg); 282 | outline-offset: -2px; 283 | box-shadow: none; 284 | } 285 | 286 | .markdown-body a:not([class]):focus, 287 | .markdown-body a:not([class]):focus-visible, 288 | .markdown-body input[type=radio]:focus, 289 | .markdown-body input[type=radio]:focus-visible, 290 | .markdown-body input[type=checkbox]:focus, 291 | .markdown-body input[type=checkbox]:focus-visible { 292 | outline-offset: 0; 293 | } 294 | 295 | .markdown-body kbd { 296 | display: inline-block; 297 | padding: 3px 5px; 298 | font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 299 | line-height: 10px; 300 | color: var(--color-fg-default); 301 | vertical-align: middle; 302 | background-color: var(--color-canvas-subtle); 303 | border: solid 1px var(--color-neutral-muted); 304 | border-bottom-color: var(--color-neutral-muted); 305 | border-radius: 6px; 306 | box-shadow: inset 0 -1px 0 var(--color-neutral-muted); 307 | } 308 | 309 | .markdown-body h1, 310 | .markdown-body h2, 311 | .markdown-body h3, 312 | .markdown-body h4, 313 | .markdown-body h5, 314 | .markdown-body h6 { 315 | margin-top: 24px; 316 | margin-bottom: 16px; 317 | font-weight: var(--base-text-weight-semibold, 600); 318 | line-height: 1.25; 319 | } 320 | 321 | .markdown-body h2 { 322 | font-weight: var(--base-text-weight-semibold, 600); 323 | padding-bottom: .3em; 324 | font-size: 1.5em; 325 | border-bottom: 1px solid var(--color-border-muted); 326 | } 327 | 328 | .markdown-body h3 { 329 | font-weight: var(--base-text-weight-semibold, 600); 330 | font-size: 1.25em; 331 | } 332 | 333 | .markdown-body h4 { 334 | font-weight: var(--base-text-weight-semibold, 600); 335 | font-size: 1em; 336 | } 337 | 338 | .markdown-body h5 { 339 | font-weight: var(--base-text-weight-semibold, 600); 340 | font-size: .875em; 341 | } 342 | 343 | .markdown-body h6 { 344 | font-weight: var(--base-text-weight-semibold, 600); 345 | font-size: .85em; 346 | color: var(--color-fg-muted); 347 | } 348 | 349 | .markdown-body p { 350 | margin-top: 0; 351 | margin-bottom: 10px; 352 | } 353 | 354 | .markdown-body blockquote { 355 | margin: 0; 356 | padding: 0 1em; 357 | color: var(--color-fg-muted); 358 | border-left: .25em solid var(--color-border-default); 359 | } 360 | 361 | .markdown-body ul, 362 | .markdown-body ol { 363 | margin-top: 0; 364 | margin-bottom: 0; 365 | padding-left: 2em; 366 | } 367 | 368 | .markdown-body ol ol, 369 | .markdown-body ul ol { 370 | list-style-type: lower-roman; 371 | } 372 | 373 | .markdown-body ul ul ol, 374 | .markdown-body ul ol ol, 375 | .markdown-body ol ul ol, 376 | .markdown-body ol ol ol { 377 | list-style-type: lower-alpha; 378 | } 379 | 380 | .markdown-body dd { 381 | margin-left: 0; 382 | } 383 | 384 | .markdown-body tt, 385 | .markdown-body code, 386 | .markdown-body samp { 387 | font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 388 | font-size: 12px; 389 | } 390 | 391 | .markdown-body pre { 392 | margin-top: 0; 393 | margin-bottom: 0; 394 | font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 395 | font-size: 12px; 396 | word-wrap: normal; 397 | } 398 | 399 | .markdown-body .octicon { 400 | display: inline-block; 401 | overflow: visible !important; 402 | vertical-align: text-bottom; 403 | fill: currentColor; 404 | } 405 | 406 | .markdown-body input::-webkit-outer-spin-button, 407 | .markdown-body input::-webkit-inner-spin-button { 408 | margin: 0; 409 | -webkit-appearance: none; 410 | appearance: none; 411 | } 412 | 413 | .markdown-body::before { 414 | display: table; 415 | content: ""; 416 | } 417 | 418 | .markdown-body::after { 419 | display: table; 420 | clear: both; 421 | content: ""; 422 | } 423 | 424 | .markdown-body>*:first-child { 425 | margin-top: 0 !important; 426 | } 427 | 428 | .markdown-body>*:last-child { 429 | margin-bottom: 0 !important; 430 | } 431 | 432 | .markdown-body a:not([href]) { 433 | color: inherit; 434 | text-decoration: none; 435 | } 436 | 437 | .markdown-body .absent { 438 | color: var(--color-danger-fg); 439 | } 440 | 441 | .markdown-body .anchor { 442 | float: left; 443 | padding-right: 4px; 444 | margin-left: -20px; 445 | line-height: 1; 446 | } 447 | 448 | .markdown-body .anchor:focus { 449 | outline: none; 450 | } 451 | 452 | .markdown-body p, 453 | .markdown-body blockquote, 454 | .markdown-body ul, 455 | .markdown-body ol, 456 | .markdown-body dl, 457 | .markdown-body table, 458 | .markdown-body pre, 459 | .markdown-body details { 460 | margin-top: 0; 461 | margin-bottom: 16px; 462 | } 463 | 464 | .markdown-body blockquote>:first-child { 465 | margin-top: 0; 466 | } 467 | 468 | .markdown-body blockquote>:last-child { 469 | margin-bottom: 0; 470 | } 471 | 472 | .markdown-body h1 .octicon-link, 473 | .markdown-body h2 .octicon-link, 474 | .markdown-body h3 .octicon-link, 475 | .markdown-body h4 .octicon-link, 476 | .markdown-body h5 .octicon-link, 477 | .markdown-body h6 .octicon-link { 478 | color: var(--color-fg-default); 479 | vertical-align: middle; 480 | visibility: hidden; 481 | } 482 | 483 | .markdown-body h1:hover .anchor, 484 | .markdown-body h2:hover .anchor, 485 | .markdown-body h3:hover .anchor, 486 | .markdown-body h4:hover .anchor, 487 | .markdown-body h5:hover .anchor, 488 | .markdown-body h6:hover .anchor { 489 | text-decoration: none; 490 | } 491 | 492 | .markdown-body h1:hover .anchor .octicon-link, 493 | .markdown-body h2:hover .anchor .octicon-link, 494 | .markdown-body h3:hover .anchor .octicon-link, 495 | .markdown-body h4:hover .anchor .octicon-link, 496 | .markdown-body h5:hover .anchor .octicon-link, 497 | .markdown-body h6:hover .anchor .octicon-link { 498 | visibility: visible; 499 | } 500 | 501 | .markdown-body h1 tt, 502 | .markdown-body h1 code, 503 | .markdown-body h2 tt, 504 | .markdown-body h2 code, 505 | .markdown-body h3 tt, 506 | .markdown-body h3 code, 507 | .markdown-body h4 tt, 508 | .markdown-body h4 code, 509 | .markdown-body h5 tt, 510 | .markdown-body h5 code, 511 | .markdown-body h6 tt, 512 | .markdown-body h6 code { 513 | padding: 0 .2em; 514 | font-size: inherit; 515 | } 516 | 517 | .markdown-body summary h1, 518 | .markdown-body summary h2, 519 | .markdown-body summary h3, 520 | .markdown-body summary h4, 521 | .markdown-body summary h5, 522 | .markdown-body summary h6 { 523 | display: inline-block; 524 | } 525 | 526 | .markdown-body summary h1 .anchor, 527 | .markdown-body summary h2 .anchor, 528 | .markdown-body summary h3 .anchor, 529 | .markdown-body summary h4 .anchor, 530 | .markdown-body summary h5 .anchor, 531 | .markdown-body summary h6 .anchor { 532 | margin-left: -40px; 533 | } 534 | 535 | .markdown-body summary h1, 536 | .markdown-body summary h2 { 537 | padding-bottom: 0; 538 | border-bottom: 0; 539 | } 540 | 541 | .markdown-body ul.no-list, 542 | .markdown-body ol.no-list { 543 | padding: 0; 544 | list-style-type: none; 545 | } 546 | 547 | .markdown-body ol[type=a] { 548 | list-style-type: lower-alpha; 549 | } 550 | 551 | .markdown-body ol[type=A] { 552 | list-style-type: upper-alpha; 553 | } 554 | 555 | .markdown-body ol[type=i] { 556 | list-style-type: lower-roman; 557 | } 558 | 559 | .markdown-body ol[type=I] { 560 | list-style-type: upper-roman; 561 | } 562 | 563 | .markdown-body ol[type="1"] { 564 | list-style-type: decimal; 565 | } 566 | 567 | .markdown-body div>ol:not([type]) { 568 | list-style-type: decimal; 569 | } 570 | 571 | .markdown-body ul ul, 572 | .markdown-body ul ol, 573 | .markdown-body ol ol, 574 | .markdown-body ol ul { 575 | margin-top: 0; 576 | margin-bottom: 0; 577 | } 578 | 579 | .markdown-body li>p { 580 | margin-top: 16px; 581 | } 582 | 583 | .markdown-body li+li { 584 | margin-top: .25em; 585 | } 586 | 587 | .markdown-body dl { 588 | padding: 0; 589 | } 590 | 591 | .markdown-body dl dt { 592 | padding: 0; 593 | margin-top: 16px; 594 | font-size: 1em; 595 | font-style: italic; 596 | font-weight: var(--base-text-weight-semibold, 600); 597 | } 598 | 599 | .markdown-body dl dd { 600 | padding: 0 16px; 601 | margin-bottom: 16px; 602 | } 603 | 604 | .markdown-body table th { 605 | font-weight: var(--base-text-weight-semibold, 600); 606 | } 607 | 608 | .markdown-body table th, 609 | .markdown-body table td { 610 | padding: 6px 13px; 611 | border: 1px solid var(--color-border-default); 612 | } 613 | 614 | .markdown-body table tr { 615 | background-color: var(--color-canvas-default); 616 | border-top: 1px solid var(--color-border-muted); 617 | } 618 | 619 | .markdown-body table tr:nth-child(2n) { 620 | background-color: var(--color-canvas-subtle); 621 | } 622 | 623 | .markdown-body table img { 624 | background-color: transparent; 625 | } 626 | 627 | .markdown-body img[align=right] { 628 | padding-left: 20px; 629 | } 630 | 631 | .markdown-body img[align=left] { 632 | padding-right: 20px; 633 | } 634 | 635 | .markdown-body .emoji { 636 | max-width: none; 637 | vertical-align: text-top; 638 | background-color: transparent; 639 | } 640 | 641 | .markdown-body span.frame { 642 | display: block; 643 | overflow: hidden; 644 | } 645 | 646 | .markdown-body span.frame>span { 647 | display: block; 648 | float: left; 649 | width: auto; 650 | padding: 7px; 651 | margin: 13px 0 0; 652 | overflow: hidden; 653 | border: 1px solid var(--color-border-default); 654 | } 655 | 656 | .markdown-body span.frame span img { 657 | display: block; 658 | float: left; 659 | } 660 | 661 | .markdown-body span.frame span span { 662 | display: block; 663 | padding: 5px 0 0; 664 | clear: both; 665 | color: var(--color-fg-default); 666 | } 667 | 668 | .markdown-body span.align-center { 669 | display: block; 670 | overflow: hidden; 671 | clear: both; 672 | } 673 | 674 | .markdown-body span.align-center>span { 675 | display: block; 676 | margin: 13px auto 0; 677 | overflow: hidden; 678 | text-align: center; 679 | } 680 | 681 | .markdown-body span.align-center span img { 682 | margin: 0 auto; 683 | text-align: center; 684 | } 685 | 686 | .markdown-body span.align-right { 687 | display: block; 688 | overflow: hidden; 689 | clear: both; 690 | } 691 | 692 | .markdown-body span.align-right>span { 693 | display: block; 694 | margin: 13px 0 0; 695 | overflow: hidden; 696 | text-align: right; 697 | } 698 | 699 | .markdown-body span.align-right span img { 700 | margin: 0; 701 | text-align: right; 702 | } 703 | 704 | .markdown-body span.float-left { 705 | display: block; 706 | float: left; 707 | margin-right: 13px; 708 | overflow: hidden; 709 | } 710 | 711 | .markdown-body span.float-left span { 712 | margin: 13px 0 0; 713 | } 714 | 715 | .markdown-body span.float-right { 716 | display: block; 717 | float: right; 718 | margin-left: 13px; 719 | overflow: hidden; 720 | } 721 | 722 | .markdown-body span.float-right>span { 723 | display: block; 724 | margin: 13px auto 0; 725 | overflow: hidden; 726 | text-align: right; 727 | } 728 | 729 | .markdown-body code, 730 | .markdown-body tt { 731 | padding: .2em .4em; 732 | margin: 0; 733 | font-size: 85%; 734 | white-space: break-spaces; 735 | background-color: var(--color-neutral-muted); 736 | border-radius: 6px; 737 | } 738 | 739 | .markdown-body code br, 740 | .markdown-body tt br { 741 | display: none; 742 | } 743 | 744 | .markdown-body del code { 745 | text-decoration: inherit; 746 | } 747 | 748 | .markdown-body samp { 749 | font-size: 85%; 750 | } 751 | 752 | .markdown-body pre code { 753 | font-size: 100%; 754 | } 755 | 756 | .markdown-body pre>code { 757 | padding: 0; 758 | margin: 0; 759 | word-break: normal; 760 | white-space: pre; 761 | background: transparent; 762 | border: 0; 763 | } 764 | 765 | .markdown-body .highlight { 766 | margin-bottom: 16px; 767 | } 768 | 769 | .markdown-body .highlight pre { 770 | margin-bottom: 0; 771 | word-break: normal; 772 | } 773 | 774 | .markdown-body .highlight pre, 775 | .markdown-body pre { 776 | padding: 16px; 777 | overflow: auto; 778 | font-size: 85%; 779 | line-height: 1.45; 780 | background-color: var(--color-canvas-subtle); 781 | border-radius: 6px; 782 | } 783 | 784 | .markdown-body pre code, 785 | .markdown-body pre tt { 786 | display: inline; 787 | max-width: auto; 788 | padding: 0; 789 | margin: 0; 790 | overflow: visible; 791 | line-height: inherit; 792 | word-wrap: normal; 793 | background-color: transparent; 794 | border: 0; 795 | } 796 | 797 | .markdown-body .csv-data td, 798 | .markdown-body .csv-data th { 799 | padding: 5px; 800 | overflow: hidden; 801 | font-size: 12px; 802 | line-height: 1; 803 | text-align: left; 804 | white-space: nowrap; 805 | } 806 | 807 | .markdown-body .csv-data .blob-num { 808 | padding: 10px 8px 9px; 809 | text-align: right; 810 | background: var(--color-canvas-default); 811 | border: 0; 812 | } 813 | 814 | .markdown-body .csv-data tr { 815 | border-top: 0; 816 | } 817 | 818 | .markdown-body .csv-data th { 819 | font-weight: var(--base-text-weight-semibold, 600); 820 | background: var(--color-canvas-subtle); 821 | border-top: 0; 822 | } 823 | 824 | .markdown-body [data-footnote-ref]::before { 825 | content: "["; 826 | } 827 | 828 | .markdown-body [data-footnote-ref]::after { 829 | content: "]"; 830 | } 831 | 832 | .markdown-body .footnotes { 833 | font-size: 12px; 834 | color: var(--color-fg-muted); 835 | border-top: 1px solid var(--color-border-default); 836 | } 837 | 838 | .markdown-body .footnotes ol { 839 | padding-left: 16px; 840 | } 841 | 842 | .markdown-body .footnotes ol ul { 843 | display: inline-block; 844 | padding-left: 16px; 845 | margin-top: 16px; 846 | } 847 | 848 | .markdown-body .footnotes li { 849 | position: relative; 850 | } 851 | 852 | .markdown-body .footnotes li:target::before { 853 | position: absolute; 854 | top: -8px; 855 | right: -8px; 856 | bottom: -8px; 857 | left: -24px; 858 | pointer-events: none; 859 | content: ""; 860 | border: 2px solid var(--color-accent-emphasis); 861 | border-radius: 6px; 862 | } 863 | 864 | .markdown-body .footnotes li:target { 865 | color: var(--color-fg-default); 866 | } 867 | 868 | .markdown-body .footnotes .data-footnote-backref g-emoji { 869 | font-family: monospace; 870 | } 871 | 872 | .markdown-body .pl-c { 873 | color: var(--color-prettylights-syntax-comment); 874 | } 875 | 876 | .markdown-body .pl-c1, 877 | .markdown-body .pl-s .pl-v { 878 | color: var(--color-prettylights-syntax-constant); 879 | } 880 | 881 | .markdown-body .pl-e, 882 | .markdown-body .pl-en { 883 | color: var(--color-prettylights-syntax-entity); 884 | } 885 | 886 | .markdown-body .pl-smi, 887 | .markdown-body .pl-s .pl-s1 { 888 | color: var(--color-prettylights-syntax-storage-modifier-import); 889 | } 890 | 891 | .markdown-body .pl-ent { 892 | color: var(--color-prettylights-syntax-entity-tag); 893 | } 894 | 895 | .markdown-body .pl-k { 896 | color: var(--color-prettylights-syntax-keyword); 897 | } 898 | 899 | .markdown-body .pl-s, 900 | .markdown-body .pl-pds, 901 | .markdown-body .pl-s .pl-pse .pl-s1, 902 | .markdown-body .pl-sr, 903 | .markdown-body .pl-sr .pl-cce, 904 | .markdown-body .pl-sr .pl-sre, 905 | .markdown-body .pl-sr .pl-sra { 906 | color: var(--color-prettylights-syntax-string); 907 | } 908 | 909 | .markdown-body .pl-v, 910 | .markdown-body .pl-smw { 911 | color: var(--color-prettylights-syntax-variable); 912 | } 913 | 914 | .markdown-body .pl-bu { 915 | color: var(--color-prettylights-syntax-brackethighlighter-unmatched); 916 | } 917 | 918 | .markdown-body .pl-ii { 919 | color: var(--color-prettylights-syntax-invalid-illegal-text); 920 | background-color: var(--color-prettylights-syntax-invalid-illegal-bg); 921 | } 922 | 923 | .markdown-body .pl-c2 { 924 | color: var(--color-prettylights-syntax-carriage-return-text); 925 | background-color: var(--color-prettylights-syntax-carriage-return-bg); 926 | } 927 | 928 | .markdown-body .pl-sr .pl-cce { 929 | font-weight: bold; 930 | color: var(--color-prettylights-syntax-string-regexp); 931 | } 932 | 933 | .markdown-body .pl-ml { 934 | color: var(--color-prettylights-syntax-markup-list); 935 | } 936 | 937 | .markdown-body .pl-mh, 938 | .markdown-body .pl-mh .pl-en, 939 | .markdown-body .pl-ms { 940 | font-weight: bold; 941 | color: var(--color-prettylights-syntax-markup-heading); 942 | } 943 | 944 | .markdown-body .pl-mi { 945 | font-style: italic; 946 | color: var(--color-prettylights-syntax-markup-italic); 947 | } 948 | 949 | .markdown-body .pl-mb { 950 | font-weight: bold; 951 | color: var(--color-prettylights-syntax-markup-bold); 952 | } 953 | 954 | .markdown-body .pl-md { 955 | color: var(--color-prettylights-syntax-markup-deleted-text); 956 | background-color: var(--color-prettylights-syntax-markup-deleted-bg); 957 | } 958 | 959 | .markdown-body .pl-mi1 { 960 | color: var(--color-prettylights-syntax-markup-inserted-text); 961 | background-color: var(--color-prettylights-syntax-markup-inserted-bg); 962 | } 963 | 964 | .markdown-body .pl-mc { 965 | color: var(--color-prettylights-syntax-markup-changed-text); 966 | background-color: var(--color-prettylights-syntax-markup-changed-bg); 967 | } 968 | 969 | .markdown-body .pl-mi2 { 970 | color: var(--color-prettylights-syntax-markup-ignored-text); 971 | background-color: var(--color-prettylights-syntax-markup-ignored-bg); 972 | } 973 | 974 | .markdown-body .pl-mdr { 975 | font-weight: bold; 976 | color: var(--color-prettylights-syntax-meta-diff-range); 977 | } 978 | 979 | .markdown-body .pl-ba { 980 | color: var(--color-prettylights-syntax-brackethighlighter-angle); 981 | } 982 | 983 | .markdown-body .pl-sg { 984 | color: var(--color-prettylights-syntax-sublimelinter-gutter-mark); 985 | } 986 | 987 | .markdown-body .pl-corl { 988 | text-decoration: underline; 989 | color: var(--color-prettylights-syntax-constant-other-reference-link); 990 | } 991 | 992 | .markdown-body g-emoji { 993 | display: inline-block; 994 | min-width: 1ch; 995 | font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; 996 | font-size: 1em; 997 | font-style: normal !important; 998 | font-weight: var(--base-text-weight-normal, 400); 999 | line-height: 1; 1000 | vertical-align: -0.075em; 1001 | } 1002 | 1003 | .markdown-body g-emoji img { 1004 | width: 1em; 1005 | height: 1em; 1006 | } 1007 | 1008 | .markdown-body .task-list-item { 1009 | list-style-type: none; 1010 | } 1011 | 1012 | .markdown-body .task-list-item label { 1013 | font-weight: var(--base-text-weight-normal, 400); 1014 | } 1015 | 1016 | .markdown-body .task-list-item.enabled label { 1017 | cursor: pointer; 1018 | } 1019 | 1020 | .markdown-body .task-list-item+.task-list-item { 1021 | margin-top: 4px; 1022 | } 1023 | 1024 | .markdown-body .task-list-item .handle { 1025 | display: none; 1026 | } 1027 | 1028 | .markdown-body .task-list-item-checkbox { 1029 | margin: 0 .2em .25em -1.4em; 1030 | vertical-align: middle; 1031 | } 1032 | 1033 | .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { 1034 | margin: 0 -1.6em .25em .2em; 1035 | } 1036 | 1037 | .markdown-body .contains-task-list { 1038 | position: relative; 1039 | } 1040 | 1041 | .markdown-body .contains-task-list:hover .task-list-item-convert-container, 1042 | .markdown-body .contains-task-list:focus-within .task-list-item-convert-container { 1043 | display: block; 1044 | width: auto; 1045 | height: 24px; 1046 | overflow: visible; 1047 | clip: auto; 1048 | } 1049 | 1050 | .markdown-body ::-webkit-calendar-picker-indicator { 1051 | filter: invert(50%); 1052 | } 1053 | -------------------------------------------------------------------------------- /packages/quick-question/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "nodenext", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@/*": ["./*"] 20 | } 21 | }, 22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules", "data"] 24 | } 25 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for i in $(find pages -type f); do 4 | sed -i 's/process.env.REPO_DIR!/"data\/diffusers"/g' $i 5 | done 6 | 7 | vercel "$@" 8 | 9 | git co . 10 | --------------------------------------------------------------------------------