├── .changeset
├── README.md
└── config.json
├── .editorconfig
├── .envrc
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .husky
└── post-checkout
├── .npmrc
├── .remarkignore
├── .remarkrc.json
├── .stylelintignore
├── .stylelintrc.json
├── .vscode
├── console.code-snippets
├── extensions.json
├── operators.code-snippets
└── settings.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── biome.jsonc
├── codecov.yml
├── eslint.config.mjs
├── examples
├── with-automatic-ticket-creation
│ ├── CHANGELOG.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ ├── avatars
│ │ │ ├── logo.png
│ │ │ └── user.png
│ │ ├── markprompt.png
│ │ └── markprompt.svg
│ ├── src
│ │ ├── main.ts
│ │ ├── style.css
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.js
├── with-css-modules
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── markprompt.svg
│ ├── src
│ │ ├── App.tsx
│ │ ├── index.css
│ │ ├── main.tsx
│ │ ├── markprompt.module.css
│ │ └── vite-env.d.ts
│ └── tsconfig.json
├── with-custom-trigger-react
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── markprompt.svg
│ ├── src
│ │ ├── App.module.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ └── react.svg
│ │ ├── index.css
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── with-custom-trigger
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── markprompt.svg
│ ├── src
│ │ ├── main.module.css
│ │ ├── main.ts
│ │ ├── style.css
│ │ └── vite-env.d.ts
│ └── tsconfig.json
├── with-custom-ui
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ ├── avatars
│ │ │ ├── logo.png
│ │ │ └── user.png
│ │ ├── markprompt.png
│ │ └── markprompt.svg
│ ├── src
│ │ ├── main.tsx
│ │ ├── style.css
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.js
├── with-docusaurus-algolia
│ ├── README.md
│ ├── babel.config.js
│ ├── docs
│ │ └── intro.md
│ ├── docusaurus.config.ts
│ ├── package.json
│ ├── sidebars.js
│ ├── src
│ │ ├── css
│ │ │ └── custom.css
│ │ ├── markprompt-config.js
│ │ └── pages
│ │ │ ├── index.module.css
│ │ │ ├── index.tsx
│ │ │ └── markdown-page.md
│ ├── static
│ │ └── img
│ │ │ ├── docusaurus-social-card.jpg
│ │ │ ├── docusaurus.png
│ │ │ ├── favicon.ico
│ │ │ └── logo.svg
│ └── tsconfig.json
├── with-docusaurus-swizzled
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── babel.config.js
│ ├── docs
│ │ └── intro.md
│ ├── docusaurus.config.ts
│ ├── package.json
│ ├── sidebars.js
│ ├── src
│ │ ├── css
│ │ │ └── custom.css
│ │ ├── pages
│ │ │ ├── index.module.css
│ │ │ ├── index.tsx
│ │ │ └── markdown-page.md
│ │ └── theme
│ │ │ └── SearchBar
│ │ │ └── index.tsx
│ ├── static
│ │ └── img
│ │ │ ├── docusaurus-social-card.jpg
│ │ │ ├── docusaurus.png
│ │ │ ├── favicon.ico
│ │ │ └── logo.svg
│ └── tsconfig.json
├── with-docusaurus
│ ├── README.md
│ ├── babel.config.js
│ ├── docs
│ │ └── intro.md
│ ├── docusaurus.config.ts
│ ├── package.json
│ ├── sidebars.js
│ ├── src
│ │ ├── css
│ │ │ └── custom.css
│ │ ├── markprompt-config.js
│ │ └── pages
│ │ │ ├── index.module.css
│ │ │ ├── index.tsx
│ │ │ └── markdown-page.md
│ ├── static
│ │ └── img
│ │ │ ├── docusaurus-social-card.jpg
│ │ │ ├── docusaurus.png
│ │ │ ├── favicon.ico
│ │ │ └── logo.svg
│ └── tsconfig.json
├── with-embed
│ ├── chatbot.html
│ └── embed.html
├── with-function-calling
│ ├── README.md
│ ├── example.env
│ ├── next-env.d.ts
│ ├── package.json
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── global.css
│ │ └── index.tsx
│ ├── public
│ │ └── markprompt.svg
│ └── tsconfig.json
├── with-init
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── markprompt.svg
│ ├── src
│ │ ├── main.ts
│ │ ├── style.css
│ │ └── vite-env.d.ts
│ └── tsconfig.json
├── with-markprompt-web
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ ├── avatars
│ │ │ ├── logo.png
│ │ │ └── user.png
│ │ ├── markprompt.png
│ │ └── markprompt.svg
│ ├── src
│ │ ├── main.ts
│ │ ├── style.css
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.js
├── with-next
│ ├── README.md
│ ├── components
│ │ └── icons.tsx
│ ├── next-env.d.ts
│ ├── package.json
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ ├── global.css
│ │ └── index.tsx
│ ├── public
│ │ ├── avatars
│ │ │ ├── logo.png
│ │ │ └── user.png
│ │ └── markprompt.svg
│ └── tsconfig.json
├── with-standalone-ticket-deflection
│ ├── eslint.config.js
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── markprompt.svg
│ ├── src
│ │ ├── main.ts
│ │ ├── style.css
│ │ └── vite-env.d.ts
│ └── tsconfig.json
└── with-ticket-creation
│ ├── CHANGELOG.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ ├── avatars
│ │ ├── logo.png
│ │ └── user.png
│ ├── markprompt.png
│ └── markprompt.svg
│ ├── src
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.js
├── flake.lock
├── flake.nix
├── package.json
├── packages
├── core
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── eslint.config.js
│ ├── package.json
│ ├── src
│ │ ├── chat
│ │ │ ├── index.test.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── constants.ts
│ │ ├── docsearch.ts
│ │ ├── feedback.test.ts
│ │ ├── feedback.ts
│ │ ├── search.test.ts
│ │ ├── search.ts
│ │ ├── types.ts
│ │ ├── utils.test.ts
│ │ └── utils.ts
│ ├── tsconfig.json
│ └── vitest.config.js
├── css
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── index.d.ts
│ ├── markprompt.css
│ └── package.json
├── docusaurus-theme-search
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── eslint.config.js
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ └── theme
│ │ │ └── SearchBar
│ │ │ └── index.tsx
│ ├── tsconfig.json
│ └── vitest.config.js
├── eslint-config
│ ├── eslint.config.js
│ ├── package.json
│ ├── src
│ │ ├── astro.ts
│ │ ├── base.ts
│ │ ├── index.ts
│ │ ├── next.ts
│ │ ├── react.ts
│ │ ├── tanstack.ts
│ │ ├── types
│ │ │ ├── eslint-config-turbo.d.ts
│ │ │ ├── eslint-plugin-jsx-a11y.d.ts
│ │ │ ├── eslint-plugin-promise.d.ts
│ │ │ ├── eslint-plugin-react-refresh.d.ts
│ │ │ ├── eslint-plugin-react.d.ts
│ │ │ ├── eslint-plugin-testing-library.d.ts
│ │ │ └── next__eslint-plugin-next.d.ts
│ │ └── vitest.ts
│ ├── tsconfig.json
│ └── turbo.json
├── react
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── __mocks__
│ │ └── zustand.ts
│ ├── eslint.config.js
│ ├── package.json
│ ├── src
│ │ ├── CreateTicketView.tsx
│ │ ├── Markprompt.test.tsx
│ │ ├── Markprompt.tsx
│ │ ├── Menu.tsx
│ │ ├── TicketDeflectionForm.tsx
│ │ ├── Trigger.tsx
│ │ ├── chat
│ │ │ ├── Answer.tsx
│ │ │ ├── AssistantMessage.tsx
│ │ │ ├── ChatView.test.tsx
│ │ │ ├── ChatView.tsx
│ │ │ ├── ChatViewForm.tsx
│ │ │ ├── DefaultToolCallsConfirmation.tsx
│ │ │ ├── DefaultView.tsx
│ │ │ ├── MessageAnswer.tsx
│ │ │ ├── MessagePrompt.tsx
│ │ │ ├── Messages.tsx
│ │ │ ├── References.tsx
│ │ │ ├── ThreadSelect.tsx
│ │ │ ├── ThreadSidebar.tsx
│ │ │ ├── aes.ts
│ │ │ ├── provider.tsx
│ │ │ ├── store.tsx
│ │ │ ├── supabase.ts
│ │ │ └── utils.ts
│ │ ├── constants.test.ts
│ │ ├── constants.tsx
│ │ ├── context
│ │ │ └── global
│ │ │ │ ├── provider.tsx
│ │ │ │ ├── store.ts
│ │ │ │ └── utils.ts
│ │ ├── feedback
│ │ │ ├── Feedback.test.tsx
│ │ │ ├── Feedback.tsx
│ │ │ ├── csat-picker.tsx
│ │ │ ├── useFeedback.test.ts
│ │ │ └── useFeedback.ts
│ │ ├── icons.test.tsx
│ │ ├── icons.tsx
│ │ ├── index.ts
│ │ ├── primitives
│ │ │ ├── ConditionalWrap.test.tsx
│ │ │ ├── ConditionalWrap.tsx
│ │ │ ├── Select.tsx
│ │ │ ├── branding.test.tsx
│ │ │ ├── branding.tsx
│ │ │ ├── headless.test.tsx
│ │ │ └── headless.tsx
│ │ ├── search
│ │ │ ├── SearchBoxTrigger.tsx
│ │ │ ├── SearchResult.tsx
│ │ │ ├── SearchView.test.tsx
│ │ │ ├── SearchView.tsx
│ │ │ ├── useSearch.test.ts
│ │ │ └── useSearch.ts
│ │ ├── test-utils.ts
│ │ ├── types.ts
│ │ ├── ui
│ │ │ ├── navigation-menu.tsx
│ │ │ ├── rich-text.tsx
│ │ │ └── utils.ts
│ │ ├── useAbortController.tsx
│ │ ├── useDefaults.test.tsx
│ │ ├── useDefaults.ts
│ │ ├── useMediaQuery.ts
│ │ ├── utils.test.ts
│ │ └── utils.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ ├── vitest.config.ts
│ └── vitest.setup.ts
└── web
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── eslint.config.js
│ ├── package.json
│ ├── scripts
│ ├── analyze.js
│ ├── build.js
│ ├── config.js
│ ├── dev.js
│ └── tsc-plugin.js
│ ├── src
│ ├── index.tsx
│ └── init.ts
│ ├── tsconfig.json
│ └── vitest.config.js
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── test
└── utils.ts
├── tsconfig.json
├── turbo.json
└── vitest.workspace.ts
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by
4 | `@changesets/cli`, a build tool that works with multi-package repos, or
5 | single-package repos to help you version and publish your code. You can find the
6 | full documentation for it
7 | [in our repository](https://github.com/changesets/changesets)
8 |
9 | We have a quick list of common questions to get you started engaging with this
10 | project in
11 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
12 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
3 | "access": "public",
4 | "baseBranch": "main",
5 | "changelog": [
6 | "@changesets/changelog-github",
7 | { "repo": "markprompt/markprompt-js" }
8 | ],
9 | "commit": false,
10 | "privatePackages": false,
11 | "updateInternalDependencies": "patch",
12 | "ignore": [
13 | "with-css-modules",
14 | "with-custom-trigger-react",
15 | "with-custom-trigger",
16 | "with-docusaurus-algolia",
17 | "with-docusaurus-swizzled",
18 | "with-docusaurus",
19 | "with-function-calling",
20 | "with-init",
21 | "with-markprompt-web",
22 | "with-next",
23 | "with-standalone-ticket-deflection"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | use flake;
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 |
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 |
15 | Steps to reproduce the behavior:
16 |
17 | 1. Go to '...'
18 | 2. Click on '....'
19 | 3. Scroll down to '....'
20 | 4. See error
21 |
22 | **Expected behavior**
23 |
24 | A clear and concise description of what you expected to happen.
25 |
26 | **Screenshots**
27 |
28 | If applicable, add screenshots to help explain your problem.
29 |
30 | **Desktop (please complete the following information):**
31 |
32 | - OS: [e.g. iOS]
33 | - Browser [e.g. chrome, safari]
34 | - Version [e.g. 22]
35 |
36 | **Smartphone (please complete the following information):**
37 |
38 | - Device: [e.g. iPhone6]
39 | - OS: [e.g. iOS8.1]
40 | - Browser [e.g. stock browser, safari]
41 | - Version [e.g. 22]
42 |
43 | **Additional context**
44 |
45 | Add any other context about the problem here.
46 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 |
11 | A clear and concise description of what the problem is. Ex. I'm always
12 | frustrated when [...]
13 |
14 | **Describe the solution you'd like**
15 |
16 | A clear and concise description of what you want to happen.
17 |
18 | **Describe alternatives you've considered**
19 |
20 | A clear and concise description of any alternative solutions or features you've
21 | considered.
22 |
23 | **Additional context**
24 |
25 | Add any other context or screenshots about the feature request here.
26 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | push:
7 | branches: [main]
8 |
9 | concurrency:
10 | group: ${{ github.workflow }}-${{ github.ref }}
11 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
12 |
13 | jobs:
14 | lint:
15 | runs-on: blacksmith-4vcpu-ubuntu-2204
16 | steps:
17 | - uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 2
20 | - uses: pnpm/action-setup@v4
21 | with:
22 | run_install: false
23 | - uses: useblacksmith/setup-node@v5
24 | with:
25 | node-version: 22
26 | cache: pnpm
27 | - run: pnpm install
28 | - run: pnpm lint:ci
29 | env:
30 | MARKPROMPT_PROJECT_KEY: ${{ secrets.MARKPROMPT_PROJECT_KEY }}
31 | NEXT_PUBLIC_MARKPROMPT_PROJECT_KEY:
32 | ${{ secrets.MARKPROMPT_PROJECT_KEY }}
33 |
34 | test:
35 | runs-on: blacksmith-4vcpu-ubuntu-2204
36 | steps:
37 | - uses: actions/checkout@v4
38 | with:
39 | fetch-depth: 2
40 | - uses: pnpm/action-setup@v4
41 | with:
42 | run_install: false
43 | - uses: useblacksmith/setup-node@v5
44 | with:
45 | node-version: 22
46 | cache: pnpm
47 | - run: pnpm install
48 | - run: pnpm build:packages
49 | - run: pnpm turbo run test
50 | - uses: codecov/codecov-action@v4
51 | env:
52 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
53 |
54 | bundle-size:
55 | runs-on: blacksmith-4vcpu-ubuntu-2204
56 | steps:
57 | - uses: actions/checkout@v4
58 | with:
59 | fetch-depth: 2
60 | - uses: pnpm/action-setup@v4
61 | with:
62 | run_install: false
63 | - uses: useblacksmith/setup-node@v5
64 | with:
65 | node-version: 22
66 | cache: pnpm
67 | - run: pnpm install
68 | - uses: preactjs/compressed-size-action@v2
69 | with:
70 | build-script: '"build:packages"'
71 | pattern: '{./packages/**/dist/**/*.{cjs,js},./packages/css/markprompt.css}'
72 | exclude: '{**/*.map,**/*.d.{ts,cts},**/node_modules/**,./packages/**/node_modules/**/dist/**/*,./packages/eslint-config/dist/**/*}'
73 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | id-token: write
13 | contents: write
14 | pull-requests: write
15 | steps:
16 | - uses: actions/checkout@v4
17 | - uses: pnpm/action-setup@v4
18 | with:
19 | run_install: false
20 | - uses: actions/setup-node@v4
21 | with:
22 | node-version: 22
23 | cache: pnpm
24 | - run: pnpm install
25 | - uses: changesets/action@v1
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
29 | with:
30 | version: pnpm run version
31 | publish: pnpm run publish
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 | *.tgz
6 |
7 | # packing
8 | dist/
9 | .docusaurus/
10 | .next/
11 | build/
12 |
13 | # testing
14 | /coverage/
15 | **/coverage/
16 |
17 | # production
18 | /build/
19 | /dist/
20 |
21 | # misc
22 | .DS_Store
23 | *.pem
24 | .idea/
25 | .direnv/
26 | .turbo/
27 |
28 | # debug
29 | *.log*
30 | meta.json
31 |
32 | # local env files
33 | .env
34 | .env.*
35 |
36 | private
37 |
38 | # typescript
39 | *.tsbuildinfo
40 |
--------------------------------------------------------------------------------
/.husky/post-checkout:
--------------------------------------------------------------------------------
1 | rm -f {examples,packages}/*/*.tsbuildinfo || true
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | lockfile-version = 3
2 |
3 | # https://docs.npmjs.com/generating-provenance-statements#using-third-party-package-publishing-tools
4 | provenance=true
5 |
--------------------------------------------------------------------------------
/.remarkignore:
--------------------------------------------------------------------------------
1 | **/CHANGELOG.md
2 | .changeset/*.md
3 | .direnv/
4 |
--------------------------------------------------------------------------------
/.remarkrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "settings": {
3 | "fences": true,
4 | "listItemIndent": "one"
5 | },
6 | "plugins": [
7 | "remark-frontmatter",
8 | ["remark-toc", { "tight": true }],
9 | "remark-validate-links",
10 | "unified-prettier"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | .next/
2 | build/
3 | coverage/
4 | dist/
5 | node_modules/
6 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard"],
3 | "rules": {
4 | "block-no-empty": null,
5 | "custom-property-pattern": null,
6 | "declaration-block-no-shorthand-property-overrides": null,
7 | "font-family-no-duplicate-names": null,
8 | "function-linear-gradient-no-nonstandard-direction": null,
9 | "function-no-unknown": null,
10 | "keyframe-block-no-duplicate-selectors": null,
11 | "keyframe-declaration-no-important": null,
12 | "keyframes-name-pattern": null,
13 | "media-feature-name-no-unknown": null,
14 | "named-grid-areas-no-invalid": null,
15 | "no-descending-specificity": null,
16 | "no-duplicate-at-import-rules": null,
17 | "no-invalid-position-at-import-rule": null,
18 | "number-max-precision": 7,
19 | "property-no-unknown": null,
20 | "selector-anb-no-unmatchable": null,
21 | "selector-class-pattern": null,
22 | "selector-id-pattern": null,
23 | "unit-no-unknown": null
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.vscode/console.code-snippets:
--------------------------------------------------------------------------------
1 | {
2 | "console.log": {
3 | "scope": "javascript,typescript,javascriptreact,typescriptreact",
4 | "prefix": "con",
5 | "body": ["console.log(\"$1\")"],
6 | "description": "console.log",
7 | },
8 | "JSON.stringify": {
9 | "scope": "javascript,typescript,javascriptreact,typescriptreact",
10 | "prefix": "json",
11 | "body": ["console.log(\"$1\", JSON.stringify($2, null, 2));"],
12 | "description": "JSON.stringify",
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "astro-build.astro-vscode",
4 | "biomejs.biome",
5 | "bradlc.vscode-tailwindcss",
6 | "dbaeumer.vscode-eslint",
7 | "financialforce.lana",
8 | "redhat.vscode-xml",
9 | "salesforce.salesforcedx-vscode",
10 | "unifiedjs.vscode-mdx"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/operators.code-snippets:
--------------------------------------------------------------------------------
1 | {
2 | "Effect Gen Function": {
3 | "prefix": "egen",
4 | "body": ["Effect.gen(function* ($) {\n\t$0\n})"],
5 | "description": "Effect generator Function with $ input",
6 | },
7 | "Gen Function": {
8 | "prefix": "gen",
9 | "body": ["function* ($) {}"],
10 | "description": "Generator Function with $ input",
11 | },
12 | "Gen Yield * tmp": {
13 | "prefix": "yy",
14 | "body": ["yield* $($0)"],
15 | "description": "Yield generator calling $()",
16 | },
17 | "Gen Yield *": {
18 | "prefix": "!",
19 | "body": ["yield* $($0)"],
20 | "description": "Yield generator calling $()",
21 | },
22 | "Otel import": {
23 | "prefix": "+otel",
24 | "body": ["import * as otel from '@opentelemetry/api'\n$0"],
25 | "description": "Import OpenTelemetry",
26 | },
27 | }
28 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.workingDirectories": [{"mode": "auto"}],
3 | "eslint.useFlatConfig": true,
4 | "prettier.requireConfig": true,
5 | "typescript.tsdk": "node_modules/typescript/lib",
6 | // parse CSS files as Tailwind
7 | "files.associations": {
8 | "*.css": "tailwindcss"
9 | },
10 | // improve suggestions UX when working with Tailwind
11 | "editor.quickSuggestions": {
12 | "strings": "on"
13 | },
14 | "editor.formatOnSave": true,
15 | "editor.codeActionsOnSave": {
16 | "quickfix.biome": "explicit",
17 | "source.fixAll.eslint": "explicit",
18 | },
19 | "[typescriptreact]": {
20 | "editor.defaultFormatter": "biomejs.biome",
21 | },
22 | "[javascript]": {
23 | "editor.defaultFormatter": "biomejs.biome"
24 | },
25 | "[typescript]": {
26 | "editor.defaultFormatter": "biomejs.biome"
27 | },
28 | "[json]": {
29 | "editor.defaultFormatter": "biomejs.biome"
30 | },
31 | "[jsonc]": {
32 | "editor.defaultFormatter": "biomejs.biome"
33 | },
34 | "[css]": {
35 | "editor.defaultFormatter": "biomejs.biome"
36 | },
37 | "[tailwindcss]": {
38 | "editor.defaultFormatter": "biomejs.biome"
39 | },
40 | }
41 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Getting Started
4 |
5 | To start working on Markprompt, see the GitHub
6 | _[Contributing to projects](https://docs.github.com/en/get-started/quickstart/contributing-to-projects)_
7 | documentation.
8 |
9 | After cloning, you need to install dependencies using
10 | [npm](https://www.npmjs.com):
11 |
12 | ```sh
13 | npm ci
14 | ```
15 |
16 | Markprompt is linted using [ESLint](https://eslint.org) and
17 | [Prettier](https://prettier.io). It is type checked using
18 | [TypeScript](https://www.typescriptlang.org). To verify your changes, run:
19 |
20 | ```sh
21 | npm run lint
22 | ```
23 |
24 | When you open a pull request, this is also run by our GitHub Actions
25 | [ci workflow](./.github/workflows/ci.yml) to make sure all code conforms to the
26 | code quality standards.
27 |
28 | You can format all code using the `prettier` command:
29 |
30 | ```sh
31 | npx prettier --write .
32 | ```
33 |
34 | Releases are managed using
35 | [Changesets](https://github.com/changesets/changesets). You need to add a
36 | changeset for changes that need a release. To add a changeset, run the following
37 | command, and follow the instructions from the command line:
38 |
39 | ```sh
40 | npx changeset
41 | ```
42 |
43 | Don’t forget to commit the generated changeset!
44 |
45 | ## Releasing
46 |
47 | > **Note** This section is only for maintainers
48 |
49 | Releases are managed using
50 | [Changesets](https://github.com/changesets/changesets). If there are any
51 | [changesets](./.changeset) present, Changesets will open a pull request. Merging
52 | this pull request will publish all updated packages to npm and create a GitHub
53 | release.
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Motif
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - 'examples/'
3 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { configs } from '@markprompt/eslint-config';
2 |
3 | export default [
4 | {
5 | ignores: ['coverage/', 'examples/', 'packages/'],
6 | },
7 | ...configs.base(import.meta.url, ['test/utils.ts']),
8 | {
9 | rules: {
10 | '@typescript-eslint/consistent-indexed-object-style': [
11 | 'error',
12 | 'index-signature',
13 | ],
14 | },
15 | },
16 | ];
17 |
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Automatic ticket creation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Click the chat button
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-automatic-ticket-creation",
3 | "version": "0.0.20",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "dev": "vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@markprompt/css": "workspace:*",
13 | "@markprompt/web": "workspace:*",
14 | "preact": "^10.25.2",
15 | "zod": "^3.23.8"
16 | },
17 | "devDependencies": {
18 | "vite": "^6.0.3"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/public/avatars/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-automatic-ticket-creation/public/avatars/logo.png
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/public/avatars/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-automatic-ticket-creation/public/avatars/user.png
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/public/markprompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-automatic-ticket-creation/public/markprompt.png
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/src/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 | color-scheme: light dark;
6 | color: #737373;
7 | background-color: #f5f5f5;
8 | font-synthesis: none;
9 | text-rendering: optimizelegibility;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | text-size-adjust: 100%;
13 | }
14 |
15 | body {
16 | margin: 0;
17 | }
18 |
19 | button {
20 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
21 | }
22 |
23 | body,
24 | #app {
25 | min-height: 100vh;
26 | min-height: 100dvh;
27 | }
28 |
29 | main {
30 | position: relative;
31 | display: grid;
32 | place-items: center;
33 | width: 100%;
34 | height: 100vh;
35 | }
36 |
37 | #message {
38 | position: absolute;
39 | display: grid;
40 | place-items: center;
41 | inset: 0;
42 | }
43 |
44 | #message p {
45 | border: 1px solid #ddd;
46 | background-color: #fafafa;
47 | padding: 0.25rem 0.75rem;
48 | border-radius: 9999px;
49 | font-size: 13px;
50 | font-weight: 500;
51 | color: #222;
52 | }
53 |
54 | /* We make the container fill the entire screen to accommodate for
55 | the `display: "plain"` case.
56 | */
57 | #markprompt {
58 | position: absolute;
59 | inset: 0;
60 | pointer-events: auto;
61 | }
62 |
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2021",
4 | "skipLibCheck": true,
5 | "strict": true,
6 | "module": "esnext",
7 | "moduleResolution": "bundler",
8 | "isolatedModules": true,
9 | "noEmit": true,
10 | "jsx": "react-jsx"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-automatic-ticket-creation/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 |
3 | export default defineConfig({
4 | optimizeDeps: {
5 | exclude: ['@markprompt/web', '@markprompt/css', '@markprompt/react'],
6 | },
7 | ssr: {
8 | noExternal: ['@markprompt/css'],
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/examples/with-css-modules/README.md:
--------------------------------------------------------------------------------
1 | # `with-css-modules`
2 |
3 | This example contains a reference implementation of
4 | [`@markprompt/react`](../../packages/react/README.md), styled using CSS Modules.
5 |
6 | It also contains an example of using the exported context provider to manage the
7 | state of the prompt, conditionally showing a loading state and references.
8 |
--------------------------------------------------------------------------------
/examples/with-css-modules/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Markprompt + Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/with-css-modules/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-css-modules",
3 | "version": "0.0.1",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "dev": "vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@markprompt/core": "workspace:*",
13 | "@markprompt/react": "workspace:*",
14 | "@radix-ui/react-icons": "^1.3.2",
15 | "@radix-ui/react-visually-hidden": "^1.1.3",
16 | "react": "^19.0.0",
17 | "react-dom": "^19.0.0"
18 | },
19 | "devDependencies": {
20 | "@types/react": "^19.0.1",
21 | "@types/react-dom": "^19.0.2",
22 | "vite": "^6.0.3"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/with-css-modules/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/with-css-modules/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 | color-scheme: light dark;
6 | color: rgb(255 255 255 / 87%);
7 | background-color: #fff;
8 | font-synthesis: none;
9 | text-rendering: optimizelegibility;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | text-size-adjust: 100%;
13 | }
14 |
15 | html {
16 | box-sizing: border-box;
17 | }
18 |
19 | *,
20 | *::before,
21 | *::after {
22 | box-sizing: inherit;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
28 | Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
29 | font-weight: 400;
30 | line-height: 1.6;
31 | min-height: 100vh;
32 | min-height: 100dvh;
33 | }
34 |
35 | body,
36 | #root {
37 | min-height: 100vh;
38 | min-height: 100dvh;
39 | }
40 |
--------------------------------------------------------------------------------
/examples/with-css-modules/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react';
2 | import { createRoot } from 'react-dom/client';
3 |
4 | import App from './App';
5 |
6 | import './index.css';
7 |
8 | const root = createRoot(document.getElementById('root')!);
9 | root.render(
10 |
11 |
12 | ,
13 | );
14 |
--------------------------------------------------------------------------------
/examples/with-css-modules/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/with-css-modules/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2021",
4 | "skipLibCheck": true,
5 | "strict": true,
6 | "module": "esnext",
7 | "moduleResolution": "bundler",
8 | "isolatedModules": true,
9 | "noEmit": true,
10 | "jsx": "react-jsx"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/README.md:
--------------------------------------------------------------------------------
1 | # `with-custom-trigger-react`
2 |
3 | This example contains a reference implementation of
4 | [`@markprompt/react`](../../packages/react/README.md) using a custom trigger button.
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Markprompt + Custom Trigger + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-custom-trigger-react",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "dev": "vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@markprompt/css": "workspace:*",
13 | "@markprompt/react": "workspace:*",
14 | "react": "^19.0.0",
15 | "react-dom": "^19.0.0"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^19.0.1",
19 | "@types/react-dom": "^19.0.2",
20 | "@vitejs/plugin-react-swc": "^3.7.2",
21 | "vite": "^6.0.3"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/src/App.module.css:
--------------------------------------------------------------------------------
1 | .app {
2 | display: grid;
3 | place-items: center;
4 | min-height: 100vh;
5 | min-height: 100dvh;
6 | }
7 |
8 | .centered {
9 | display: grid;
10 | place-items: center;
11 | text-align: center;
12 | }
13 |
14 | .customTrigger {
15 | all: unset;
16 | display: inline-block;
17 | text-transform: uppercase;
18 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
19 | Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
20 | font-size: 0.875rem;
21 | padding: 0.5rem 1rem;
22 | background-color: var(--markprompt-primary);
23 | color: var(--markprompt-primaryForeground);
24 | cursor: pointer;
25 | transition: background 0.2s ease-out, color 0.2s ease-out;
26 | }
27 |
28 | .customTrigger:hover,
29 | .customTrigger:focus,
30 | .customTrigger:active {
31 | background-color: var(--markprompt-primaryForeground);
32 | color: var(--markprompt-primary);
33 | }
34 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/src/App.tsx:
--------------------------------------------------------------------------------
1 | import '@markprompt/css';
2 | import styles from './App.module.css';
3 |
4 | import { Markprompt, openMarkprompt } from '@markprompt/react';
5 | import type { JSX } from 'react';
6 |
7 | function App(): JSX.Element {
8 | return (
9 |
10 |
11 |
Open the Markprompt dialog
12 |
16 |
openMarkprompt()}
20 | >
21 | Open Markprompt
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | export default App;
29 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 | color-scheme: light dark;
6 | color: rgb(255 255 255 / 87%);
7 | background-color: #242424;
8 | font-synthesis: none;
9 | text-rendering: optimizelegibility;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | text-size-adjust: 100%;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react';
2 | import { createRoot } from 'react-dom/client';
3 |
4 | import App from './App';
5 |
6 | import './index.css';
7 |
8 | const root = createRoot(document.getElementById('root')!);
9 |
10 | root.render(
11 |
12 |
13 | ,
14 | );
15 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger-react/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react-swc';
2 | import { defineConfig } from 'vite';
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | });
8 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger/README.md:
--------------------------------------------------------------------------------
1 | # `with-custom-trigger`
2 |
3 | This example contains a reference implementation of
4 | [`@markprompt/web`](../../packages/web/README.md) using a custom trigger button.
--------------------------------------------------------------------------------
/examples/with-custom-trigger/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Markprompt + Custom Trigger
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-custom-trigger",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "dev": "vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@markprompt/css": "workspace:*",
13 | "@markprompt/web": "workspace:*"
14 | },
15 | "devDependencies": {
16 | "@vitejs/plugin-react-swc": "^3.7.2",
17 | "vite": "^6.0.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger/src/main.module.css:
--------------------------------------------------------------------------------
1 | .app {
2 | display: grid;
3 | place-items: center;
4 | min-height: 100vh;
5 | min-height: 100dvh;
6 | }
7 |
8 | .centered {
9 | display: grid;
10 | place-items: center;
11 | text-align: center;
12 | }
13 |
14 | .customTrigger {
15 | all: unset;
16 | display: inline-block;
17 | text-transform: uppercase;
18 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
19 | Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
20 | font-size: 0.875rem;
21 | padding: 0.5rem 1rem;
22 | background-color: var(--markprompt-primary);
23 | color: var(--markprompt-primaryForeground);
24 | cursor: pointer;
25 | transition: background 0.2s ease-out, color 0.2s ease-out;
26 | }
27 |
28 | .customTrigger:hover,
29 | .customTrigger:focus,
30 | .customTrigger:active {
31 | background-color: var(--markprompt-primaryForeground);
32 | color: var(--markprompt-primary);
33 | }
34 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger/src/main.ts:
--------------------------------------------------------------------------------
1 | import '@markprompt/css';
2 | import './style.css';
3 | import { markprompt, openMarkprompt } from '@markprompt/web';
4 |
5 | import styles from './main.module.css';
6 |
7 | document.querySelector('#app')!.innerHTML = `
8 |
9 |
Open the Markprompt dialog
10 |
11 | Open Markprompt
12 |
13 |
14 |
15 | `;
16 |
17 | const el = document.querySelector('#markprompt');
18 |
19 | if (el instanceof HTMLElement) {
20 | markprompt(import.meta.env.VITE_PROJECT_API_KEY, el, {
21 | trigger: { customElement: true },
22 | search: { enabled: false },
23 | });
24 | }
25 |
26 | const trigger = document.querySelector(
27 | '#markprompt-trigger',
28 | );
29 |
30 | if (trigger) {
31 | trigger.addEventListener('click', () => {
32 | openMarkprompt();
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger/src/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 | color-scheme: light dark;
6 | color: rgb(255 255 255 / 87%);
7 | background-color: #242424;
8 | font-synthesis: none;
9 | text-rendering: optimizelegibility;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | text-size-adjust: 100%;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/with-custom-trigger/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ESNext", "DOM"],
7 | "moduleResolution": "Node",
8 | "strict": true,
9 | "resolveJsonModule": true,
10 | "isolatedModules": true,
11 | "esModuleInterop": true,
12 | "noEmit": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noImplicitReturns": true,
16 | "skipLibCheck": true
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/with-custom-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Markprompt Custom UI Demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/with-custom-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-custom-ui",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "dev": "vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@markprompt/css": "workspace:*",
13 | "@markprompt/react": "workspace:*",
14 | "@markprompt/web": "workspace:*",
15 | "react": "^19.0.0",
16 | "react-dom": "^19.0.0",
17 | "react-markdown": "^9.0.1"
18 | },
19 | "devDependencies": {
20 | "@types/react": "^19.0.1",
21 | "@types/react-dom": "^19.0.2",
22 | "@vitejs/plugin-react": "^4.2.0",
23 | "vite": "^6.0.3"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/with-custom-ui/public/avatars/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-custom-ui/public/avatars/logo.png
--------------------------------------------------------------------------------
/examples/with-custom-ui/public/avatars/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-custom-ui/public/avatars/user.png
--------------------------------------------------------------------------------
/examples/with-custom-ui/public/markprompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-custom-ui/public/markprompt.png
--------------------------------------------------------------------------------
/examples/with-custom-ui/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/with-custom-ui/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/with-custom-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2021",
4 | "skipLibCheck": true,
5 | "strict": true,
6 | "module": "esnext",
7 | "moduleResolution": "bundler",
8 | "isolatedModules": true,
9 | "noEmit": true,
10 | "jsx": "react-jsx"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-custom-ui/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | optimizeDeps: {
7 | exclude: ['@markprompt/web', '@markprompt/css', '@markprompt/react'],
8 | },
9 | ssr: {
10 | noExternal: ['@markprompt/css'],
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/docs/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Tutorial Intro
6 |
7 | Let's discover **Docusaurus in less than 5 minutes**.
8 |
9 | ## Getting Started
10 |
11 | Get started by **creating a new site**.
12 |
13 | Or **try Docusaurus immediately** with
14 | **[docusaurus.new](https://docusaurus.new)**.
15 |
16 | ### What you'll need
17 |
18 | - [Node.js](https://nodejs.org/en/download/) version 16.14 or above:
19 | - When installing Node.js, you are recommended to check all checkboxes related
20 | to dependencies.
21 |
22 | ## Generate a new site
23 |
24 | Generate a new Docusaurus site using the **classic template**.
25 |
26 | The classic template will automatically be added to your project after you run
27 | the command:
28 |
29 | ```bash
30 | npm init docusaurus@latest my-website classic
31 | ```
32 |
33 | You can type this command into Command Prompt, Powershell, Terminal, or any
34 | other integrated terminal of your code editor.
35 |
36 | The command also installs all necessary dependencies you need to run Docusaurus.
37 |
38 | ## Start your site
39 |
40 | Run the development server:
41 |
42 | ```bash
43 | cd my-website
44 | npm run start
45 | ```
46 |
47 | The `cd` command changes the directory you're working with. In order to work
48 | with your newly created Docusaurus site, you'll need to navigate the terminal
49 | there.
50 |
51 | The `npm run start` command builds your website locally and serves it through a
52 | development server, ready for you to view at http://localhost:3000/.
53 |
54 | Open `docs/intro.md` (this page) and edit some lines: the site **reloads
55 | automatically** and displays your changes.
56 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-docusaurus-algolia",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "build": "docusaurus build",
7 | "clear": "docusaurus clear",
8 | "deploy": "docusaurus deploy",
9 | "docusaurus": "docusaurus",
10 | "serve": "docusaurus serve",
11 | "start": "docusaurus start",
12 | "swizzle": "docusaurus swizzle",
13 | "typecheck": "tsc",
14 | "write-heading-ids": "docusaurus write-heading-ids",
15 | "write-translations": "docusaurus write-translations"
16 | },
17 | "browserslist": {
18 | "production": [">0.5%", "not dead", "not op_mini all"],
19 | "development": [
20 | "last 1 chrome version",
21 | "last 1 firefox version",
22 | "last 1 safari version"
23 | ]
24 | },
25 | "dependencies": {
26 | "@algolia/client-search": "^4.23.3",
27 | "@docusaurus/core": "^3.2.1",
28 | "@docusaurus/module-type-aliases": "^3.2.1",
29 | "@docusaurus/preset-classic": "^3.2.1",
30 | "@markprompt/docusaurus-theme-search": "workspace:*",
31 | "@mdx-js/react": "^3.1.0",
32 | "acorn": "^8.14.0",
33 | "prism-react-renderer": "^2.3.1",
34 | "react": "^19.0.0",
35 | "react-dom": "^19.0.0",
36 | "search-insights": "^2.17.3"
37 | },
38 | "devDependencies": {
39 | "@docusaurus/tsconfig": "^3.2.1",
40 | "dotenv": "^16.4.5"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/sidebars.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
2 | const sidebars = {
3 | tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
4 | };
5 |
6 | module.exports = sidebars;
7 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/src/markprompt-config.js:
--------------------------------------------------------------------------------
1 | // Custom link mapping functions: https://markprompt.com/docs#link-mapping
2 | if (typeof window !== 'undefined') {
3 | window.markpromptConfigExtras = {
4 | references: {
5 | // Example link mapping for references:
6 | // getHref: (reference) => reference.file?.path?.replace(/\.[^.]+$/, ''),
7 | // getLabel: (reference) => reference.meta?.leadHeading?.value || reference.file?.title,
8 | },
9 | search: {
10 | // Example link mapping for search results (e.g. Algolia):
11 | getHref: (result) => result.url,
12 | getHeading: (result) => result.hierarchy?.lvl0,
13 | getTitle: (result) => result.hierarchy?.lvl1,
14 | getSubtitle: (result) => result.hierarchy?.lvl2,
15 | },
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/src/pages/index.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | margin: 0 auto;
3 | max-width: 80ch;
4 | padding: 2em 0;
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import styles from './index.module.css';
2 | import type { JSX } from 'react';
3 | import Layout from '@theme/Layout';
4 |
5 | export default function Home(): JSX.Element {
6 | return (
7 |
8 |
9 | Markprompt + Algolia demo
10 |
11 | This is the demo of the{' '}
12 | @markprompt/docusaurus-theme-search
plugin.
13 |
14 | Click the button at the bottom right to open Markprompt.
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/src/pages/markdown-page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Markdown page example
3 | ---
4 |
5 | # Markdown page example
6 |
7 | You don't need React to write simple standalone pages.
8 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/static/img/docusaurus-social-card.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-docusaurus-algolia/static/img/docusaurus-social-card.jpg
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/static/img/docusaurus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-docusaurus-algolia/static/img/docusaurus.png
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-docusaurus-algolia/static/img/favicon.ico
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-algolia/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@docusaurus/tsconfig",
3 | "references": [{ "path": "../../packages/docusaurus-theme-search" }],
4 | "compilerOptions": {
5 | "baseUrl": ".",
6 | "paths": {
7 | "@site/*": ["./*"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # with-docusaurus-swizzled
2 |
3 | ## 0.0.3
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies
8 | [[`b607149`](https://github.com/markprompt/markprompt-js/commit/b60714904c2481da40801e16acc2a3c4b0717f85),
9 | [`b607149`](https://github.com/markprompt/markprompt-js/commit/b60714904c2481da40801e16acc2a3c4b0717f85),
10 | [`54af915`](https://github.com/markprompt/markprompt-js/commit/54af9150ea22da96ec4cf3d283d6d8a485696a06),
11 | [`54af915`](https://github.com/markprompt/markprompt-js/commit/54af9150ea22da96ec4cf3d283d6d8a485696a06),
12 | [`54af915`](https://github.com/markprompt/markprompt-js/commit/54af9150ea22da96ec4cf3d283d6d8a485696a06)]:
13 | - @markprompt/css@0.4.0
14 | - @markprompt/docusaurus-theme-search@0.4.0
15 | - @markprompt/react@0.7.0
16 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/README.md:
--------------------------------------------------------------------------------
1 | # Example with Docusaurus swizzled search plugin
2 |
3 | This example shows you how to use Markprompt in
4 | [Docusaurus](https://docusaurus.io/) while using another theme search plugin,
5 | such as
6 | [theme-search-algolia](https://docusaurus.io/docs/api/themes/@docusaurus/theme-search-algolia).
7 |
8 | ## Installation
9 |
10 | ```
11 | $ npm install
12 | ```
13 |
14 | ## Local development
15 |
16 | ```
17 | $ npm start
18 | ```
19 |
20 | This command starts a local development server. Most changes are reflected live
21 | without having to restart the server.
22 |
23 | ## Notes
24 |
25 | If you don't have an existing search plugin installed in your Docusaurus
26 | project, you can use the Markprompt Docusaurus plugin without swizzling. Please
27 | refer to the
28 | [Docusaurus example](https://github.com/markprompt/markprompt-js/tree/main/examples/with-docusaurus)
29 | for a simpler configuration-based setup using the
30 | `@markprompt/docusaurus-theme-search` plugin.
31 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/docs/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Tutorial Intro
6 |
7 | Let's discover **Docusaurus in less than 5 minutes**.
8 |
9 | ## Getting Started
10 |
11 | Get started by **creating a new site**.
12 |
13 | Or **try Docusaurus immediately** with
14 | **[docusaurus.new](https://docusaurus.new)**.
15 |
16 | ### What you'll need
17 |
18 | - [Node.js](https://nodejs.org/en/download/) version 16.14 or above:
19 | - When installing Node.js, you are recommended to check all checkboxes related
20 | to dependencies.
21 |
22 | ## Generate a new site
23 |
24 | Generate a new Docusaurus site using the **classic template**.
25 |
26 | The classic template will automatically be added to your project after you run
27 | the command:
28 |
29 | ```bash
30 | npm init docusaurus@latest my-website classic
31 | ```
32 |
33 | You can type this command into Command Prompt, Powershell, Terminal, or any
34 | other integrated terminal of your code editor.
35 |
36 | The command also installs all necessary dependencies you need to run Docusaurus.
37 |
38 | ## Start your site
39 |
40 | Run the development server:
41 |
42 | ```bash
43 | cd my-website
44 | npm run start
45 | ```
46 |
47 | The `cd` command changes the directory you're working with. In order to work
48 | with your newly created Docusaurus site, you'll need to navigate the terminal
49 | there.
50 |
51 | The `npm run start` command builds your website locally and serves it through a
52 | development server, ready for you to view at http://localhost:3000/.
53 |
54 | Open `docs/intro.md` (this page) and edit some lines: the site **reloads
55 | automatically** and displays your changes.
56 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/docusaurus.config.ts:
--------------------------------------------------------------------------------
1 | import type * as Preset from '@docusaurus/preset-classic';
2 | import type { Config } from '@docusaurus/types';
3 | import type { ThemeConfig as MarkpromptThemeConfig } from '@markprompt/docusaurus-theme-search';
4 | import dotenv from 'dotenv';
5 | import { themes } from 'prism-react-renderer';
6 |
7 | dotenv.config();
8 |
9 | const config = {
10 | title: 'Markprompt with swizzling',
11 | tagline: 'Markprompt with swizzling',
12 | favicon: 'img/favicon.ico',
13 |
14 | // Set the production url of your site here
15 | url: 'https://your-docusaurus-test-site.com',
16 | // Set the // pathname under which your site is served
17 | // For GitHub pages deployment, it is often '//'
18 | baseUrl: '/',
19 |
20 | presets: [
21 | [
22 | 'classic',
23 | {
24 | theme: {
25 | customCss: require.resolve('./src/css/custom.css'),
26 | },
27 | } satisfies Preset.Options,
28 | ],
29 | ],
30 |
31 | themeConfig: {
32 | // Replace with your project's social card
33 | image: 'img/docusaurus-social-card.jpg',
34 | markprompt: {
35 | // Set the project key here, on in a `.env` file. You can obtain
36 | // the project key in the Markprompt dashboard, under
37 | // project settings.
38 | projectKey: 'YOUR-PROJECT-KEY',
39 | trigger: { floating: false },
40 | chat: {
41 | systemPrompt: 'You are a friendly AI who loves to help people.',
42 | },
43 | search: { enabled: true },
44 | },
45 | navbar: {
46 | title: 'Markprompt + Algolia demo',
47 | logo: {
48 | alt: 'My Site Logo',
49 | src: 'img/logo.svg',
50 | },
51 | items: [
52 | {
53 | href: 'https://github.com/markprompt/markprompt-js/blob/main/examples/with-docusaurus-swizzled',
54 | label: 'GitHub',
55 | position: 'right',
56 | },
57 | ],
58 | },
59 | footer: {
60 | style: 'light',
61 | copyright: `Copyright © ${new Date().getFullYear()} Markprompt. Built with Docusaurus.`,
62 | },
63 | prism: {
64 | theme: themes.github,
65 | darkTheme: themes.dracula,
66 | },
67 | } satisfies Preset.ThemeConfig & MarkpromptThemeConfig,
68 | } satisfies Config;
69 |
70 | module.exports = config;
71 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-docusaurus-swizzled",
3 | "version": "0.0.3",
4 | "private": true,
5 | "scripts": {
6 | "build": "docusaurus build",
7 | "clear": "docusaurus clear",
8 | "deploy": "docusaurus deploy",
9 | "docusaurus": "docusaurus",
10 | "serve": "docusaurus serve",
11 | "start": "docusaurus start",
12 | "swizzle": "docusaurus swizzle",
13 | "typecheck": "tsc",
14 | "write-heading-ids": "docusaurus write-heading-ids",
15 | "write-translations": "docusaurus write-translations"
16 | },
17 | "browserslist": {
18 | "production": [">0.5%", "not dead", "not op_mini all"],
19 | "development": [
20 | "last 1 chrome version",
21 | "last 1 firefox version",
22 | "last 1 safari version"
23 | ]
24 | },
25 | "dependencies": {
26 | "@docusaurus/core": "^3.2.1",
27 | "@docusaurus/module-type-aliases": "^3.2.1",
28 | "@docusaurus/preset-classic": "^3.2.1",
29 | "@docusaurus/theme-search-algolia": "^3.2.1",
30 | "@markprompt/css": "workspace:*",
31 | "@markprompt/docusaurus-theme-search": "workspace:*",
32 | "@markprompt/react": "workspace:*",
33 | "@mdx-js/react": "^3.1.0",
34 | "acorn": "^8.14.0",
35 | "prism-react-renderer": "^2.3.1",
36 | "react": "^19.0.0",
37 | "react-dom": "^19.0.0"
38 | },
39 | "devDependencies": {
40 | "@docusaurus/tsconfig": "^3.2.1",
41 | "dotenv": "^16.4.5"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/sidebars.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
2 | const sidebars = {
3 | tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
4 | };
5 |
6 | module.exports = sidebars;
7 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/src/pages/index.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | margin: 0 auto;
3 | max-width: 80ch;
4 | padding: 2em 0;
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import styles from './index.module.css';
2 | import type { JSX } from 'react';
3 | import Layout from '@theme/Layout';
4 |
5 | export default function Home(): JSX.Element {
6 | return (
7 |
8 |
9 | Markprompt + Algolia demo with swizzling
10 | This demo features Markprompt with Algolia as a swizzled plugin.
11 | Click the search bar at the top right to open Markprompt.
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/src/pages/markdown-page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Markdown page example
3 | ---
4 |
5 | # Markdown page example
6 |
7 | You don't need React to write simple standalone pages.
8 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/src/theme/SearchBar/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import '@markprompt/css';
3 |
4 | import type { WrapperProps } from '@docusaurus/types';
5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
6 | import type { MarkpromptConfig } from '@markprompt/docusaurus-theme-search';
7 | import type SearchBarType from '@theme/SearchBar';
8 | import SearchBar from '@theme-original/SearchBar';
9 | import { Suspense, lazy, type JSX } from 'react';
10 |
11 | // import Markprompt lazily as Docusaurus does not currently support ESM
12 | const Markprompt = lazy(() => {
13 | return import('@markprompt/react').then((mod) => ({
14 | default: mod.Markprompt,
15 | }));
16 | });
17 |
18 | type Props = WrapperProps;
19 |
20 | export default function SearchBarWrapper(props: Props): JSX.Element {
21 | const { siteConfig } = useDocusaurusContext();
22 | const { projectKey, ...config } = siteConfig.themeConfig
23 | .markprompt as MarkpromptConfig;
24 |
25 | return (
26 |
27 | {/* Docusaurus' version of `ReactDOMServer` doesn't support Suspense yet, so we can only render the component on the client. */}
28 | {typeof window !== 'undefined' && (
29 |
30 |
31 |
32 | )}
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/static/img/docusaurus-social-card.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-docusaurus-swizzled/static/img/docusaurus-social-card.jpg
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/static/img/docusaurus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-docusaurus-swizzled/static/img/docusaurus.png
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-docusaurus-swizzled/static/img/favicon.ico
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/with-docusaurus-swizzled/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@docusaurus/tsconfig",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@site/*": ["./*"]
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/docs/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Tutorial Intro
6 |
7 | Let's discover **Docusaurus in less than 5 minutes**.
8 |
9 | ## Getting Started
10 |
11 | Get started by **creating a new site**.
12 |
13 | Or **try Docusaurus immediately** with
14 | **[docusaurus.new](https://docusaurus.new)**.
15 |
16 | ### What you'll need
17 |
18 | - [Node.js](https://nodejs.org/en/download/) version 16.14 or above:
19 | - When installing Node.js, you are recommended to check all checkboxes related
20 | to dependencies.
21 |
22 | ## Generate a new site
23 |
24 | Generate a new Docusaurus site using the **classic template**.
25 |
26 | The classic template will automatically be added to your project after you run
27 | the command:
28 |
29 | ```bash
30 | npm init docusaurus@latest my-website classic
31 | ```
32 |
33 | You can type this command into Command Prompt, Powershell, Terminal, or any
34 | other integrated terminal of your code editor.
35 |
36 | The command also installs all necessary dependencies you need to run Docusaurus.
37 |
38 | ## Start your site
39 |
40 | Run the development server:
41 |
42 | ```bash
43 | cd my-website
44 | npm run start
45 | ```
46 |
47 | The `cd` command changes the directory you're working with. In order to work
48 | with your newly created Docusaurus site, you'll need to navigate the terminal
49 | there.
50 |
51 | The `npm run start` command builds your website locally and serves it through a
52 | development server, ready for you to view at http://localhost:3000/.
53 |
54 | Open `docs/intro.md` (this page) and edit some lines: the site **reloads
55 | automatically** and displays your changes.
56 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-docusaurus",
3 | "version": "0.0.2",
4 | "private": true,
5 | "scripts": {
6 | "build": "docusaurus build",
7 | "clear": "docusaurus clear",
8 | "deploy": "docusaurus deploy",
9 | "docusaurus": "docusaurus",
10 | "serve": "docusaurus serve",
11 | "start": "docusaurus start",
12 | "swizzle": "docusaurus swizzle",
13 | "typecheck": "tsc",
14 | "write-heading-ids": "docusaurus write-heading-ids",
15 | "write-translations": "docusaurus write-translations"
16 | },
17 | "browserslist": {
18 | "production": [">0.5%", "not dead", "not op_mini all"],
19 | "development": [
20 | "last 1 chrome version",
21 | "last 1 firefox version",
22 | "last 1 safari version"
23 | ]
24 | },
25 | "dependencies": {
26 | "@algolia/client-search": "^4.23.3",
27 | "@docusaurus/core": "^3.2.1",
28 | "@docusaurus/module-type-aliases": "^3.2.1",
29 | "@docusaurus/preset-classic": "^3.2.1",
30 | "@markprompt/docusaurus-theme-search": "workspace:*",
31 | "@mdx-js/react": "^3.1.0",
32 | "acorn": "^8.14.0",
33 | "prism-react-renderer": "^2.3.1",
34 | "react": "^19.0.0",
35 | "react-dom": "^19.0.0",
36 | "search-insights": "^2.17.3"
37 | },
38 | "devDependencies": {
39 | "@docusaurus/tsconfig": "^3.2.1",
40 | "dotenv": "^16.4.5"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/sidebars.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
2 | const sidebars = {
3 | tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
4 | };
5 |
6 | module.exports = sidebars;
7 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/src/markprompt-config.js:
--------------------------------------------------------------------------------
1 | // Custom link mapping functions: https://markprompt.com/docs#link-mapping
2 | if (typeof window !== 'undefined') {
3 | window.markpromptConfigExtras = {
4 | references: {
5 | // Example link mapping for references:
6 | // getHref: (reference) => reference.file?.path?.replace(/\.[^.]+$/, ''),
7 | // getLabel: (reference) => reference.meta?.leadHeading?.value || reference.file?.title,
8 | },
9 | search: {
10 | // Example link mapping for search results (e.g. Algolia):
11 | // getHref: (result) => result.url,
12 | // getHeading: (result) => result.hierarchy?.lvl0,
13 | // getTitle: (result) => result.hierarchy?.lvl1,
14 | // getSubtitle: (result) => result.hierarchy?.lvl2,
15 | },
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/src/pages/index.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | margin: 0 auto;
3 | max-width: 80ch;
4 | padding: 2em 0;
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Layout from '@theme/Layout';
2 | import type React from 'react';
3 |
4 | import styles from './index.module.css';
5 |
6 | export default function Home(): React.JSX.Element {
7 | return (
8 |
9 |
10 | Markprompt demo
11 |
12 | This is the demo of the{' '}
13 | @markprompt/docusaurus-theme-search
plugin.
14 |
15 | Click the button at the bottom right to open Markprompt.
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/src/pages/markdown-page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Markdown page example
3 | ---
4 |
5 | # Markdown page example
6 |
7 | You don't need React to write simple standalone pages.
8 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/static/img/docusaurus-social-card.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-docusaurus/static/img/docusaurus-social-card.jpg
--------------------------------------------------------------------------------
/examples/with-docusaurus/static/img/docusaurus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-docusaurus/static/img/docusaurus.png
--------------------------------------------------------------------------------
/examples/with-docusaurus/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-docusaurus/static/img/favicon.ico
--------------------------------------------------------------------------------
/examples/with-docusaurus/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/with-docusaurus/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@docusaurus/tsconfig",
3 | "references": [{ "path": "../../packages/docusaurus-theme-search" }],
4 | "compilerOptions": {
5 | "baseUrl": ".",
6 | "paths": {
7 | "@site/*": ["./*"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/with-embed/chatbot.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Embed Demo | Markprompt
4 |
10 |
11 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/with-embed/embed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Embed Demo | Markprompt
4 |
19 |
20 |
29 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/with-function-calling/README.md:
--------------------------------------------------------------------------------
1 | # `with-next`
2 |
3 | This example contains a reference implementation of
4 | [`@markprompt/react`](../../packages/react/README.md) with
5 | [Next.js](https://nextjs.org), and showcases the use for [OpenAI function calling](https://platform.openai.com/docs/guides/function-calling).
--------------------------------------------------------------------------------
/examples/with-function-calling/example.env:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_PROJECT_API_KEY=
2 | NEXT_PUBLIC_MARKPROMPT_API_URL=
--------------------------------------------------------------------------------
/examples/with-function-calling/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/with-function-calling/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-function-calling",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "next build",
8 | "dev": "next dev",
9 | "start": "next start"
10 | },
11 | "dependencies": {
12 | "@markprompt/core": "workspace:*",
13 | "@markprompt/css": "workspace:*",
14 | "@radix-ui/react-icons": "^1.3.2",
15 | "@radix-ui/react-visually-hidden": "^1.1.3",
16 | "classnames": "^2.5.1",
17 | "next": "15.1.0",
18 | "openai": "^4.80.1",
19 | "react": "^19.0.0",
20 | "react-dom": "^19.0.0"
21 | },
22 | "devDependencies": {
23 | "@types/react": "^19.0.1",
24 | "@types/react-dom": "^19.0.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/with-function-calling/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '@markprompt/css';
2 | import './global.css';
3 |
4 | import type { AppProps } from 'next/app';
5 | import type { JSX } from 'react';
6 |
7 | export default function App({ Component, pageProps }: AppProps): JSX.Element {
8 | return ;
9 | }
10 |
--------------------------------------------------------------------------------
/examples/with-function-calling/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/with-function-calling/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "eslint.config.mjs"],
3 | "exclude": ["**/node_modules", ".next/"],
4 | "compilerOptions": {
5 | "allowJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "incremental": true,
9 | "isolatedModules": true,
10 | "jsx": "preserve",
11 | "lib": ["dom", "dom.iterable", "esnext"],
12 | "module": "esnext",
13 | "moduleResolution": "bundler",
14 | "noEmit": true,
15 | "resolveJsonModule": true,
16 | "skipLibCheck": true,
17 | "strict": true,
18 | "target": "es2021"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/with-init/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/with-init/README.md:
--------------------------------------------------------------------------------
1 | # `with-init`
2 |
3 | This example contains a reference implementation of
4 | [`@markprompt/web`](../../packages/web/README.md), using a global instance of Markprompt.
--------------------------------------------------------------------------------
/examples/with-init/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Markprompt Init
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/with-init/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-init",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "dev": "vite",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^6.0.3"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/with-init/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/with-init/src/main.ts:
--------------------------------------------------------------------------------
1 | import './style.css';
2 |
3 | // @ts-expect-error - Markprompt types have not been included here
4 | window.markprompt = {
5 | projectKey: import.meta.env.VITE_PROJECT_API_KEY,
6 | options: {
7 | chat: {
8 | assistantId: import.meta.env.VITE_ASSISTANT_ID,
9 | },
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/examples/with-init/src/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 | color-scheme: light dark;
6 | color: rgb(255 255 255 / 87%);
7 | background-color: #fff;
8 | font-synthesis: none;
9 | text-rendering: optimizelegibility;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | text-size-adjust: 100%;
13 | }
14 |
15 | body {
16 | margin: 0;
17 | display: flex;
18 | place-items: center;
19 | min-width: 320px;
20 | min-height: 100vh;
21 | }
22 |
23 | #app {
24 | max-width: 1280px;
25 | margin: 0 auto;
26 | padding: 2rem;
27 | text-align: center;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/with-init/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/with-init/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ESNext", "DOM"],
7 | "moduleResolution": "bundler",
8 | "strict": true,
9 | "resolveJsonModule": true,
10 | "isolatedModules": true,
11 | "esModuleInterop": true,
12 | "noEmit": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noImplicitReturns": true,
16 | "skipLibCheck": true
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/with-markprompt-web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Markprompt Web Component
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Click the chat button
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/with-markprompt-web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-markprompt-web",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "dev": "vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@markprompt/css": "workspace:*",
13 | "@markprompt/web": "workspace:*",
14 | "preact": "^10.25.2"
15 | },
16 | "devDependencies": {
17 | "vite": "^6.0.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/with-markprompt-web/public/avatars/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-markprompt-web/public/avatars/logo.png
--------------------------------------------------------------------------------
/examples/with-markprompt-web/public/avatars/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-markprompt-web/public/avatars/user.png
--------------------------------------------------------------------------------
/examples/with-markprompt-web/public/markprompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-markprompt-web/public/markprompt.png
--------------------------------------------------------------------------------
/examples/with-markprompt-web/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/with-markprompt-web/src/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 | color-scheme: light dark;
6 | color: #737373;
7 | background-color: #f5f5f5;
8 | font-synthesis: none;
9 | text-rendering: optimizelegibility;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | text-size-adjust: 100%;
13 | }
14 |
15 | body {
16 | margin: 0;
17 | }
18 |
19 | button {
20 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
21 | }
22 |
23 | body,
24 | #app {
25 | min-height: 100vh;
26 | min-height: 100dvh;
27 | }
28 |
29 | main {
30 | position: relative;
31 | display: grid;
32 | place-items: center;
33 | width: 100%;
34 | height: 100vh;
35 | }
36 |
37 | #message {
38 | position: absolute;
39 | display: grid;
40 | place-items: center;
41 | inset: 0;
42 | }
43 |
44 | #message p {
45 | border: 1px solid #ddd;
46 | background-color: #fafafa;
47 | padding: 0.25rem 0.75rem;
48 | border-radius: 9999px;
49 | font-size: 13px;
50 | font-weight: 500;
51 | color: #222;
52 | }
53 |
54 | /* We make the container fill the entire screen to accommodate for
55 | the `display: "plain"` case.
56 | */
57 | #markprompt {
58 | position: absolute;
59 | inset: 0;
60 | pointer-events: auto;
61 | }
62 |
--------------------------------------------------------------------------------
/examples/with-markprompt-web/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/with-markprompt-web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2021",
4 | "skipLibCheck": true,
5 | "strict": true,
6 | "module": "esnext",
7 | "moduleResolution": "bundler",
8 | "isolatedModules": true,
9 | "noEmit": true,
10 | "jsx": "react-jsx"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-markprompt-web/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 |
3 | export default defineConfig({
4 | optimizeDeps: {
5 | exclude: ['@markprompt/web', '@markprompt/css', '@markprompt/react'],
6 | },
7 | ssr: {
8 | noExternal: ['@markprompt/css'],
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/examples/with-next/README.md:
--------------------------------------------------------------------------------
1 | # `with-next`
2 |
3 | This example contains a reference implementation of
4 | [`@markprompt/react`](../../packages/react/README.md) with
5 | [Next.js](https://nextjs.org), and featuring [Algolia](https://algolia.com/) docs search.
6 |
7 |
8 |
9 | https://github.com/markprompt/markprompt-js/assets/504893/6b9809d7-2ac7-44a3-9ae8-8b017441da7d
10 |
11 |
12 |
13 | ## Getting Started
14 |
15 | Create a `.env.local` file at the root of the project, and add the following:
16 |
17 | ```
18 | NEXT_PUBLIC_MARKPROMPT_PROJECT_KEY=
19 | NEXT_PUBLIC_ALGOLIA_API_KEY=
20 | NEXT_PUBLIC_ALGOLIA_APP_ID=
21 | NEXT_PUBLIC_ALGOLIA_INDEX_NAME=
22 | ```
23 |
24 | Install dependencies and start the local dev server:
25 |
26 | ```sh
27 | npm install
28 | npm run dev
29 | ```
30 |
31 | Open http://localhost:3000 and see the result.
32 |
33 | ## Documentation
34 |
35 | To use the Markprompt platform as is, please refer to the
36 | [Markprompt documentation](https://markprompt.com/docs).
37 |
38 | ## Community
39 |
40 | - [X](https://x.com/markprompt)
41 |
42 | ## Authors
43 |
44 | This library is created by the team behind [Markprompt](https://markprompt.com)
45 | ([@markprompt](https://x.com/markprompt)).
46 |
--------------------------------------------------------------------------------
/examples/with-next/components/icons.tsx:
--------------------------------------------------------------------------------
1 | import type { ComponentPropsWithoutRef, JSX } from 'react';
2 |
3 | const SearchIcon = (props: ComponentPropsWithoutRef<'svg'>): JSX.Element => (
4 | // biome-ignore lint/a11y/noSvgWithoutTitle: label/hidden via props
5 |
12 |
17 |
18 | );
19 |
20 | export { SearchIcon };
21 |
--------------------------------------------------------------------------------
/examples/with-next/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/with-next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-next",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "next build",
8 | "dev": "next dev",
9 | "start": "next start"
10 | },
11 | "dependencies": {
12 | "@markprompt/css": "workspace:*",
13 | "@markprompt/react": "workspace:*",
14 | "@radix-ui/react-icons": "^1.3.2",
15 | "@radix-ui/react-visually-hidden": "^1.1.3",
16 | "classnames": "^2.5.1",
17 | "next": "15.1.0",
18 | "react": "^19.0.0",
19 | "react-dom": "^19.0.0"
20 | },
21 | "devDependencies": {
22 | "@types/react": "^19.0.1",
23 | "@types/react-dom": "^19.0.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/with-next/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '@markprompt/css';
2 | import './global.css';
3 |
4 | import type { AppProps } from 'next/app';
5 | import type { JSX } from 'react';
6 |
7 | export default function App({ Component, pageProps }: AppProps): JSX.Element {
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/with-next/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document';
2 | import Script from 'next/script';
3 | import type { JSX } from 'react';
4 |
5 | export default function Document(): JSX.Element {
6 | return (
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/examples/with-next/public/avatars/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-next/public/avatars/logo.png
--------------------------------------------------------------------------------
/examples/with-next/public/avatars/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-next/public/avatars/user.png
--------------------------------------------------------------------------------
/examples/with-next/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/with-next/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "eslint.config.mjs"],
3 | "exclude": ["**/node_modules", ".next/"],
4 | "compilerOptions": {
5 | "allowJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "incremental": true,
9 | "isolatedModules": true,
10 | "jsx": "preserve",
11 | "lib": ["dom", "dom.iterable", "esnext"],
12 | "module": "esnext",
13 | "moduleResolution": "bundler",
14 | "noEmit": true,
15 | "resolveJsonModule": true,
16 | "skipLibCheck": true,
17 | "strict": true,
18 | "target": "es2021"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/with-standalone-ticket-deflection/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { configs } from '@markprompt/eslint-config';
2 |
3 | export default [...configs.base(import.meta.dirname), ...configs.react];
4 |
--------------------------------------------------------------------------------
/examples/with-standalone-ticket-deflection/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + TS
8 |
9 |
10 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/with-standalone-ticket-deflection/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-standalone-ticket-deflection",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "dev": "vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@markprompt/css": "workspace:*",
13 | "@markprompt/web": "workspace:*"
14 | },
15 | "devDependencies": {
16 | "vite": "^6.0.3"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/with-standalone-ticket-deflection/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/with-standalone-ticket-deflection/src/main.ts:
--------------------------------------------------------------------------------
1 | import '@markprompt/css';
2 | import './style.css';
3 |
4 | import { ticketDeflectionForm } from '@markprompt/web';
5 |
6 | const el = document.querySelector('#ticket-deflection-form');
7 |
8 | const renderTicketDeflectionForm = (): void => {
9 | if (!el || !(el instanceof HTMLElement)) return;
10 | ticketDeflectionForm(el, {
11 | projectKey: import.meta.env.VITE_MARKPROMPT_PROJECT_KEY,
12 | apiUrl: 'http://api.localhost:3000',
13 | });
14 | };
15 |
16 | renderTicketDeflectionForm();
17 |
--------------------------------------------------------------------------------
/examples/with-standalone-ticket-deflection/src/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 | color-scheme: light dark;
6 | background-color: oklch(98% 0 0deg);
7 | color: oklch(55.5% 0 0deg);
8 | font-synthesis: none;
9 | text-rendering: optimizelegibility;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | body {
15 | margin: 0;
16 | min-width: 100dvw;
17 | min-height: 100dvh;
18 | }
19 |
20 | #app {
21 | min-height: 100dvh;
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | padding: 2vh 2vw;
26 | }
27 |
28 | #ticket-deflection-form {
29 | flex: 1 1 auto;
30 | max-width: 37.5rem;
31 | }
32 |
--------------------------------------------------------------------------------
/examples/with-standalone-ticket-deflection/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/with-standalone-ticket-deflection/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "checkJs": true,
4 | "target": "ESNext",
5 | "useDefineForClassFields": true,
6 | "module": "ESNext",
7 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": true,
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": [".", "eslint.config.js"],
24 | "exclude": ["node_modules", "dist"]
25 | }
26 |
--------------------------------------------------------------------------------
/examples/with-ticket-creation/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Markprompt Web Component
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Click the chat button
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/with-ticket-creation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-ticket-creation",
3 | "version": "0.0.22",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "dev": "vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@markprompt/css": "workspace:*",
13 | "@markprompt/web": "workspace:*",
14 | "preact": "^10.25.2",
15 | "randomuuid": "^1.0.1"
16 | },
17 | "devDependencies": {
18 | "vite": "^6.0.3"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/with-ticket-creation/public/avatars/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-ticket-creation/public/avatars/logo.png
--------------------------------------------------------------------------------
/examples/with-ticket-creation/public/avatars/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-ticket-creation/public/avatars/user.png
--------------------------------------------------------------------------------
/examples/with-ticket-creation/public/markprompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markprompt/markprompt-js/e14063b3370ca23da2409f7d9d4a151d866dd1d1/examples/with-ticket-creation/public/markprompt.png
--------------------------------------------------------------------------------
/examples/with-ticket-creation/public/markprompt.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/with-ticket-creation/src/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 | color-scheme: light dark;
6 | color: #737373;
7 | background-color: #f5f5f5;
8 | font-synthesis: none;
9 | text-rendering: optimizelegibility;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | text-size-adjust: 100%;
13 | }
14 |
15 | body {
16 | margin: 0;
17 | }
18 |
19 | button {
20 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
21 | }
22 |
23 | body,
24 | #app {
25 | min-height: 100vh;
26 | min-height: 100dvh;
27 | }
28 |
29 | main {
30 | position: relative;
31 | display: grid;
32 | place-items: center;
33 | width: 100%;
34 | height: 100vh;
35 | }
36 |
37 | #message {
38 | position: absolute;
39 | display: grid;
40 | place-items: center;
41 | inset: 0;
42 | }
43 |
44 | #message p {
45 | border: 1px solid #ddd;
46 | background-color: #fafafa;
47 | padding: 0.25rem 0.75rem;
48 | border-radius: 9999px;
49 | font-size: 13px;
50 | font-weight: 500;
51 | color: #222;
52 | }
53 |
54 | /* We make the container fill the entire screen to accommodate for
55 | the `display: "plain"` case.
56 | */
57 | #markprompt {
58 | position: absolute;
59 | inset: 0;
60 | pointer-events: auto;
61 | }
62 |
--------------------------------------------------------------------------------
/examples/with-ticket-creation/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/with-ticket-creation/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2021",
4 | "skipLibCheck": true,
5 | "strict": true,
6 | "module": "esnext",
7 | "moduleResolution": "bundler",
8 | "isolatedModules": true,
9 | "noEmit": true,
10 | "jsx": "react-jsx"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-ticket-creation/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import path from 'node:path';
3 |
4 | export default defineConfig({
5 | resolve: {
6 | alias: {
7 | '@markprompt/core': path.resolve(__dirname, '../../packages/core/dist'),
8 | },
9 | },
10 | optimizeDeps: {
11 | exclude: ['@markprompt/web', '@markprompt/css', '@markprompt/react'],
12 | },
13 | ssr: {
14 | noExternal: ['@markprompt/css'],
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "nixpkgs": {
4 | "locked": {
5 | "lastModified": 1738009863,
6 | "narHash": "sha256-KxmFlQ2j9PpDhKRXWu85bv3R2wmfkUqdpJhEwz9JN/E=",
7 | "owner": "nixos",
8 | "repo": "nixpkgs",
9 | "rev": "f898cbfddfab52593da301a397a17d0af801bbc3",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "nixos",
14 | "ref": "nixpkgs-unstable",
15 | "repo": "nixpkgs",
16 | "type": "github"
17 | }
18 | },
19 | "root": {
20 | "inputs": {
21 | "nixpkgs": "nixpkgs"
22 | }
23 | }
24 | },
25 | "root": "root",
26 | "version": 7
27 | }
28 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs = {
4 | url = "github:nixos/nixpkgs/nixpkgs-unstable";
5 | };
6 | };
7 | outputs = {nixpkgs, ...}: let
8 | systems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"];
9 | forAllSystems = nixpkgs.lib.genAttrs systems;
10 | in {
11 | formatter = forAllSystems (
12 | system: let
13 | pkgs = nixpkgs.legacyPackages.${system};
14 | in
15 | pkgs.alejandra
16 | );
17 | devShells = forAllSystems (
18 | system: let
19 | pkgs = nixpkgs.legacyPackages.${system};
20 | node = pkgs.nodejs_22;
21 | corepackEnable = pkgs.runCommand "corepack-enable" {} ''
22 | mkdir -p $out/bin
23 | ${node}/bin/corepack enable --install-directory $out/bin
24 | '';
25 | in {
26 | default = with pkgs;
27 | mkShell {
28 | buildInputs = [
29 | corepackEnable
30 | node
31 | ];
32 | };
33 | }
34 | );
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "markprompt",
3 | "private": true,
4 | "type": "module",
5 | "workspaces": ["examples/*", "packages/*"],
6 | "scripts": {
7 | "build:packages": "turbo run build --filter @markprompt/*...",
8 | "build": "turbo run build",
9 | "clean": "git clean -fdx -e .direnv/ -e \"examples/**/.env*\" -e \".husky/\"",
10 | "fix:biome": "biome check --write",
11 | "lint:biome:ci": "biome ci --diagnostic-level=error --reporter=github",
12 | "lint:biome": "biome check --diagnostic-level=error",
13 | "lint:ci": "turbo run lint:biome:ci lint:css lint:js lint:md lint:ts",
14 | "lint:css": "stylelint \"**/*.css\"",
15 | "lint:js": "eslint .",
16 | "lint:md": "remark . --frail",
17 | "lint": "turbo run lint:biome lint:css lint:js lint:md lint:ts",
18 | "postinstall": "manypkg check",
19 | "prepare": "husky",
20 | "publish": "pnpm build:packages && changeset publish",
21 | "test:watch": "vitest watch --coverage",
22 | "test": "vitest run --coverage",
23 | "version": "changeset version && biome check --write . && pnpm install"
24 | },
25 | "dependencies": {
26 | "@biomejs/biome": "1.9.4",
27 | "@changesets/changelog-github": "^0.5.0",
28 | "@changesets/cli": "^2.27.9",
29 | "@manypkg/cli": "^0.22.0",
30 | "@markprompt/eslint-config": "workspace:*",
31 | "@vitest/coverage-v8": "^2.1.6",
32 | "eslint": "^9.16.0",
33 | "husky": "^9.0.11",
34 | "jsdom": "^25.0.1",
35 | "prettier": "^3.4.2",
36 | "remark-cli": "^12.0.1",
37 | "remark-frontmatter": "^5.0.0",
38 | "remark-toc": "^9.0.0",
39 | "remark-validate-links": "^13.0.2",
40 | "stylelint": "^16.10.0",
41 | "stylelint-config-standard": "^36.0.1",
42 | "turbo": "^2.3.4",
43 | "typescript": "^5.7.2",
44 | "unified-prettier": "^2.0.1",
45 | "vitest": "^2.1.4"
46 | },
47 | "optionalDependencies": {
48 | "@rollup/rollup-linux-x64-gnu": "^4.25.0"
49 | },
50 | "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0",
51 | "pnpm": {
52 | "overrides": {
53 | "@types/react": "^19",
54 | "@types/react-dom": "^19",
55 | "react": "^19",
56 | "react-dom": "^19"
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/core/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Motif
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/core/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { configs } from '@markprompt/eslint-config';
2 |
3 | export default [
4 | ...configs.base(import.meta.dirname, [
5 | 'eslint.config.js',
6 | 'vitest.config.js',
7 | 'src/*.test.ts',
8 | 'src/*/*.test.ts',
9 | ]),
10 | {
11 | rules: {
12 | '@typescript-eslint/consistent-indexed-object-style': [
13 | 'error',
14 | 'index-signature',
15 | ],
16 | },
17 | },
18 | ...configs.vitest,
19 | ];
20 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@markprompt/core",
3 | "version": "0.44.1",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/markprompt/markprompt-js.git",
7 | "directory": "packages/core"
8 | },
9 | "license": "MIT",
10 | "sideEffects": false,
11 | "type": "module",
12 | "exports": {
13 | "./chat": {
14 | "types": "./dist/chat/index.d.ts",
15 | "import": "./dist/chat/index.js"
16 | },
17 | "./constants": {
18 | "types": "./dist/constants.d.ts",
19 | "import": "./dist/constants.js"
20 | },
21 | "./feedback": {
22 | "types": "./dist/feedback.d.ts",
23 | "import": "./dist/feedback.js"
24 | },
25 | "./search": {
26 | "types": "./dist/search.d.ts",
27 | "import": "./dist/search.js"
28 | },
29 | "./types": {
30 | "types": "./dist/types.d.ts",
31 | "import": "./dist/types.js"
32 | },
33 | "./utils": {
34 | "types": "./dist/utils.d.ts",
35 | "import": "./dist/utils.js"
36 | }
37 | },
38 | "files": ["dist"],
39 | "scripts": {
40 | "build": "tsc --build",
41 | "dev": "tsc --build --watch",
42 | "lint:js": "eslint .",
43 | "lint:ts": "tsc --build --noEmit",
44 | "prepack": "tsc --build"
45 | },
46 | "dependencies": {
47 | "@algolia/client-search": "^4.23.3",
48 | "@types/lodash-es": "^4.17.12",
49 | "defaults": "^3.0.0",
50 | "eventsource-parser": "^1.1.2",
51 | "lodash-es": "^4.17.21",
52 | "openai": "^4.80.1",
53 | "type-fest": "^4.15.0"
54 | },
55 | "devDependencies": {
56 | "@types/defaults": "^1.0.6",
57 | "msw": "^2.6.2"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/core/src/constants.ts:
--------------------------------------------------------------------------------
1 | import type { BaseOptions } from './types.js';
2 |
3 | export const DEFAULT_OPTIONS = {
4 | apiUrl: 'https://api.markprompt.com',
5 | } satisfies BaseOptions;
6 |
7 | export type { BaseOptions };
8 |
--------------------------------------------------------------------------------
/packages/core/src/feedback.test.ts:
--------------------------------------------------------------------------------
1 | import { http, HttpResponse } from 'msw';
2 | import { setupServer } from 'msw/node';
3 | import { afterAll, afterEach, beforeAll, describe, expect, test } from 'vitest';
4 |
5 | import { DEFAULT_OPTIONS } from './constants.js';
6 | import { submitFeedback } from './feedback.js';
7 |
8 | let status = 200;
9 |
10 | const TEST_MESSAGE_ID = 'test-id';
11 |
12 | const server = setupServer(
13 | http.post(`${DEFAULT_OPTIONS.apiUrl}/messages/${TEST_MESSAGE_ID}`, () => {
14 | return HttpResponse.json(
15 | status === 200 ? { status: 'ok' } : { error: 'Internal Server Error' },
16 | { status: status },
17 | );
18 | }),
19 | );
20 |
21 | beforeAll(() => {
22 | server.listen({ onUnhandledRequest: 'error' });
23 | });
24 |
25 | afterAll(() => {
26 | server.close();
27 | });
28 |
29 | afterEach(() => {
30 | status = 200;
31 | server.resetHandlers();
32 | });
33 |
34 | describe('submitFeedback', () => {
35 | test('requires a projectKey', async () => {
36 | // @ts-expect-error We test a missing project key.
37 | await expect(() => submitFeedback()).rejects.toThrowError(
38 | 'A projectKey is required',
39 | );
40 | });
41 |
42 | test('makes a request', async () => {
43 | const response = await submitFeedback(
44 | {
45 | feedback: { vote: '1' },
46 | messageId: TEST_MESSAGE_ID,
47 | },
48 | 'testKey',
49 | );
50 |
51 | expect(response).toStrictEqual(undefined);
52 | });
53 |
54 | test('throws an error on invalid status code', async () => {
55 | status = 500;
56 |
57 | await expect(
58 | submitFeedback(
59 | {
60 | feedback: { vote: '1' },
61 | messageId: TEST_MESSAGE_ID,
62 | },
63 | 'testKey',
64 | ),
65 | ).rejects.toThrowError('Internal Server Error');
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/packages/core/src/types.ts:
--------------------------------------------------------------------------------
1 | export type SourceType =
2 | | 'github'
3 | | 'motif'
4 | | 'website'
5 | | 'file-upload'
6 | | 'api-upload'
7 | | 'nango'
8 | | 'salesforce';
9 |
10 | export interface Source {
11 | id: string;
12 | type: SourceType;
13 | data?: {
14 | url?: string;
15 | domain?: string;
16 | name?: string;
17 | };
18 | }
19 |
20 | export interface FileSectionReference extends FileSectionReferenceSectionData {
21 | /**
22 | * Referenced file.
23 | */
24 | file: FileReferenceFileData;
25 | sectionId: number;
26 | }
27 |
28 | export interface FileSectionReferenceSectionData {
29 | /**
30 | * Metadata associated to the file section.
31 | */
32 | meta?: {
33 | leadHeading?: {
34 | id?: string;
35 | depth?: number;
36 | value?: string;
37 | slug?: string;
38 | };
39 | };
40 | }
41 |
42 | export interface FileReferenceFileData {
43 | /**
44 | * File title.
45 | */
46 | title?: string;
47 | /**
48 | * File path, e.g. URL or GitHub file path.
49 | */
50 | path: string;
51 | /**
52 | * File metadata.
53 | */
54 | meta?: object;
55 | /**
56 | * File source.
57 | */
58 | source: Source;
59 | }
60 |
61 | export interface BaseOptions {
62 | apiUrl?: string;
63 | headers?: { [key: string]: string };
64 | }
65 |
--------------------------------------------------------------------------------
/packages/core/src/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from 'vitest';
2 |
3 | import {
4 | getErrorMessage,
5 | isFileSectionReferences,
6 | isAbortError,
7 | isKeyOf,
8 | } from './utils.js';
9 |
10 | describe('getErrorMessage', () => {
11 | test('returns error from response if present', async () => {
12 | const mockResponse = new Response(JSON.stringify({ error: 'Test error' }));
13 | const result = await getErrorMessage(mockResponse);
14 | expect(result).toBe('Test error');
15 | });
16 |
17 | test('returns text from response if error is not present', async () => {
18 | const mockResponse = new Response('Test text');
19 | const result = await getErrorMessage(mockResponse);
20 | expect(result).toBe('Test text');
21 | });
22 | });
23 |
24 | describe('isFileSectionReferences', () => {
25 | test('identifies FileSectionReference types', () => {
26 | const references = [
27 | {
28 | file: {
29 | path: '/docs/some-page',
30 | source: { type: 'website' },
31 | },
32 | },
33 | ];
34 |
35 | expect(isFileSectionReferences(references)).toBe(true);
36 | });
37 | });
38 |
39 | describe('isAbortError', () => {
40 | test('identifies AbortError', () => {
41 | const err1 = new DOMException('AbortError');
42 | expect(isAbortError(err1)).toBe(true);
43 | const err2 = new Error('AbortError');
44 | expect(isAbortError(err2)).toBe(true);
45 | const err3 = new Error('Some other error');
46 | expect(isAbortError(err3)).toBe(false);
47 | });
48 | });
49 |
50 | describe('isKeyOf', () => {
51 | test('identifies keys of an object', () => {
52 | const obj = { foo: 'bar' };
53 | expect(isKeyOf(obj, 'foo')).toBeTruthy();
54 | expect(isKeyOf(obj, 'bar')).toBeFalsy();
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/packages/core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
2 |
3 | import type { FileSectionReference } from './types.js';
4 |
5 | export type RequiredKeys = Required> &
6 | Omit;
7 |
8 | export type ArrayToUnion = T extends (infer U)[]
9 | ? U
10 | : T extends readonly (infer U)[]
11 | ? U
12 | : never;
13 |
14 | export const isKeyOf = (
15 | obj: T,
16 | key: PropertyKey,
17 | ): key is keyof T => Object.hasOwn(obj, key);
18 |
19 | export const getErrorMessage = async (res: Response): Promise => {
20 | const text = await res.text();
21 |
22 | try {
23 | const json: unknown = JSON.parse(text);
24 | if (
25 | json &&
26 | typeof json === 'object' &&
27 | 'error' in json &&
28 | typeof json.error === 'string'
29 | ) {
30 | return json?.error ?? text;
31 | }
32 | } catch {
33 | return text;
34 | }
35 |
36 | return text;
37 | };
38 |
39 | export function isAbortError(err: unknown): err is DOMException {
40 | return (
41 | (err instanceof DOMException && err.name === 'AbortError') ||
42 | (err instanceof Error && err.message.includes('AbortError'))
43 | );
44 | }
45 |
46 | export function isFileSectionReferences(
47 | data: unknown,
48 | ): data is FileSectionReference[] {
49 | return (
50 | Array.isArray(data) &&
51 | typeof data.at(0)?.file?.path === 'string' &&
52 | typeof data.at(0)?.file?.source?.type === 'string'
53 | );
54 | }
55 |
56 | export function getMessageTextContent(m?: {
57 | content?: null | string | ChatCompletionMessageParam['content'];
58 | }) {
59 | if (!m?.content) {
60 | return;
61 | }
62 |
63 | if (typeof m.content === 'string') {
64 | return m.content;
65 | }
66 |
67 | return m.content
68 | .map((part) => (part.type === 'text' ? part.text : ''))
69 | .join(' ')
70 | .trim();
71 | }
72 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // base
4 | "esModuleInterop": true,
5 | "skipLibCheck": true,
6 | "target": "es2023",
7 | "moduleDetection": "force",
8 | "isolatedModules": true,
9 | "checkJs": true,
10 |
11 | // strictness
12 | "strict": true,
13 |
14 | // transpilation
15 | "module": "nodenext",
16 | "moduleResolution": "nodenext",
17 | "rootDir": "src/",
18 | "outDir": "dist/",
19 |
20 | "sourceMap": true,
21 | "declaration": true,
22 | "declarationMap": true,
23 |
24 | // runtime
25 | "lib": ["dom", "dom.iterable", "es2023"]
26 | },
27 | "include": ["src/"],
28 | "exclude": ["**/node_modules", "src/**/*.test.ts", "dist/"]
29 | }
30 |
--------------------------------------------------------------------------------
/packages/core/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineProject, mergeConfig } from 'vitest/config';
2 |
3 | export default mergeConfig(
4 | {
5 | coverage: {
6 | enabled: true,
7 | provider: 'v8',
8 | include: ['./src/**/*'],
9 | },
10 | },
11 | defineProject({
12 | test: {
13 | pool: 'forks',
14 | },
15 | }),
16 | );
17 |
--------------------------------------------------------------------------------
/packages/css/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Motif
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/css/README.md:
--------------------------------------------------------------------------------
1 | # Markprompt CSS
2 |
3 | Common CSS for [Markprompt](https://markprompt.com) components.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ## Installation
16 |
17 | ```sh
18 | npm install @markprompt/css
19 | ```
20 |
21 | ## Usage
22 |
23 | With a bundler:
24 |
25 | ```js
26 | import '@markprompt/css';
27 | ```
28 |
29 | With a CDN:
30 |
31 | ```html
32 |
36 | ```
37 |
38 | This package adds styling for various CSS classes. All styling is applied using
39 | the [`:where()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) pseudo
40 | class, so you can override all styling manually.
41 |
42 | ## Documentation
43 |
44 | The full documentation for the package can be found on the
45 | [Markprompt docs](https://markprompt.com/docs/sdk).
46 |
47 | ## Community
48 |
49 | - [X](https://x.com/markprompt)
50 |
51 | ## Authors
52 |
53 | This library is created by the team behind [Markprompt](https://markprompt.com)
54 | ([@markprompt](https://x.com/markprompt)).
55 |
56 | ## License
57 |
58 | [MIT](./LICENSE) © [Markprompt](https://markprompt.com)
59 |
--------------------------------------------------------------------------------
/packages/css/index.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/packages/css/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@markprompt/css",
3 | "version": "0.34.0",
4 | "description": "Common CSS for Markprompt components",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/markprompt/markprompt-js.git",
8 | "directory": "packages/css"
9 | },
10 | "license": "MIT",
11 | "main": "markprompt.css",
12 | "types": "./index.d.ts",
13 | "files": ["markprompt.css", "index.d.ts"]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/docusaurus-theme-search/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Motif
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/docusaurus-theme-search/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { configs } from '@markprompt/eslint-config';
2 |
3 | export default [
4 | ...configs.base(import.meta.dirname, [
5 | 'eslint.config.js',
6 | 'vitest.config.js',
7 | ]),
8 | {
9 | rules: {
10 | '@typescript-eslint/consistent-indexed-object-style': [
11 | 'error',
12 | 'index-signature',
13 | ],
14 | 'import-x/no-unresolved': [
15 | 'error',
16 | {
17 | ignore: ['@docusaurus/*', '@theme/*', '@site/*'],
18 | },
19 | ],
20 | },
21 | },
22 | ...configs.react,
23 | ...configs.vitest,
24 | ];
25 |
--------------------------------------------------------------------------------
/packages/docusaurus-theme-search/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@markprompt/docusaurus-theme-search",
3 | "version": "0.32.7",
4 | "description": "Markprompt search theme for Docusaurus",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/markprompt/markprompt-js.git",
8 | "directory": "packages/docusaurus-theme-search"
9 | },
10 | "license": "MIT",
11 | "sideEffects": false,
12 | "type": "module",
13 | "exports": "./dist/index.js",
14 | "main": "dist/index.js",
15 | "files": ["dist", "src/theme"],
16 | "scripts": {
17 | "build": "tsc --build",
18 | "lint:js": "eslint .",
19 | "lint:ts": "tsc --build --noEmit",
20 | "prepack": "tsc --build"
21 | },
22 | "dependencies": {
23 | "@docusaurus/types": "^3.2.1",
24 | "@markprompt/css": "workspace:*",
25 | "@markprompt/react": "workspace:*",
26 | "acorn": "^8.14.0"
27 | },
28 | "devDependencies": {
29 | "@docusaurus/module-type-aliases": "^3.2.1",
30 | "@types/react": "^19.0.1",
31 | "react": "^19.0.0"
32 | },
33 | "peerDependencies": {
34 | "react": "^18.3 || ^19"
35 | },
36 | "publishConfig": {
37 | "access": "public"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/docusaurus-theme-search/src/index.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PluginModule,
3 | ThemeConfigValidationContext,
4 | } from '@docusaurus/types';
5 | import type { MarkpromptProps } from '@markprompt/react';
6 |
7 | export type MarkpromptConfig = MarkpromptProps;
8 |
9 | export interface ThemeConfig {
10 | markprompt?: MarkpromptConfig;
11 | }
12 |
13 | const themeSearchMarkprompt: PluginModule = async () => ({
14 | name: '@markprompt/docusaurus-theme-search',
15 | getThemePath: () => '../dist/theme',
16 | getTypeScriptThemePath: () => '../src/theme',
17 | });
18 |
19 | export function getSwizzleComponentList(): string[] {
20 | return ['SearchBar'];
21 | }
22 |
23 | export const validateThemeConfig = (
24 | context: ThemeConfigValidationContext,
25 | ): ThemeConfig => {
26 | return context.themeConfig;
27 | };
28 |
29 | export default themeSearchMarkprompt;
30 |
--------------------------------------------------------------------------------
/packages/docusaurus-theme-search/src/theme/SearchBar/index.tsx:
--------------------------------------------------------------------------------
1 | import '@markprompt/css';
2 |
3 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
4 | import {
5 | Markprompt,
6 | type MarkpromptProps,
7 | openMarkprompt,
8 | } from '@markprompt/react';
9 | import { useEffect, useState } from 'react';
10 | import type { JSX } from 'react';
11 |
12 | declare global {
13 | interface Window {
14 | markpromptConfigExtras?: {
15 | references?: MarkpromptProps['references'];
16 | search?: MarkpromptProps['search'];
17 | };
18 | }
19 | }
20 |
21 | export default function SearchBar(): JSX.Element {
22 | const [markpromptExtras, setMarkpromptExtras] = useState<{
23 | references?: MarkpromptProps['references'];
24 | search?: MarkpromptProps['search'];
25 | }>({});
26 | const { siteConfig } = useDocusaurusContext();
27 |
28 | useEffect(() => {
29 | if (typeof window === 'undefined') {
30 | return;
31 | }
32 |
33 | setMarkpromptExtras(window.markpromptConfigExtras || {});
34 | }, []);
35 |
36 | const markpromptConfigProps = siteConfig.themeConfig
37 | .markprompt as MarkpromptProps;
38 | const markpromptProps = {
39 | ...markpromptConfigProps,
40 | references: {
41 | ...markpromptConfigProps.references,
42 | ...markpromptExtras.references,
43 | },
44 | search: {
45 | ...markpromptConfigProps.search,
46 | ...markpromptExtras.search,
47 | },
48 | };
49 |
50 | if (markpromptProps.trigger?.floating) {
51 | return ;
52 | }
53 |
54 | return (
55 | <>
56 |
57 |
58 | openMarkprompt()}
64 | style={{
65 | textAlign: 'left',
66 | }}
67 | >
68 | {markpromptProps.trigger?.placeholder || 'Search or ask'}
69 |
70 |
77 |
78 | >
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/packages/docusaurus-theme-search/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // base
4 | "esModuleInterop": true,
5 | "skipLibCheck": true,
6 | "target": "es2023",
7 | "moduleDetection": "force",
8 | "isolatedModules": true,
9 | "checkJs": true,
10 |
11 | // strictness
12 | "strict": true,
13 | "noUncheckedIndexedAccess": true,
14 |
15 | // transpilation
16 | "module": "nodenext",
17 | "moduleResolution": "nodenext",
18 | "outDir": "dist/",
19 | "rootDir": "src/",
20 | "sourceMap": true,
21 | "declaration": true,
22 | "declarationMap": true,
23 | "jsx": "react-jsx",
24 | "verbatimModuleSyntax": true,
25 |
26 | // runtime
27 | "lib": ["dom", "dom.iterable", "es2023"],
28 |
29 | "types": ["@docusaurus/module-type-aliases"]
30 | },
31 | "include": ["src/"],
32 | "exclude": ["**/node_modules", "dist/", ".turbo/"]
33 | }
34 |
--------------------------------------------------------------------------------
/packages/docusaurus-theme-search/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineProject } from 'vitest/config';
2 |
3 | export default defineProject({});
4 |
--------------------------------------------------------------------------------
/packages/eslint-config/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { configs } from './dist/index.js';
2 |
3 | export default [
4 | ...configs.base(import.meta.dirname, ['eslint.config.js']),
5 | {
6 | rules: {
7 | '@typescript-eslint/consistent-indexed-object-style': [
8 | 'error',
9 | 'index-signature',
10 | ],
11 | },
12 | },
13 | ];
14 |
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@markprompt/eslint-config",
3 | "private": true,
4 | "type": "module",
5 | "exports": {
6 | ".": "./dist/index.js"
7 | },
8 | "scripts": {
9 | "build": "tsc --build",
10 | "dev": "tsc --build --watch",
11 | "lint:js": "eslint .",
12 | "lint:ts": "tsc --build --noEmit"
13 | },
14 | "dependencies": {
15 | "@eslint/compat": "^1.2.4",
16 | "@eslint/js": "^9.16.0",
17 | "@next/eslint-plugin-next": "^14.2.15",
18 | "@tanstack/eslint-plugin-query": "^5.62.1",
19 | "@tanstack/eslint-plugin-router": "^1.92.7",
20 | "@vitest/eslint-plugin": "^1.1.14",
21 | "eslint-config-turbo": "^2.3.3",
22 | "eslint-import-resolver-typescript": "^3.7.0",
23 | "eslint-plugin-astro": "^1.3.1",
24 | "eslint-plugin-import-x": "^4.5.0",
25 | "eslint-plugin-jsx-a11y": "^6.10.2",
26 | "eslint-plugin-promise": "^7.2.1",
27 | "eslint-plugin-react": "^7.37.2",
28 | "eslint-plugin-react-refresh": "^0.4.16",
29 | "eslint-plugin-testing-library": "^7.1.0",
30 | "globals": "^15.13.0",
31 | "typescript-eslint": "^8.17.0"
32 | },
33 | "devDependencies": {
34 | "@types/eslint__js": "^8.42.3",
35 | "@types/node": "^22",
36 | "eslint": "^9.16.0"
37 | },
38 | "peerDependencies": {
39 | "eslint": ">= 9"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/astro.ts:
--------------------------------------------------------------------------------
1 | import type { Linter } from 'eslint';
2 | import { configs } from 'eslint-plugin-astro';
3 |
4 | export const astro: Linter.Config[] = [
5 | ...configs.recommended,
6 | ...configs['jsx-a11y-recommended'],
7 | {
8 | files: ['**/*.astro'],
9 | settings: {
10 | 'import-x/extensions': ['.astro', '.js', '.jsx', '.ts', '.tsx'],
11 | 'import-x/parsers': {
12 | 'astro-eslint-parser': ['.astro'],
13 | },
14 | },
15 | rules: {
16 | 'import-x/default': 'off',
17 | },
18 | },
19 | ];
20 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/index.ts:
--------------------------------------------------------------------------------
1 | import { astro } from './astro.js';
2 | import { base } from './base.js';
3 | import { next } from './next.js';
4 | import { react } from './react.js';
5 | import { tanstack } from './tanstack.js';
6 | import { vitest } from './vitest.js';
7 |
8 | export const configs = {
9 | astro: astro,
10 | base: base,
11 | next: next,
12 | react: react,
13 | tanstack: tanstack,
14 | vitest: vitest,
15 | } as const;
16 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/next.ts:
--------------------------------------------------------------------------------
1 | import { fixupPluginRules } from '@eslint/compat';
2 | import nextPlugin from '@next/eslint-plugin-next';
3 | import type { Linter } from 'eslint';
4 |
5 | export const next: Linter.Config[] = [
6 | {
7 | ignores: ['.next/'],
8 | },
9 | {
10 | name: 'next',
11 | plugins: { '@next/next': fixupPluginRules(nextPlugin) },
12 | rules: {
13 | ...(nextPlugin.configs?.recommended as Linter.Config)
14 | .rules,
15 | ...(
16 | nextPlugin.configs?.[
17 | 'core-web-vitals'
18 | ] as Linter.Config
19 | ).rules,
20 | },
21 | },
22 | ];
23 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/tanstack.ts:
--------------------------------------------------------------------------------
1 | import query from '@tanstack/eslint-plugin-query';
2 | import router from '@tanstack/eslint-plugin-router';
3 | import type { Linter } from 'eslint';
4 |
5 | export const tanstack: Linter.Config[] = [
6 | ...query.configs['flat/recommended'],
7 | ...router.configs['flat/recommended'],
8 | ];
9 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/types/eslint-config-turbo.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'eslint-config-turbo/flat' {
2 | // biome-ignore lint/correctness/noUnusedImports: this is correct in d.ts files
3 | import type { Linter } from 'eslint';
4 | // biome-ignore lint/correctness/noUndeclaredVariables: false positive
5 | const config: Linter.Config[];
6 |
7 | export = config;
8 | }
9 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/types/eslint-plugin-jsx-a11y.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'eslint-plugin-jsx-a11y' {
2 | import type { Linter } from 'eslint';
3 |
4 | interface Plugin {
5 | flatConfigs: {
6 | recommended: Linter.Config;
7 | };
8 | }
9 |
10 | const plugin: Plugin;
11 |
12 | export = plugin;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/types/eslint-plugin-promise.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'eslint-plugin-promise' {
2 | // biome-ignore lint/correctness/noUnusedImports: this is correct in d.ts files
3 | import type { Linter } from 'eslint';
4 |
5 | interface PluginPromise {
6 | rules: Linter.RulesRecord;
7 | rulesConfig: Linter.RulesRecord;
8 | configs: {
9 | recommended: Linter.LegacyConfig;
10 | 'flat/recommended': Linter.Config;
11 | };
12 | }
13 |
14 | const pluginPromise: PluginPromise;
15 |
16 | export = pluginPromise;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/types/eslint-plugin-react-refresh.d.ts:
--------------------------------------------------------------------------------
1 | // types exist but are wrong: https://github.com/jsx-eslint/eslint-plugin-react/pull/3840
2 | declare module 'eslint-plugin-react-refresh' {
3 | import type { Plugin } from 'eslint';
4 |
5 | const plugin: Plugin;
6 |
7 | export = plugin;
8 | }
9 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/types/eslint-plugin-react.d.ts:
--------------------------------------------------------------------------------
1 | // types exist but are wrong: https://github.com/jsx-eslint/eslint-plugin-react/pull/3840
2 | declare module 'eslint-plugin-react' {
3 | // biome-ignore lint/correctness/noUnusedImports: this is correct in d.ts files
4 | import type { Linter } from 'eslint';
5 |
6 | interface Plugin {
7 | configs: {
8 | flat: {
9 | recommended: Linter.Config;
10 | 'jsx-runtime': Linter.Config;
11 | };
12 | };
13 | }
14 |
15 | const plugin: Plugin;
16 |
17 | export = plugin;
18 | }
19 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/types/eslint-plugin-testing-library.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'eslint-plugin-testing-library' {
2 | // biome-ignore lint/correctness/noUnusedImports: this is correct in d.ts files
3 | import type { Linter } from 'eslint';
4 |
5 | interface TestingLibraryPlugin {
6 | configs: {
7 | 'flat/react': Linter.Config;
8 | };
9 | }
10 |
11 | const testingLibraryPlugin: TestingLibraryPlugin;
12 |
13 | export = testingLibraryPlugin;
14 | }
15 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/types/next__eslint-plugin-next.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@next/eslint-plugin-next' {
2 | // biome-ignore lint/correctness/noUnusedImports: this is correct in d.ts files
3 | import type { ESLint } from 'eslint';
4 | const pluginNext: ESLint.Plugin;
5 | export = pluginNext;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/eslint-config/src/vitest.ts:
--------------------------------------------------------------------------------
1 | import vitestPlugin from '@vitest/eslint-plugin';
2 | import testingLibrary from 'eslint-plugin-testing-library';
3 |
4 | export const vitest = [
5 | { ignores: ['coverage/'] },
6 | {
7 | files: ['**/*.test.{js,jsx,ts,tsx}'],
8 | plugins: { vitest: vitestPlugin },
9 | languageOptions: {
10 | parserOptions: {
11 | ecmaFeatures: {
12 | jsx: true,
13 | },
14 | },
15 | globals: {
16 | ...vitestPlugin.environments.env.globals,
17 | },
18 | },
19 | rules: {
20 | ...vitestPlugin.configs.recommended.rules,
21 | 'vitest/max-nested-describe': ['error', { max: 3 }],
22 | },
23 | },
24 | {
25 | files: ['**/*.test.{js,jsx,ts,tsx}'],
26 | ...testingLibrary.configs['flat/react'],
27 | },
28 | ];
29 |
--------------------------------------------------------------------------------
/packages/eslint-config/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Base Options: */
4 | "esModuleInterop": true,
5 | "skipLibCheck": true,
6 | "target": "es2023",
7 | "allowJs": true,
8 | "checkJs": true,
9 | "resolveJsonModule": true,
10 | "moduleDetection": "force",
11 | "isolatedModules": true,
12 | "verbatimModuleSyntax": true,
13 |
14 | /* Strictness */
15 | "strict": true,
16 | "noUncheckedIndexedAccess": true,
17 | "noImplicitOverride": true,
18 |
19 | /* If transpiling with TypeScript: */
20 | "module": "NodeNext",
21 | "rootDir": "src",
22 | "outDir": "dist",
23 | "sourceMap": true,
24 |
25 | /* AND if you're building for a library: */
26 | "declaration": true,
27 |
28 | /* AND if you're building for a library in a monorepo: */
29 | "composite": true,
30 | "declarationMap": true,
31 |
32 | /* If your code doesn't run in the DOM: */
33 | "lib": ["es2023"]
34 | },
35 | "include": ["src/"]
36 | }
37 |
--------------------------------------------------------------------------------
/packages/eslint-config/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": ["dist/**"]
6 | },
7 | "dev": {
8 | "persistent": true,
9 | "cache": false
10 | },
11 | "lint:js": {
12 | "dependsOn": ["build"]
13 | },
14 | "lint:ts": {}
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/react/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Motif
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/react/__mocks__/zustand.ts:
--------------------------------------------------------------------------------
1 | import { act } from '@testing-library/react';
2 | import { afterEach, vi } from 'vitest';
3 | import type * as zustand from 'zustand';
4 |
5 | const {
6 | create: actualCreate,
7 | createStore: actualCreateStore,
8 | useStore: actualUseStore,
9 | } = await vi.importActual('zustand');
10 |
11 | // a variable to hold reset functions for all stores declared in the app
12 | export const storeResetFns = new Set<() => void>();
13 |
14 | const createUncurried = (
15 | stateCreator: zustand.StateCreator,
16 | ): zustand.UseBoundStore> => {
17 | const store = actualCreate(stateCreator);
18 | const initialState = store.getState();
19 | storeResetFns.add(() => {
20 | store.setState(initialState, true);
21 | });
22 | return store;
23 | };
24 |
25 | // when creating a store, we get its initial state, create a reset function and add it in the set
26 | export const create = ((stateCreator: zustand.StateCreator) => {
27 | // to support curried version of create
28 | return typeof stateCreator === 'function'
29 | ? createUncurried(stateCreator)
30 | : createUncurried;
31 | }) as typeof zustand.create;
32 |
33 | const createStoreUncurried = (
34 | stateCreator: zustand.StateCreator,
35 | ): zustand.StoreApi => {
36 | const store = actualCreateStore(stateCreator);
37 | const initialState = store.getState();
38 | storeResetFns.add(() => {
39 | store.setState(initialState, true);
40 | });
41 | return store;
42 | };
43 |
44 | // when creating a store, we get its initial state, create a reset function and add it in the set
45 | export const createStore = ((stateCreator: zustand.StateCreator) => {
46 | // to support curried version of createStore
47 | return typeof stateCreator === 'function'
48 | ? createStoreUncurried(stateCreator)
49 | : createStoreUncurried;
50 | }) as typeof zustand.createStore;
51 |
52 | afterEach(() => {
53 | act(() => {
54 | localStorage.clear();
55 | for (const resetFn of storeResetFns) {
56 | resetFn();
57 | }
58 | });
59 | });
60 |
61 | export { actualUseStore as useStore };
62 |
--------------------------------------------------------------------------------
/packages/react/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { configs } from '@markprompt/eslint-config';
2 |
3 | export default [
4 | ...configs.base(import.meta.dirname, [
5 | 'eslint.config.js',
6 | 'vitest.config.ts',
7 | 'vitest.setup.ts',
8 | ]),
9 | {
10 | rules: {
11 | '@typescript-eslint/consistent-indexed-object-style': [
12 | 'error',
13 | 'index-signature',
14 | ],
15 | },
16 | },
17 | ...configs.react,
18 | ...configs.vitest,
19 | ];
20 |
--------------------------------------------------------------------------------
/packages/react/src/chat/Answer.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx';
2 | import type { ComponentType, JSX } from 'react';
3 |
4 | import * as BaseMarkprompt from '../primitives/headless.js';
5 | import type { ChatViewAssistantMessage } from '../types.js';
6 |
7 | interface AnswerProps {
8 | /**
9 | * Custom class name.
10 | */
11 | className?: string;
12 | /**
13 | * The message to display, in Markdown format.
14 | */
15 | message: ChatViewAssistantMessage;
16 | /**
17 | * Component to use in place of .
18 | * @default "a"
19 | */
20 | linkAs?: string | ComponentType;
21 | }
22 |
23 | export function Answer(props: AnswerProps): JSX.Element {
24 | const { message, className, linkAs } = props;
25 |
26 | return (
27 |
34 | {Array.isArray(message.content) ? (
35 | message.content.map((part, index) => (
36 |
43 | ))
44 | ) : (
45 |
51 | )}
52 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/packages/react/src/chat/MessageAnswer.tsx:
--------------------------------------------------------------------------------
1 | import type { ComponentType, JSX } from 'react';
2 |
3 | import { Answer } from './Answer.js';
4 | import type { ChatViewAssistantMessage } from '../types.js';
5 |
6 | function LoadingDots(): JSX.Element {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | interface MessageAnswerProps {
17 | message: ChatViewAssistantMessage;
18 | linkAs?: string | ComponentType;
19 | }
20 |
21 | export function MessageAnswer(props: MessageAnswerProps): JSX.Element {
22 | const { message } = props;
23 |
24 | return (
25 |
26 | {(message.state === 'indeterminate' || message.state === 'preload') && (
27 |
28 |
29 |
30 | )}
31 |
32 |
33 |
34 | {message.state === 'cancelled' && (
35 |
36 |
37 | This chat response was cancelled. Please try regenerating the answer
38 | or ask another question.
39 |
40 |
41 | )}
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/packages/react/src/chat/ThreadSelect.tsx:
--------------------------------------------------------------------------------
1 | import { getMessageTextContent } from '@markprompt/core/utils';
2 | import type { JSX } from 'react';
3 |
4 | import { selectProjectThreads, useChatStore } from './store.js';
5 | import { CounterClockwiseClockIcon, PlusIcon } from '../icons.js';
6 | import { Select } from '../primitives/Select.js';
7 |
8 | export function ThreadSelect({
9 | disabled,
10 | }: {
11 | disabled?: boolean;
12 | }): JSX.Element {
13 | const threads = useChatStore(selectProjectThreads);
14 | const selectThread = useChatStore((state) => state.selectThread);
15 |
16 | return (
17 |
27 | }
28 | items={[
29 | ...threads.map(([threadId, { messages }]) => ({
30 | value: threadId,
31 | label: getMessageTextContent(messages[0]) ?? '',
32 | })),
33 | {
34 | value: 'new',
35 | label: 'Start new chat',
36 | children: (
37 |
38 | {' '}
43 | New chat
44 |
45 | ),
46 | },
47 | ]}
48 | itemToString={(item) => item?.value ?? ''}
49 | itemToChildren={(item) => {
50 | if ('children' in item!) return item.children;
51 | return item?.label;
52 | }}
53 | onSelectedItemChange={({ selectedItem }) => {
54 | selectThread(
55 | selectedItem?.value === 'new' ? undefined : selectedItem?.value,
56 | );
57 | }}
58 | />
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/packages/react/src/chat/ThreadSidebar.tsx:
--------------------------------------------------------------------------------
1 | import { getMessageTextContent } from '@markprompt/core/utils';
2 | import { useMemo, type JSX } from 'react';
3 |
4 | import { selectProjectThreads, useChatStore } from './store.js';
5 | import { PlusIcon } from '../icons.js';
6 | import type { MarkpromptDisplay } from '../types.js';
7 | import { markdownToString } from '../utils.js';
8 |
9 | export interface ThreadSidebarProps {
10 | /**
11 | * The way to display the chat/search content.
12 | * @default "dialog"
13 | **/
14 | display?: MarkpromptDisplay;
15 | }
16 |
17 | export function ThreadSidebar(props: ThreadSidebarProps): JSX.Element {
18 | const selectedThreadId = useChatStore((state) => state.threadId);
19 | const threads = useChatStore(selectProjectThreads);
20 | const selectThread = useChatStore((state) => state.selectThread);
21 |
22 | const sortedThreads = useMemo(() => {
23 | if (props.display === 'plain') {
24 | return threads.slice().reverse();
25 | }
26 | return threads;
27 | }, [threads, props.display]);
28 |
29 | return (
30 |
31 |
32 | Chats
33 |
34 |
35 |
36 | selectThread(undefined)} type="button">
37 |
38 | New
39 | chat
40 |
41 |
42 |
43 | {sortedThreads.map(([threadId, { messages }]) => (
44 |
49 | selectThread(threadId)} type="button">
50 |
51 |
52 | {getMessageTextContent(messages[0]) ?? 'Unknown thread'}
53 |
54 |
55 | {typeof messages[1]?.content === 'string' && (
56 | {markdownToString(messages[1]?.content, 70)}
57 | )}
58 |
59 |
60 | ))}
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/packages/react/src/chat/aes.ts:
--------------------------------------------------------------------------------
1 | export const importKeyFromBase64 = async (base64Key: string) => {
2 | const rawKey = Uint8Array.from(atob(base64Key), (c) => c.charCodeAt(0));
3 | return await globalThis.crypto.subtle.importKey(
4 | 'raw',
5 | rawKey,
6 | { name: 'AES-GCM' },
7 | true,
8 | ['encrypt', 'decrypt'],
9 | );
10 | };
11 |
12 | export const packEncrypted = (
13 | ciphertext: ArrayBuffer,
14 | iv: Uint8Array,
15 | ): string => {
16 | const combined = new Uint8Array(iv.length + ciphertext.byteLength);
17 | combined.set(iv, 0);
18 | combined.set(new Uint8Array(ciphertext), iv.length);
19 | return btoa(String.fromCharCode(...combined));
20 | };
21 |
22 | export const unpackEncrypted = (
23 | data: string,
24 | ): { iv: Uint8Array; ciphertext: ArrayBuffer } => {
25 | const binary = atob(data);
26 | const bytes = new Uint8Array([...binary].map((c) => c.charCodeAt(0)));
27 | const iv = bytes.slice(0, 12);
28 | const ciphertext = bytes.slice(12).buffer;
29 | return { ciphertext, iv };
30 | };
31 |
32 | export const encrypt = async (plaintext: string, key: CryptoKey) => {
33 | const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
34 | const encoded = new TextEncoder().encode(plaintext);
35 | const ciphertext = await globalThis.crypto.subtle.encrypt(
36 | { name: 'AES-GCM', iv },
37 | key,
38 | encoded,
39 | );
40 | return { iv, ciphertext };
41 | };
42 |
43 | export const decrypt = async (
44 | ciphertext: ArrayBuffer,
45 | key: CryptoKey,
46 | iv: Uint8Array,
47 | ) => {
48 | const decrypted = await globalThis.crypto.subtle.decrypt(
49 | { name: 'AES-GCM', iv },
50 | key,
51 | ciphertext,
52 | );
53 | return new TextDecoder().decode(decrypted);
54 | };
55 |
--------------------------------------------------------------------------------
/packages/react/src/chat/provider.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, type JSX, type ReactNode } from 'react';
2 |
3 | import { createChatStore, ChatContext, type ChatStore } from './store.js';
4 | import type { MarkpromptOptions } from '../types.js';
5 |
6 | export interface ChatProviderProps {
7 | chatOptions?: MarkpromptOptions['chat'];
8 | children: ReactNode;
9 | debug?: boolean;
10 | projectKey: string;
11 | storeKey?: string;
12 | apiUrl?: string;
13 | headers?: { [key: string]: string };
14 | }
15 |
16 | export function ChatProvider(props: ChatProviderProps): JSX.Element {
17 | const {
18 | chatOptions,
19 | children,
20 | debug,
21 | projectKey,
22 | storeKey,
23 | apiUrl,
24 | headers,
25 | } = props;
26 |
27 | const store = useRef<{
28 | storeKey: string | undefined;
29 | store: ChatStore | null;
30 | }>({
31 | storeKey: undefined,
32 | store: null,
33 | });
34 |
35 | if (!store.current?.store || store.current?.storeKey !== storeKey) {
36 | store.current = {
37 | storeKey,
38 | store: createChatStore({
39 | apiUrl,
40 | headers,
41 | projectKey,
42 | chatOptions,
43 | debug,
44 | persistChatHistory: chatOptions?.history,
45 | maxHistorySize: chatOptions?.maxHistorySize,
46 | storeKey,
47 | }),
48 | };
49 | }
50 |
51 | // update chat options when they change
52 | useEffect(() => {
53 | if (!chatOptions) return;
54 | store.current?.store?.getState().setOptions(chatOptions);
55 | }, [chatOptions]);
56 |
57 | return (
58 |
59 | {children}
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/packages/react/src/chat/supabase.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from '@supabase/supabase-js';
2 |
3 | export const createSupabaseClient = (url: string, key: string) =>
4 | createClient(url, key);
5 |
--------------------------------------------------------------------------------
/packages/react/src/context/global/provider.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, type ReactNode, type JSX } from 'react';
2 |
3 | import {
4 | createGlobalStore,
5 | type GlobalOptions,
6 | type GlobalStore,
7 | } from './store.js';
8 | import { GlobalStoreContext } from './store.js';
9 | import { getEnabledViews, getInitialView } from './utils.js';
10 |
11 | interface GlobalStoreProviderProps {
12 | options: GlobalOptions;
13 | children: ReactNode;
14 | }
15 |
16 | function updateStateOnOptionsChange(
17 | store: GlobalStore,
18 | nextOptions: GlobalOptions,
19 | ): void {
20 | const activeView = store.getState().activeView;
21 | const nextInitialView = getInitialView(nextOptions);
22 | const nextEnabledViews = getEnabledViews(nextOptions);
23 |
24 | // only change the active view to the nextInitialView if the current view is no longer enabled
25 | store?.setState({
26 | activeView: nextEnabledViews.includes(activeView)
27 | ? activeView
28 | : nextInitialView,
29 | options: nextOptions,
30 | });
31 | }
32 |
33 | export function GlobalStoreProvider(
34 | props: GlobalStoreProviderProps,
35 | ): JSX.Element {
36 | const { options, children } = props;
37 |
38 | const store = useRef(null);
39 |
40 | if (!store.current) {
41 | store.current = createGlobalStore(options);
42 | }
43 |
44 | useEffect(() => {
45 | if (!store.current) return;
46 | updateStateOnOptionsChange(store.current, options);
47 | }, [options]);
48 |
49 | return (
50 |
51 | {children}
52 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/packages/react/src/context/global/utils.ts:
--------------------------------------------------------------------------------
1 | import type { MarkpromptOptions, View } from '../../types.js';
2 | import { getDefaultView } from '../../utils.js';
3 |
4 | export function getInitialView(options: MarkpromptOptions): View {
5 | if (options.defaultView) {
6 | return getDefaultView(options.defaultView, options);
7 | }
8 |
9 | if (options?.search?.enabled) {
10 | return 'search';
11 | }
12 |
13 | return 'chat';
14 | }
15 |
16 | export function getEnabledViews(options: MarkpromptOptions): View[] {
17 | const views: View[] = ['chat'];
18 |
19 | if (options?.search?.enabled) {
20 | views.push('search');
21 | }
22 |
23 | if (typeof options?.integrations?.createTicket === 'string') {
24 | views.push('ticket');
25 | }
26 |
27 | return views;
28 | }
29 |
--------------------------------------------------------------------------------
/packages/react/src/feedback/Feedback.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from '@testing-library/react';
2 | import { userEvent } from '@testing-library/user-event';
3 | import { afterEach, describe, expect, test, vi } from 'vitest';
4 |
5 | import { Feedback } from './Feedback.js';
6 |
7 | describe('Feedback', () => {
8 | const submitFeedback = vi.fn(() => Promise.resolve());
9 | const abortFeedbackRequest = vi.fn();
10 |
11 | const messageId = 'test-message-id';
12 |
13 | afterEach(() => {
14 | vi.resetAllMocks();
15 | });
16 |
17 | test('render the Feedback component', () => {
18 | render(
19 | ,
26 | );
27 |
28 | const element = screen.getByTestId('test-feedback');
29 |
30 | expect(element).toBeInTheDocument();
31 | });
32 |
33 | test('render the Feedback component with the icons variant', () => {
34 | render(
35 | ,
42 | );
43 |
44 | const element = screen.getByTestId('test-feedback');
45 |
46 | expect(element).toBeInTheDocument();
47 | });
48 |
49 | test('thank the user when feedback was provided', async () => {
50 | const user = userEvent.setup();
51 |
52 | render(
53 | ,
59 | );
60 |
61 | const buttons = screen.getAllByRole('button');
62 | expect(buttons).toHaveLength(2);
63 |
64 | const yesButton = screen.getByText('Yes');
65 |
66 | await user.click(yesButton);
67 |
68 | await waitFor(() =>
69 | expect(submitFeedback).toHaveBeenCalledWith({ vote: '1' }, messageId),
70 | );
71 |
72 | expect(yesButton).toHaveAttribute('data-active', 'true');
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/packages/react/src/icons.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import { describe, expect, test } from 'vitest';
3 |
4 | import {
5 | ChatIcon,
6 | CheckIcon,
7 | ChevronLeftIcon,
8 | ChevronUpIcon,
9 | CloseIcon,
10 | CommandIcon,
11 | CornerDownLeftIcon,
12 | CounterClockwiseClockIcon,
13 | FileTextIcon,
14 | HashIcon,
15 | ReloadIcon,
16 | SearchIcon,
17 | SparklesIcon,
18 | StopIcon,
19 | ThumbsDownIcon,
20 | ThumbsUpIcon,
21 | PlusIcon,
22 | } from './icons.js';
23 |
24 | const icons = [
25 | ChatIcon,
26 | CheckIcon,
27 | ChevronLeftIcon,
28 | ChevronUpIcon,
29 | CloseIcon,
30 | CommandIcon,
31 | CornerDownLeftIcon,
32 | CounterClockwiseClockIcon,
33 | FileTextIcon,
34 | HashIcon,
35 | ReloadIcon,
36 | SearchIcon,
37 | SparklesIcon,
38 | StopIcon,
39 | ThumbsDownIcon,
40 | ThumbsUpIcon,
41 | PlusIcon,
42 | ];
43 |
44 | describe('icons', () => {
45 | for (const Icon of icons) {
46 | test(`renders the ${Icon.name} component`, () => {
47 | render( );
48 | const el = screen.getByTestId(`test-icon-${Icon.name}`);
49 | expect(el).toBeInTheDocument();
50 | });
51 | }
52 | });
53 |
--------------------------------------------------------------------------------
/packages/react/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Answer,
3 | AutoScroller,
4 | Close,
5 | Content,
6 | Description,
7 | DialogTrigger,
8 | Form,
9 | Overlay,
10 | PlainContent,
11 | Portal,
12 | Prompt,
13 | References,
14 | Root,
15 | SearchResult,
16 | SearchResults,
17 | Title,
18 | type AnswerProps,
19 | type AutoScrollerProps,
20 | type CloseProps,
21 | type ContentProps,
22 | type DescriptionProps,
23 | type DialogTriggerProps,
24 | type FormProps,
25 | type OverlayProps,
26 | type PortalProps,
27 | type PromptProps,
28 | type ReferencesProps,
29 | type RootProps,
30 | type SearchResultProps,
31 | type SearchResultsProps,
32 | type TitleProps,
33 | } from './primitives/headless.js';
34 |
35 | export {
36 | useFeedback,
37 | type UseFeedbackOptions,
38 | type UseFeedbackResult,
39 | } from './feedback/useFeedback.js';
40 |
41 | export {
42 | useSearch,
43 | type SearchLoadingState,
44 | type UseSearchOptions,
45 | type UseSearchResult,
46 | } from './search/useSearch.js';
47 |
48 | export {
49 | Markprompt,
50 | type MarkpromptProps,
51 | } from './Markprompt.js';
52 |
53 | export { Trigger } from './Trigger.js';
54 |
55 | export { ChatView, type ChatViewProps } from './chat/ChatView.js';
56 | export {
57 | createChatStore,
58 | useChatStore,
59 | type CreateChatOptions,
60 | } from './chat/store.js';
61 | export {
62 | ChatProvider,
63 | type ChatProviderProps,
64 | } from './chat/provider.js';
65 |
66 | export { SearchView, type SearchViewProps } from './search/SearchView.js';
67 |
68 | export {
69 | StandaloneTicketDeflectionForm,
70 | type StandaloneTicketDeflectionFormProps,
71 | } from './TicketDeflectionForm.js';
72 |
73 | export {
74 | emitter,
75 | openMarkprompt,
76 | closeMarkprompt,
77 | } from './utils.js';
78 |
79 | export { DEFAULT_MARKPROMPT_OPTIONS } from './constants.js';
80 |
81 | export * from './types.js';
82 |
--------------------------------------------------------------------------------
/packages/react/src/primitives/ConditionalWrap.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import { describe, expect, it } from 'vitest';
3 |
4 | import {
5 | ConditionalVisuallyHidden,
6 | ConditionalWrap,
7 | } from './ConditionalWrap.js';
8 |
9 | describe('ConditionalWrap', () => {
10 | it('renders', () => {
11 | const { rerender } = render(
12 | {children} }
15 | >
16 | test
17 | ,
18 | );
19 |
20 | expect(screen.getByRole('button')).toBeInTheDocument();
21 |
22 | rerender(
23 | {children} }
25 | >
26 | test
27 | ,
28 | );
29 |
30 | expect(screen.queryByRole('button')).not.toBeInTheDocument();
31 | });
32 | });
33 |
34 | describe('ConditionalVisuallyHidden', () => {
35 | it('renders', () => {
36 | const { rerender } = render(
37 | test ,
38 | );
39 |
40 | expect(screen.getByText('test')).toHaveStyle(
41 | 'position: absolute; border: 0px; width: 1px; height: 1px; padding: 0px; margin: -1px; overflow: hidden; clip: rect(0px, 0px, 0px, 0px); white-space: nowrap; word-wrap: normal;',
42 | );
43 |
44 | rerender(test );
45 |
46 | expect(screen.getByText('test')).not.toHaveStyle(
47 | 'position: absolute; border: 0px; width: 1px; height: 1px; padding: 0px; margin: -1px; overflow: hidden; clip: rect(0px, 0px, 0px, 0px); white-space: nowrap; word-wrap: normal;',
48 | );
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/packages/react/src/primitives/ConditionalWrap.tsx:
--------------------------------------------------------------------------------
1 | import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
2 | import type { ReactNode, JSX } from 'react';
3 |
4 | interface ConditionalWrapProps {
5 | condition?: boolean;
6 | wrap: (children: ReactNode) => ReactNode;
7 | children: ReactNode;
8 | }
9 |
10 | export const ConditionalWrap = (props: ConditionalWrapProps): ReactNode => {
11 | const { condition, wrap, children } = props;
12 | return condition ? wrap(children) : children;
13 | };
14 |
15 | interface ConditionalVisuallyHiddenProps {
16 | asChild?: boolean;
17 | children: ReactNode;
18 | hide?: boolean;
19 | }
20 |
21 | export const ConditionalVisuallyHidden = (
22 | props: ConditionalVisuallyHiddenProps,
23 | ): JSX.Element => {
24 | const { asChild, hide, children } = props;
25 | return (
26 | (
29 | {children}
30 | )}
31 | >
32 | {children}
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/packages/react/src/primitives/branding.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import { describe, expect, test } from 'vitest';
3 |
4 | import { Branding } from './branding.js';
5 |
6 | describe('Branding', () => {
7 | test('render a branding element', () => {
8 | render( );
9 |
10 | const element = screen.getByText(/Powered by/);
11 | expect(element).toBeInTheDocument();
12 | expect(element).toHaveTextContent('Powered by Markprompt AI');
13 |
14 | const anchor = screen.getByText('Markprompt AI');
15 | expect(anchor.href).toBe('https://markprompt.com/');
16 | });
17 |
18 | test('render a branding element with Algolia', () => {
19 | render( );
20 |
21 | const anchor = screen.getByLabelText('Algolia');
22 | expect(anchor.href).toBe('https://algolia.com/');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/packages/react/src/test-utils.ts:
--------------------------------------------------------------------------------
1 | // Server-sent events formatting code taken from:
2 | // https://github.com/rexxars/eventsource-parser/blob/main/test/format.ts
3 | export interface SseMessage {
4 | event?: string;
5 | retry?: number;
6 | id?: string;
7 | data: string;
8 | }
9 |
10 | export function formatEvent(message: SseMessage | string): string {
11 | const msg = typeof message === 'string' ? { data: message } : message;
12 |
13 | let output = '';
14 | if (msg.event) {
15 | output += `event: ${msg.event}\n`;
16 | }
17 |
18 | if (msg.retry) {
19 | output += `retry: ${msg.retry}\n`;
20 | }
21 |
22 | if (typeof msg.id === 'string' || typeof msg.id === 'number') {
23 | output += `id: ${msg.id}\n`;
24 | }
25 |
26 | output += encodeData(msg.data);
27 |
28 | return output;
29 | }
30 |
31 | export function formatComment(comment: string): string {
32 | return `:${comment}\n\n`;
33 | }
34 |
35 | export function encodeData(text: string): string {
36 | const data = String(text).replace(/(\r\n|\r|\n)/g, '\n');
37 | const lines = data.split(/\n/);
38 |
39 | let line = '';
40 | let output = '';
41 |
42 | for (let i = 0, l = lines.length; i < l; ++i) {
43 | line = lines[i]!;
44 |
45 | output += `data: ${line}`;
46 | output += i + 1 === l ? '\n\n' : '\n';
47 | }
48 |
49 | return output;
50 | }
51 |
52 | export function getChunk(
53 | content: string | null,
54 | tool_call: { name: string; parameters: string } | null,
55 | index: number,
56 | model = 'gpt-3.5-turbo',
57 | isLast?: boolean,
58 | ): string {
59 | return JSON.stringify({
60 | object: 'chat.completion.chunk',
61 | choices: [
62 | {
63 | delta: {
64 | content,
65 | role: 'assistant',
66 | ...(tool_call
67 | ? {
68 | tool_calls: [
69 | {
70 | id: crypto.randomUUID(),
71 | type: 'function',
72 | function: {
73 | name: tool_call?.name,
74 | parameters: tool_call?.parameters,
75 | },
76 | },
77 | ],
78 | }
79 | : {}),
80 | },
81 | finish_reason: isLast ? 'stop' : null,
82 | index,
83 | },
84 | ],
85 | created: Date.now(),
86 | model,
87 | });
88 | }
89 |
--------------------------------------------------------------------------------
/packages/react/src/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as AccessibleIcon from '@radix-ui/react-accessible-icon';
2 | import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
3 | import type { JSX } from 'react';
4 |
5 | import { cn } from './utils.js';
6 | import { CloseIcon } from '../icons.js';
7 | import * as BaseMarkprompt from '../primitives/headless.js';
8 | import type { MarkpromptOptions } from '../types.js';
9 | import { useMediaQuery } from '../useMediaQuery.js';
10 |
11 | interface NavigationMenuProps {
12 | className?: string;
13 | title?: string;
14 | subtitle?: string;
15 | close: MarkpromptOptions['close'];
16 | }
17 |
18 | const NavigationMenu = ({
19 | className,
20 | title,
21 | subtitle,
22 | close,
23 | }: NavigationMenuProps): JSX.Element => {
24 | const isTouchDevice = useMediaQuery('(pointer: coarse)');
25 | return (
26 |
27 |
28 |
{title}
29 | {subtitle && (
30 |
{subtitle}
31 | )}
32 |
33 | {close?.visible && (
34 |
35 |
39 |
40 | {isTouchDevice || close?.hasIcon ? (
41 |
42 | ) : (
43 | Esc
44 | )}
45 |
46 |
47 |
48 | )}
49 |
50 | );
51 | };
52 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
53 |
54 | export { NavigationMenu };
55 |
--------------------------------------------------------------------------------
/packages/react/src/ui/rich-text.tsx:
--------------------------------------------------------------------------------
1 | import type { ComponentType, JSX } from 'react';
2 | import Markdown from 'react-markdown';
3 |
4 | export function RichText(props: {
5 | children: string | ComponentType;
6 | className?: string;
7 | }): JSX.Element {
8 | if (typeof props.children === 'string') {
9 | return {props.children} ;
10 | }
11 | const Message = props.children;
12 | return ;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/react/src/ui/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from 'clsx';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export const cn = (...inputs: ClassValue[]): string => {
5 | return twMerge(clsx(inputs));
6 | };
7 |
--------------------------------------------------------------------------------
/packages/react/src/useAbortController.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef, type RefObject } from 'react';
2 |
3 | interface useAbortControllerResult {
4 | ref: RefObject;
5 | abort: () => void;
6 | }
7 |
8 | export function useAbortController(): useAbortControllerResult {
9 | const ref = useRef(undefined);
10 |
11 | const abort = useCallback(() => {
12 | if (ref.current) {
13 | ref.current.abort();
14 | ref.current = undefined;
15 | }
16 | }, []);
17 |
18 | return { ref, abort };
19 | }
20 |
--------------------------------------------------------------------------------
/packages/react/src/useDefaults.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 | import {
3 | cloneElement,
4 | type Attributes,
5 | type ReactElement,
6 | type ReactNode,
7 | } from 'react';
8 | import { describe, expect, it, vi } from 'vitest';
9 |
10 | import { useDefaults } from './useDefaults.js';
11 |
12 | vi.mock('react', async (importOriginal) => {
13 | const mod = await importOriginal();
14 | return {
15 | ...mod,
16 | cloneElement: vi.fn(
17 | (
18 | element: ReactElement,
19 | props: (Partial & Attributes) | undefined,
20 | ...children: ReactNode[]
21 | ) => {
22 | return mod.cloneElement(element, props, ...children);
23 | },
24 | ),
25 | };
26 | });
27 |
28 | describe('useDefaults', () => {
29 | it('should work', () => {
30 | const { result } = renderHook(() => useDefaults({}, { b: true }));
31 | expect(result.current).toEqual({
32 | b: true,
33 | });
34 | });
35 |
36 | it('should work with React components', () => {
37 | const A = A ;
38 | const B = B ;
39 |
40 | const { result } = renderHook(() => useDefaults({ A }, { B }));
41 | expect(result.current).toEqual({
42 | A,
43 | B,
44 | });
45 | expect(cloneElement).toHaveBeenCalled();
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/packages/react/src/useDefaults.ts:
--------------------------------------------------------------------------------
1 | import cloneDeepWith from 'lodash-es/cloneDeepWith.js';
2 | import defaults from 'lodash-es/defaultsDeep.js';
3 | import { cloneElement, isValidElement, useMemo } from 'react';
4 |
5 | type DeepMerge = T extends { [key: string]: unknown }
6 | ? U extends { [key: string]: unknown }
7 | ? {
8 | [K in keyof T | keyof U]: K extends keyof T
9 | ? K extends keyof U
10 | ? DeepMerge
11 | : T[K]
12 | : K extends keyof U
13 | ? U[K]
14 | : never;
15 | }
16 | : T
17 | : U extends undefined
18 | ? T extends undefined
19 | ? U
20 | : T
21 | : U;
22 |
23 | // biome-ignore lint/complexity/noBannedTypes: we need it to match the parameter type of isValidElement
24 | function isObjectOrNullish(value: unknown): value is {} | null | undefined {
25 | return (
26 | value === null ||
27 | value === undefined ||
28 | (typeof value === 'object' && !Array.isArray(value))
29 | );
30 | }
31 |
32 | // defaults only merges the first level of properties, we need to make sure that
33 | // deeper nested properties are merged as well.
34 | export function useDefaults<
35 | T extends { [key: string]: unknown },
36 | U extends { [key: string]: unknown } | undefined = undefined,
37 | >(options: T, defaultOptions: U): U extends undefined ? T : DeepMerge {
38 | return useMemo(
39 | // cloning options as defaultsDeep mutates the first argument
40 | () =>
41 | defaults(
42 | cloneDeepWith(options, (value) => {
43 | // don't clone React elements with lodash, they won't render properly after
44 | if (isObjectOrNullish(value) && isValidElement(value)) {
45 | return cloneElement(value);
46 | }
47 | }),
48 | defaultOptions,
49 | ) as U extends undefined ? T : DeepMerge,
50 | [defaultOptions, options],
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/packages/react/src/useMediaQuery.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export function useMediaQuery(mq: string): boolean {
4 | const [matches, setMatches] = useState(false);
5 |
6 | useEffect(() => {
7 | const mediaQuery = window.matchMedia(mq);
8 | const controller = new AbortController();
9 |
10 | setMatches(mediaQuery.matches);
11 |
12 | const listener = (): void => setMatches(mediaQuery.matches);
13 |
14 | mediaQuery.addEventListener('change', listener, {
15 | passive: true,
16 | signal: controller.signal,
17 | });
18 |
19 | return () => {
20 | controller.abort();
21 | };
22 | }, [mq]);
23 |
24 | return matches;
25 | }
26 |
--------------------------------------------------------------------------------
/packages/react/src/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from 'vitest';
2 |
3 | import {
4 | markdownToString,
5 | isPresent,
6 | isIterable,
7 | hasPresentKey,
8 | hasValueAtKey,
9 | } from './utils.js';
10 |
11 | describe('utils', () => {
12 | test('isIterable', () => {
13 | expect(isIterable(null)).toBe(false);
14 | expect(isIterable([])).toBe(true);
15 | });
16 |
17 | test('isPresent', () => {
18 | // Test case with a non-null and non-undefined value
19 | const value1 = 'Test String';
20 | const result1 = isPresent(value1);
21 | expect(result1).toBe(true);
22 |
23 | // Test case with a null value
24 | const value2: null = null;
25 | const result2 = isPresent(value2);
26 | expect(result2).toBe(false);
27 |
28 | // Test case with an undefined value
29 | const value3 = undefined;
30 | const result3 = isPresent(value3);
31 | expect(result3).toBe(false);
32 | });
33 |
34 | test('hasPresentKey', () => {
35 | const value1 = { foo: 'bar' };
36 | const value2 = { bar: 'foo' };
37 | const test = hasPresentKey('foo');
38 | expect(test(value1)).toBe(true);
39 | expect(test(value2)).toBe(false);
40 | });
41 |
42 | test('hasValueAtKey', () => {
43 | const value1 = { foo: 'bar' };
44 | const value2 = { bar: 'foo' };
45 | const test = hasValueAtKey('foo', 'bar');
46 | expect(test(value1)).toBe(true);
47 | // @ts-expect-error - testing false input
48 | expect(test(value2)).toBe(false);
49 | });
50 |
51 | test('markdownToString', () => {
52 | // Test case with markdown text fitting in maxLength
53 | const testMarkdown1 = '# Hello Vitest';
54 | const expectedString1 = 'Hello Vitest';
55 |
56 | const result1 = markdownToString(testMarkdown1);
57 | expect(result1).toBe(expectedString1);
58 |
59 | // Test case with markdown text exceeding maxLength and being trimmed
60 | const testMarkdown2 =
61 | '# This is a very very long markdown string just for the sake of this unit testing, I promise!';
62 | const expectedString2 = 'This is a very very long markdown string just f…';
63 |
64 | const result2 = markdownToString(testMarkdown2, 47);
65 |
66 | expect(result2).toBe(expectedString2);
67 |
68 | // Test case with void input
69 | const expectedString3 = '';
70 | const result3 = markdownToString();
71 | expect(result3).toBe(expectedString3);
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/packages/react/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | ".turbo/",
5 | "**/node_modules",
6 | "dist/",
7 | "src/**/*.test.ts",
8 | "src/**/*.test.tsx",
9 | "src/test-utils.ts"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // base
4 | "esModuleInterop": true,
5 | "skipLibCheck": true,
6 | "target": "es2023",
7 | "moduleDetection": "force",
8 | "isolatedModules": true,
9 | "checkJs": true,
10 |
11 | // strictness
12 | "strict": true,
13 | "types": ["@testing-library/jest-dom"],
14 |
15 | // transpilation
16 | "module": "nodenext",
17 | "moduleResolution": "nodenext",
18 | "rootDir": "src/",
19 | "outDir": "dist/",
20 |
21 | "sourceMap": true,
22 | "declaration": true,
23 | "declarationMap": true,
24 | "jsx": "react-jsx",
25 | "verbatimModuleSyntax": true,
26 |
27 | // runtime
28 | "lib": ["dom", "dom.iterable", "es2023"]
29 | },
30 | "include": ["src/"],
31 | "exclude": [".turbo/", "**/node_modules", "dist/"]
32 | }
33 |
--------------------------------------------------------------------------------
/packages/react/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react-swc';
2 | import tsconfigPaths from 'vite-tsconfig-paths';
3 | import { defineProject, mergeConfig } from 'vitest/config';
4 |
5 | export default mergeConfig(
6 | {
7 | plugins: [tsconfigPaths({ projects: ['./tsconfig.json'] }), react()],
8 | coverage: {
9 | enabled: true,
10 | provider: 'v8',
11 | include: ['./src/**/*'],
12 | },
13 | },
14 | defineProject({
15 | test: {
16 | environment: 'jsdom',
17 | setupFiles: ['./vitest.setup.ts'],
18 |
19 | // See https://vitest.dev/guide/common-errors.html#failed-to-terminate-worker for why this is enabled
20 | pool: 'forks',
21 | },
22 | }),
23 | );
24 |
--------------------------------------------------------------------------------
/packages/react/vitest.setup.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/vitest';
2 | import { cleanup } from '@testing-library/react';
3 | import { afterEach, beforeAll, vi } from 'vitest';
4 |
5 | vi.mock('zustand');
6 |
7 | beforeAll(() => {
8 | vi.spyOn(window, 'matchMedia').mockImplementation((query) => ({
9 | matches: false,
10 | media: query,
11 | onchange: null,
12 | addListener: vi.fn(),
13 | removeListener: vi.fn(),
14 | addEventListener: vi.fn(),
15 | removeEventListener: vi.fn(),
16 | dispatchEvent: vi.fn(),
17 | }));
18 |
19 | // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional
20 | Element.prototype.scrollTo = () => {};
21 | // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional
22 | Element.prototype.scrollIntoView = () => {};
23 | });
24 |
25 | afterEach(() => {
26 | cleanup();
27 | });
28 |
--------------------------------------------------------------------------------
/packages/web/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Motif
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/web/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { configs } from '@markprompt/eslint-config';
2 |
3 | export default [
4 | ...configs.base(import.meta.dirname, [
5 | 'eslint.config.js',
6 | 'vitest.config.js',
7 | 'scripts/*.js',
8 | ]),
9 | {
10 | rules: {
11 | '@typescript-eslint/consistent-indexed-object-style': [
12 | 'error',
13 | 'index-signature',
14 | ],
15 | },
16 | },
17 | ...configs.react,
18 | ...configs.vitest,
19 | ];
20 |
--------------------------------------------------------------------------------
/packages/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@markprompt/web",
3 | "version": "0.48.6",
4 | "description": "A web component for adding GPT-4 powered search using the Markprompt API.",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/markprompt/markprompt-js.git",
8 | "directory": "packages/web"
9 | },
10 | "license": "MIT",
11 | "author": "Markprompt",
12 | "type": "module",
13 | "exports": {
14 | ".": "./dist/index.js",
15 | "./init": "./dist/init.js"
16 | },
17 | "main": "dist/index.js",
18 | "files": ["dist/globals.*", "dist/index.*", "dist/init.*", "dist/types.*"],
19 | "scripts": {
20 | "analyze": "node scripts/analyze.js",
21 | "build": "node scripts/build.js",
22 | "dev": "node scripts/dev.js",
23 | "lint:js": "eslint .",
24 | "lint:ts": "tsc --build --noEmit",
25 | "prepack": "node scripts/build.js"
26 | },
27 | "dependencies": {
28 | "@markprompt/core": "workspace:*",
29 | "@markprompt/react": "workspace:*",
30 | "lodash-es": "^4.17.21"
31 | },
32 | "devDependencies": {
33 | "@types/react": "^19.0.1",
34 | "@types/react-dom": "^19.0.2",
35 | "esbuild": "^0.24.0",
36 | "preact": "^10.25.2",
37 | "react": "npm:@preact/compat",
38 | "react-dom": "npm:@preact/compat"
39 | },
40 | "publishConfig": {
41 | "access": "public"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/web/scripts/analyze.js:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises';
2 |
3 | import * as esbuild from 'esbuild';
4 |
5 | import { config } from './config.js';
6 |
7 | const result = await esbuild.build({ ...config, minify: true, metafile: true });
8 |
9 | // outputs a `meta.json` which can be uploaded to https://esbuild.github.io/analyze/
10 | void fs.writeFile('meta.json', JSON.stringify(result.metafile));
11 |
12 | // outputs an analysis to the console
13 | console.debug(await esbuild.analyzeMetafile(result.metafile));
14 |
--------------------------------------------------------------------------------
/packages/web/scripts/build.js:
--------------------------------------------------------------------------------
1 | import * as esbuild from 'esbuild';
2 |
3 | import { config } from './config.js';
4 |
5 | await esbuild.build({ ...config, minify: true });
6 |
--------------------------------------------------------------------------------
/packages/web/scripts/config.js:
--------------------------------------------------------------------------------
1 | import path from 'node:path';
2 |
3 | import { tscPlugin } from './tsc-plugin.js';
4 |
5 | /**
6 | * Shared configuration options for esbuild
7 | * @type {import('esbuild').BuildOptions}
8 | **/
9 | const config = {
10 | entryPoints: ['src/index.tsx', 'src/init.ts'],
11 | outdir: 'dist/',
12 | format: 'esm',
13 | target: 'es2023',
14 | tsconfig: path.resolve(import.meta.dirname, '..', 'tsconfig.json'),
15 | bundle: true,
16 | treeShaking: true,
17 | sourcemap: true,
18 | jsx: 'automatic',
19 | alias: {
20 | react: 'preact/compat',
21 | 'react-dom': 'preact/compat',
22 | lodash: 'lodash-es',
23 | },
24 | plugins: [tscPlugin],
25 | };
26 |
27 | export { config };
28 |
--------------------------------------------------------------------------------
/packages/web/scripts/dev.js:
--------------------------------------------------------------------------------
1 | import * as esbuild from 'esbuild';
2 |
3 | import { config } from './config.js';
4 |
5 | const ctx = await esbuild.context({
6 | ...config,
7 | jsxDev: true,
8 | minify: false,
9 | external: ['@markprompt/core'],
10 | });
11 |
12 | await ctx.watch();
13 |
--------------------------------------------------------------------------------
/packages/web/scripts/tsc-plugin.js:
--------------------------------------------------------------------------------
1 | import { execSync } from 'node:child_process';
2 |
3 | /**
4 | * This esbuild plugin runs tsc to generate type declaration files
5 | * @type {import('esbuild').Plugin}
6 | **/
7 | export const tscPlugin = {
8 | name: 'tsc',
9 | setup(build) {
10 | build.onEnd(() => {
11 | execSync('tsc --build', {
12 | stdio: 'inherit',
13 | });
14 | });
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/packages/web/src/init.ts:
--------------------------------------------------------------------------------
1 | import type { MarkpromptOptions } from '@markprompt/react';
2 |
3 | import { closeMarkprompt, markprompt, openMarkprompt } from './index.js';
4 |
5 | declare global {
6 | interface Window {
7 | markprompt?: {
8 | projectKey: string;
9 | container?: HTMLElement | string;
10 | options?: MarkpromptOptions;
11 | open?: typeof openMarkprompt;
12 | close?: typeof closeMarkprompt;
13 | };
14 | }
15 | }
16 |
17 | if (!window.markprompt) {
18 | throw new Error(
19 | 'Markprompt configuration not found on window. See: https://markprompt.com/docs#script-tag',
20 | );
21 | }
22 |
23 | let { container } = window.markprompt;
24 |
25 | if (!container) {
26 | container = document.createElement('div');
27 | container.id = 'markprompt';
28 | document.body.appendChild(container);
29 | }
30 |
31 | const { projectKey, options } = window.markprompt;
32 |
33 | if (!projectKey) {
34 | throw new Error(
35 | 'Markprompt project key not found on window. Find your project key in the project settings on https://markprompt.com/',
36 | );
37 | }
38 |
39 | window.markprompt.open = openMarkprompt;
40 | window.markprompt.close = closeMarkprompt;
41 |
42 | markprompt(projectKey, container, options);
43 |
--------------------------------------------------------------------------------
/packages/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // base
4 | "esModuleInterop": true,
5 | "skipLibCheck": true,
6 | "target": "es2023",
7 | "moduleDetection": "force",
8 | "isolatedModules": true,
9 | "allowJs": true,
10 | "checkJs": true,
11 |
12 | // strictness
13 | "strict": true,
14 |
15 | // transpilation
16 | "module": "esnext",
17 | "moduleResolution": "bundler",
18 | "emitDeclarationOnly": true,
19 | "outDir": "dist/",
20 | "rootDir": "src/",
21 |
22 | "sourceMap": true,
23 | "declaration": true,
24 | "declarationMap": true,
25 | "composite": true,
26 | "jsx": "preserve",
27 | "verbatimModuleSyntax": true,
28 |
29 | "baseUrl": "./",
30 | "paths": {
31 | "react": ["./node_modules/preact/compat/"],
32 | "react/jsx-runtime": ["./node_modules/preact/jsx-runtime"],
33 | "react-dom": ["./node_modules/preact/compat/"],
34 | "react-dom/*": ["./node_modules/preact/compat/*"]
35 | },
36 |
37 | // runtime
38 | "lib": ["dom", "dom.iterable", "es2023"]
39 | },
40 | "include": ["src/"],
41 | "exclude": ["**/node_modules/", "dist/", ".turbo/"]
42 | }
43 |
--------------------------------------------------------------------------------
/packages/web/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineProject } from 'vitest/config';
2 |
3 | export default defineProject({});
4 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'examples/*'
3 | - 'packages/*'
4 |
--------------------------------------------------------------------------------
/test/utils.ts:
--------------------------------------------------------------------------------
1 | // Server-sent events formatting code taken from:
2 | // https://github.com/rexxars/eventsource-parser/blob/main/test/format.ts
3 | export interface SseMessage {
4 | event?: string;
5 | retry?: number;
6 | id?: string;
7 | data: string;
8 | }
9 |
10 | export function formatEvent(message: SseMessage | string): string {
11 | const msg = typeof message === 'string' ? { data: message } : message;
12 |
13 | let output = '';
14 | if (msg.event) {
15 | output += `event: ${msg.event}\n`;
16 | }
17 |
18 | if (msg.retry) {
19 | output += `retry: ${msg.retry}\n`;
20 | }
21 |
22 | if (typeof msg.id === 'string' || typeof msg.id === 'number') {
23 | output += `id: ${msg.id}\n`;
24 | }
25 |
26 | output += encodeData(msg.data);
27 |
28 | return output;
29 | }
30 |
31 | export function formatComment(comment: string): string {
32 | return `:${comment}\n\n`;
33 | }
34 |
35 | export function encodeData(text: string): string {
36 | const data = String(text).replace(/(\r\n|\r|\n)/g, '\n');
37 | const lines = data.split(/\n/);
38 |
39 | let line = '';
40 | let output = '';
41 |
42 | for (let i = 0, l = lines.length; i < l; ++i) {
43 | line = lines[i];
44 |
45 | output += `data: ${line}`;
46 | output += i + 1 === l ? '\n\n' : '\n';
47 | }
48 |
49 | return output;
50 | }
51 |
52 | export function getChunk(
53 | content: string | null,
54 | index: number,
55 | model = 'gpt-3.5-turbo',
56 | isLast?: boolean,
57 | ): string {
58 | return JSON.stringify({
59 | object: 'chat.completion.chunk',
60 | choices: [
61 | {
62 | delta: {
63 | content,
64 | ...(index === 0 ? { role: 'assistant' } : {}),
65 | },
66 | finish_reason: isLast ? 'stop' : null,
67 | index,
68 | },
69 | ],
70 | created: Date.now(),
71 | model,
72 | });
73 | }
74 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | /* Base Options: */
5 | "esModuleInterop": true,
6 | "skipLibCheck": true,
7 | "target": "es2023",
8 | "allowJs": true,
9 | "checkJs": true,
10 | "resolveJsonModule": true,
11 | "moduleDetection": "force",
12 | "isolatedModules": true,
13 | "verbatimModuleSyntax": true,
14 |
15 | /* Strictness */
16 | "strict": true,
17 | "noUncheckedIndexedAccess": true,
18 | "noImplicitOverride": true,
19 |
20 | /* If NOT transpiling with TypeScript: */
21 | "module": "preserve",
22 | "noEmit": true,
23 |
24 | /* If your code doesn't run in the DOM: */
25 | "lib": ["es2023"]
26 | },
27 | "include": ["eslint.config.mjs", "test/"],
28 | "exclude": ["**/node_modules", "examples/", "packages/"]
29 | }
30 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": ["NODE_ENV", "ENABLE_EXPERIMENTAL_COREPACK"],
4 | "tasks": {
5 | "build": {
6 | "outputs": ["dist/**", ".next/**", "!.next/cache/**", "build/**"],
7 | "dependsOn": ["^build"]
8 | },
9 | "dev": {
10 | "persistent": true,
11 | "cache": false
12 | },
13 | "//#lint:biome": {},
14 | "//#lint:biome:ci": {},
15 | "//#lint:css": {},
16 | "//#lint:js": {
17 | "dependsOn": ["@markprompt/eslint-config#build"]
18 | },
19 | "lint:js": {
20 | "dependsOn": ["^build", "@markprompt/eslint-config#build"]
21 | },
22 | "//#lint:md": {},
23 | "lint:ts": {
24 | "dependsOn": ["^build"]
25 | },
26 | "//#test": {
27 | "outputs": ["coverage/**"]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/vitest.workspace.ts:
--------------------------------------------------------------------------------
1 | import { defineWorkspace } from 'vitest/config';
2 |
3 | export default defineWorkspace(['packages/*']);
4 |
--------------------------------------------------------------------------------