diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..5fcf9011b Binary files /dev/null and b/.DS_Store differ diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 000000000..5ef78cb3f --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,45 @@ +--- +name: Issue Report +about: Report a bug, suggest an enhancement, or ask a question +title: "" +labels: "" +assignees: "" +--- + +## Issue Type + +Please inform the type(s) of issue(s) you are reporting: + +- Bug Report +- Feature Request +- Discussion +- Question + +## Description + +Please provide a clear and concise description of the issue or enhancement. +Include any relevant information that could help reproduce the issue or +understand the request. + +## Steps to Reproduce (for bugs) + +1. Step one +2. Step two +3. ... + +## Expected Behavior + +Describe what you expected to happen. + +## Actual Behavior + +Describe what actually happened. + +## Additional Context + +Add any other context about the issue here, including screenshots, logs, or +other supporting information. + +--- + +Thank you for taking the time to report this issue! diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c1ea21385..988cd8c60 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,18 @@ -## What is this contribution about? +## What is this Contribution About? -## Loom -> Record a quick screencast describing your changes to show the team and help reviewers. +Please provide a brief description of the changes or enhancements you are proposing in this pull request. -## Link -> Please provide a link to a branch that demonstrates this pull request in action. +## Issue Link +Please link to the relevant issue that this pull request addresses: + +- Issue: [#ISSUE_NUMBER](link_to_issue) + +## Loom Video + +> Record a quick screencast describing your changes to help the team understand and review your contribution. This will greatly assist in the review process. + +## Demonstration Link + +> Provide a link to a branch or environment where this pull request can be tested and seen in action. diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b7384362d..d9ea4d87c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,11 +26,11 @@ jobs: path: | /home/runner/.deno /home/runner/.cache/deno/deps/https/deno.land - - uses: denoland/setup-deno@v1 + - uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - name: Bundle Apps - run: deno run -A --lock=deno.lock --lock-write --reload scripts/start.ts + run: deno run -A --lock=deno.lock --frozen=false --reload scripts/start.ts - name: Check run: deno task check @@ -47,8 +47,8 @@ jobs: - name: Test continue-on-error: true - run: deno test --lock=deno.lock --lock-write -A . + run: deno test --lock=deno.lock --frozen=false -A . - name: Benchmark continue-on-error: true - run: deno bench --lock=deno.lock --lock-write -A . \ No newline at end of file + run: deno bench --lock=deno.lock --frozen=false -A . diff --git a/.github/workflows/issues.yaml b/.github/workflows/issues.yaml new file mode 100644 index 000000000..d5f99f015 --- /dev/null +++ b/.github/workflows/issues.yaml @@ -0,0 +1,18 @@ +name: Label issues +on: + issues: + types: + - reopened + - opened +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - run: gh issue edit "$NUMBER" --add-label "$LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABELS: triage diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 74c872444..c7e75e636 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -6,7 +6,11 @@ on: # yamllint disable-line rule:truthy push: tags: - "**" - + workflow_dispatch: # Allows manual dispatch with parameters + inputs: + tag_name: + description: "The tag to be published" + required: true permissions: write-all jobs: release: @@ -15,10 +19,9 @@ jobs: runs-on: "ubuntu-latest" steps: - - name: "Determine tag" - run: 'echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV' - - name: "Create release" + env: + RELEASE_TAG: ${{ github.event.inputs.tag_name || github.ref_name }} uses: "actions/github-script@v6" with: github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/releaser.yaml b/.github/workflows/releaser.yaml new file mode 100644 index 000000000..f4fb8d2a0 --- /dev/null +++ b/.github/workflows/releaser.yaml @@ -0,0 +1,172 @@ +name: Release Tagging + +on: + pull_request_target: + types: [opened] + + push: + branches: + - main + +permissions: + contents: write # Necessary for accessing and modifying repository content + pull-requests: write # Necessary for interacting with pull requests + actions: write # Necessary for triggering other workflows + +jobs: + tag-discussion: + if: github.event_name == 'pull_request_target' && github.event.action == 'opened' + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} # Checkout the base branch (target repository) + repository: ${{ github.event.pull_request.base.repo.full_name }} # Checkout from the target repo + + - name: Calculate new versions + id: calculate_versions + run: | + git fetch --tags + LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) + if [ -z "$LATEST_TAG" ]; then + LATEST_TAG="0.0.0" + fi + MAJOR=$(echo $LATEST_TAG | cut -d. -f1) + MINOR=$(echo $LATEST_TAG | cut -d. -f2) + PATCH=$(echo $LATEST_TAG | cut -d. -f3) + NEW_PATCH_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" + NEW_MINOR_VERSION="$MAJOR.$((MINOR + 1)).0" + NEW_MAJOR_VERSION="$((MAJOR + 1)).0.0" + echo "patch_version=$NEW_PATCH_VERSION" >> $GITHUB_OUTPUT + echo "minor_version=$NEW_MINOR_VERSION" >> $GITHUB_OUTPUT + echo "major_version=$NEW_MAJOR_VERSION" >> $GITHUB_OUTPUT + + - name: Start Discussion for Tagging + uses: peter-evans/create-or-update-comment@v2 + id: comment + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Tagging Options + Should a new tag be published when this PR is merged? + - πŸ‘ for **Patch** ${{ steps.calculate_versions.outputs.patch_version }} update + - πŸŽ‰ for **Minor** ${{ steps.calculate_versions.outputs.minor_version }} update + - πŸš€ for **Major** ${{ steps.calculate_versions.outputs.major_version }} update + + determine-tag: + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Find the Merged Pull Request + id: find_pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BASE_BRANCH="main" + RECENT_PR=$(gh pr list --state closed --base $BASE_BRANCH --json number,title,closedAt --jq '.[] | select(.closedAt >= "'$(date -u -d '1 minute ago' +%Y-%m-%dT%H:%M:%SZ)'") | {number, title}') + echo "RECENT_PR=$RECENT_PR" >> $GITHUB_ENV + echo "PR_NUMBER=$(echo $RECENT_PR | jq -r '.number')" >> $GITHUB_ENV + + - name: Fetch latest stable tag (excluding prerelease tags) + id: get_latest_tag + run: | + git fetch --tags + LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) + if [ -z "$LATEST_TAG" ]; then + LATEST_TAG="0.0.0" + fi + echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT + + - name: Determine the next version based on comments + id: determine_version + if: env.PR_NUMBER != '' + env: + PR_NUMBER: ${{ env.PR_NUMBER }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + LATEST_TAG=${{ steps.get_latest_tag.outputs.latest_tag }} + MAJOR=$(echo $LATEST_TAG | cut -d. -f1) + MINOR=$(echo $LATEST_TAG | cut -d. -f2) + PATCH=$(echo $LATEST_TAG | cut -d. -f3) + + # Define allowed users as a JSON array + ALLOWED_USERS=$(cat MAINTAINERS.txt | jq -R -s -c 'split("\n")[:-1]') + echo "Maintainers list: $ALLOWED_USERS" + + # Fetch reactions and filter by allowed users + REACTION=$(gh api graphql -f query=' + query { + repository(owner:"${{ github.repository_owner }}", name:"${{ github.event.repository.name }}") { + pullRequest(number: '${PR_NUMBER}') { + comments(last: 100) { + nodes { + body + id + reactions(last: 100) { + nodes { + content + user { + login + } + } + } + } + } + } + } + }' | jq -r --argjson allowed_users "$ALLOWED_USERS" ' + .data.repository.pullRequest.comments.nodes[] | + select(.body | contains("## Tagging Options")) | + .reactions.nodes[] | + select(.user.login | IN($allowed_users[])) | + .content' + ) + # Print the reaction to debug + echo "Captured reaction: $REACTION" + + # Convert reaction to uppercase to handle any case inconsistencies + REACTION=$(echo "$REACTION" | tr '[:lower:]' '[:upper:]') + + # Determine the new tag version based on the allowed reactions + if [[ "$REACTION" == *"ROCKET"* ]]; then + NEW_TAG="$((MAJOR + 1)).0.0" + elif [[ "$REACTION" == *"HOORAY"* ]]; then + NEW_TAG="$MAJOR.$((MINOR + 1)).0" + elif [[ "$REACTION" == *"THUMBS_UP"* ]]; then # Ensure thumbs up reaction is correctly identified + NEW_TAG="$MAJOR.$MINOR.$((PATCH + 1))" + else + echo "No valid reactions found for version bump. Exiting." + exit 0 + fi + + + echo "new_version=$NEW_TAG" >> $GITHUB_OUTPUT + + - name: Update deno.json Version + if: steps.determine_version.outputs.new_version != '' + run: | + jq --arg new_version "${{ steps.determine_version.outputs.new_version }}" '.version = $new_version' deno.json > tmp.$$.json && mv tmp.$$.json deno.json + git config user.name "decobot" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add deno.json + git commit -m "Update version to ${{ steps.determine_version.outputs.new_version }}" + git push origin main + + - name: Create and Push Tag + if: steps.determine_version.outputs.new_version != '' + run: | + git tag ${{ steps.determine_version.outputs.new_version }} + git push origin ${{ steps.determine_version.outputs.new_version }} + + - name: Trigger Release Workflow + run: | + curl -X POST \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.everest-preview+json" \ + https://api.github.com/repos/${{ github.repository }}/actions/workflows/release.yaml/dispatches \ + -d '{"ref":"main", "inputs":{"tag_name":"${{ steps.determine_version.outputs.new_version }}"}}' diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml deleted file mode 100644 index 7fd045dcf..000000000 --- a/.github/workflows/update.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: update - -on: - schedule: - - cron: 0 0 * * * - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -jobs: - update: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Run updater - uses: boywithkeyboard/updater@v0 diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md new file mode 100644 index 000000000..e967f6cd7 --- /dev/null +++ b/CODE-OF-CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at {{ email }}. All complaints will be +reviewed and investigated and will result in a response that is deemed necessary +and appropriate to the circumstances. The project team is obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5114d78e1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,88 @@ +# Contributing Guidelines + +Thank you for your interest in contributing to the `deco-cx/apps` repository! We +are excited to have community members like you involved in improving our +collection of powerful apps. This document outlines how you can contribute +effectively to the project. + +## How to Contribute + +### Issues + +When submitting an issue, please use one of the following types: + +- **Issue/Bug**: Report bugs or track existing issues. +- **Issue/Discussion**: Start a discussion to gather input on a topic before it + becomes a proposal. +- **Issue/Proposal**: Propose a new idea or functionality. This allows for + feedback before any code is written. +- **Issue/Question**: Ask for help or clarification on any topic related to the + project. + +### Before You File an Issue + +Before submitting an issue, ensure the following: + +1. **Correct Repository**: Verify that you are filing the issue in the correct + repository within the deco ecosystem. +2. **Existing Issues**: Search through [open issues](./issues) to check if the + issue has already been reported or the feature has already been requested. +3. **For Bugs**: + - Confirm that it’s not an environment-specific issue. Ensure all + prerequisites (e.g., dependencies, configurations) are met. + - Provide detailed logs, stack traces, or any other relevant data to help + diagnose the issue. +4. **For Proposals**: + - Discuss potential features in the appropriate issue to gather feedback + before coding. + +### Pull Requests + +We welcome contributions via pull requests (PRs). Follow this workflow to submit +your changes: + +1. **Issue Reference**: Ensure there is an issue raised that corresponds to your + PR. +2. **Fork and Branch**: Fork the repository and create a new branch for your + changes. +3. **Code Changes**: + - Include appropriate tests with your code changes. + - Run linters and format the code according to project standards: + - Run `deno task check` +4. **Documentation**: Update any relevant documentation with your changes. +5. **Commit and PR**: Commit your changes and submit a PR for review. +6. **CI Checks**: Ensure that all Continuous Integration (CI) checks pass + successfully. +7. **Review Process**: A maintainer will review your PR, usually within a few + days. + +#### Work-in-Progress (WIP) PRs + +If you’d like early feedback on your work, you can create a PR with the prefix +`[WIP]` to indicate that it is still under development and not ready for +merging. + +### Testing Your Contributions + +Since this repository contains integrations that must be tested against a deco +site, you cannot test your contributions in isolation. Please refer to this +[Helpful content](https://www.notion.so/decocx/Helpful-Community-Content-101eec7ce64f4becaebb685dd9571e24) +for instructions on how to set up a deco site for testing purposes. + +### Use of Third-Party Code + +- Ensure that any third-party code included in your contributions comes with the + appropriate licenses. + +### Releasing a New Version + +We follow semantic versioning, and all apps in this repository are versioned +collectively using git tags. To release a new version: + +1. Fork the repository and create a pull request with your changes. +2. After the PR is approved and merged, request a maintainer to react the + releaser comment with the required emoji πŸ‘ for **Patch** πŸŽ‰ for **Minor** πŸš€ + for **Major** + +When your PR got merged, a new tag will arrive with the desired semver +modification. diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt new file mode 100644 index 000000000..70e7f287b --- /dev/null +++ b/MAINTAINERS.txt @@ -0,0 +1,3 @@ +guitavano +matheusgr +viktormarinho diff --git a/README.md b/README.md index 2b0da2402..7d7d566cd 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -
- +
+ Discord -   - Deco Twitter   - ![Build Status](https://github.com/deco-cx/apps/workflows/ci/badge.svg?event=push&branch=main) +Deco Twitter +  +![Build Status](https://github.com/deco-cx/apps/workflows/ci/badge.svg?event=push&branch=main)
@@ -30,85 +30,58 @@ At the core of all websites within this repository is the `website` app, located ## Contributing -Contributions to the `deco-cx/apps` repository are highly encouraged! We maintain an open and collaborative environment where community members are valued and respected. When contributing, please follow our contribution guidelines and treat others with kindness and professionalism. Our review process ensures high-quality contributions that align with the repository's goals. - -We adhere to **semantic versioning**, and all apps within this repository are versioned collectively using git tags. To release a new version, simply fork the repository, open a pull request, and once approved, request any maintainer to run `deno task release`. There are no strict rules about release frequency, so you can release updates as soon as your changes are merged into the main branch. - -### Developing a new app - -Just run: - -```sh -deno task new `APP_NAME` && deno task start -``` - -The app folder and the `deco.ts` will be changed in order to support your newly created app. - -Then you can run the watcher in a site that you own, - -```sh -deno task watcher $SITE_NAME -``` - -## Transition from deco-sites/std to deco-cx/apps - -This repository isn't a deco site as it used to be in `deco-sites/std`. In the past, we used `deco-sites/std` as a central hub for various platform integrations. Now, we've modularized these integrations into smaller, installable, and composable apps. These apps now reside in the `deco-cx/apps` repository. Additionally, the `/compat` folder contains two apps, `$live` and `deco-sites/std`, which serve as drop-in replacements for the older apps that contained all blocks together. As users progressively adopt the new apps, they can take full advantage of app extensibility and enhance their websites' capabilities. - -We're excited to have you as part of the Deco community and look forward to seeing the incredible apps and websites you'll create using the `deco-cx/apps` repository. Happy coding! - -For more information, check out our documentation at [https://deco.cx/docs](https://deco.cx/docs). +Check [CONTRIBUTING.md](https://github.com/deco-cx/apps/blob/main/CONTRIBUTING.md) ### Apps -| App Name | Description | Manifest | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------- | -| Algolia | Algolia search integration. Provides loaders and workflows for searching and indexing on Algolia | [manifest](/algolia/manifest.gen.ts) | -| ai-assistant | AI Assistant is a hub for artificial intelligence (AI) assistants, allowing you to register your own AI assistants and invoke them through automated actions. | [manifest](/ai-assistant/manifest.gen.ts) | -| analytics | Analytics is a powerful data analysis tool, providing valuable insights into the performance and behavior of users on your application or platform. | [manifest](/analytics/manifest.gen.ts) | -| brand-assistant | Brand Assistant is a brand assistance tool. | [manifest](/brand-assistant/manifest.gen.ts) | -| commerce | A simple configurable start for any e-commerce platform, lets you switch between any of those | [manifest](/commerce/manifest.gen.ts) | -| $live | An app for compatibility with $live blocks. | [manifest](/compat/$live/manifest.gen.ts) | -| deco-sites/std | An app for compatibility with deco-sites/std app, contains various blocks merged from e-commerce apps. | [manifest](/compat/std/manifest.gen.ts) | -| decohub | The best place to find an app for your business case, here is where apps published by any developer in the deco ecosystem will live. | [manifest](/decohub/manifest.gen.ts) | -| implementation | App for project implementation details. | [manifest](/implementation/manifest.gen.ts) | -| Konfidency | An app that uses extension block to add Aggregate Rating to products by integrating with the "[Konfidency](https://www.konfidency.com.br/)" provider. | [manifest](/konfidency/manifest.gen.ts) | -| Linx | The app for e-commerce that uses Linx as a platform. | [manifest](/linx/manifest.gen.ts) | -| nuvemshop | The app for e-commerce that uses Nuvemshop as a platform. | [manifest](/nuvemshop/manifest.gen.ts) | -| openai | Connects to OpenAI services to generate AI-powered content. | [manifest](/openai/manifest.gen.ts) | -| power-reviews | Power Reviews is an integration to show reviews and ratings of your products. It allow your customers to give a rating, write texts, emphasis pros/cons and upload images and videos. | [manifest](/power-reviews/manifest.gen.ts) | -| Resend | App for sending emails using [https://resend.com/](https://resend.com/) | [manifest](/resend/manifest.gen.ts) | -| EmailJS | App for sending emails using [https://www.emailjs.com/](https://www.emailjs.com/) | [manifest](/emailjs/manifest.gen.ts) | -| Shopify | The app for e-commerce that uses Shopify as a platform. | [manifest](/shopify/manifest.gen.ts) | -| sourei | Digitalize your business with Sourei. Offering a wide range of digital solutions, from domain registration to advanced project infrastructure. | [manifest](/sourei/manifest.gen.ts) | -| typesense | Typesense search integration. Provides loaders and workflows for searching and indexing on Typesense. | [manifest](/typesense/manifest.gen.ts) | -| Verified Reviews | An app that uses extension block to add Aggregate Rating to products by integrating with the "[OpiniΓ΅es Verificadas](https://www.opinioes-verificadas.com.br/br/)" provider. | [manifest](/verified-reviews/manifest.gen.ts) | -| VNDA | The app for e-commerce that uses VNDA as a platform. | [manifest](/vnda/manifest.gen.ts) | -| VTEX | The app for e-commerce that uses VTEX as a platform. | [manifest](/vtex/manifest.gen.ts) | -| Wake | The app for e-commerce that uses Wake as a platform. | [manifest](/wake/manifest.gen.ts) | -| Weather | Weather is an application that provides accurate and up-to-date weather information. | [manifest](/weather/manifest.gen.ts) | -| Website | The base app of any website. Contains `Page.tsx` block and other common loaders like image and fonts. | [manifest](/website/manifest.gen.ts) | -| Workflows | App for managing workflows. | [manifest](/workflows/manifest.gen.ts) | +| App Name | Description | Manifest | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| Algolia | Algolia search integration. Provides loaders and workflows for searching and indexing on Algolia | [manifest](/algolia/manifest.gen.ts) | +| ai-assistant | AI Assistant is a hub for artificial intelligence (AI) assistants, allowing you to register your own AI assistants and invoke them through automated actions. | [manifest](/ai-assistant/manifest.gen.ts) | +| analytics | Analytics is a powerful data analysis tool, providing valuable insights into the performance and behavior of users on your application or platform. | [manifest](/analytics/manifest.gen.ts) | +| brand-assistant | Brand Assistant is a brand assistance tool. | [manifest](/brand-assistant/manifest.gen.ts) | +| commerce | A simple configurable start for any e-commerce platform, lets you switch between any of those | [manifest](/commerce/manifest.gen.ts) | +| $live | An app for compatibility with $live blocks. | [manifest](/compat/$live/manifest.gen.ts) | +| deco-sites/std | An app for compatibility with deco-sites/std app, contains various blocks merged from e-commerce apps. | [manifest](/compat/std/manifest.gen.ts) | +| decohub | The best place to find an app for your business case, here is where apps published by any developer in the deco ecosystem will live. | [manifest](/decohub/manifest.gen.ts) | +| implementation | App for project implementation details. | [manifest](/implementation/manifest.gen.ts) | +| Konfidency | An app that uses extension block to add Aggregate Rating to products by integrating with the "[Konfidency](https://www.konfidency.com.br/)" provider. | [manifest](/konfidency/manifest.gen.ts) | +| Linx | The app for e-commerce that uses Linx as a platform. | [manifest](/linx/manifest.gen.ts) | +| nuvemshop | The app for e-commerce that uses Nuvemshop as a platform. | [manifest](/nuvemshop/manifest.gen.ts) | +| openai | Connects to OpenAI services to generate AI-powered content. | [manifest](/openai/manifest.gen.ts) | +| power-reviews | Power Reviews is an integration to show reviews and ratings of your products. It allow your customers to give a rating, write texts, emphasis pros/cons and upload images and videos. | [manifest](/power-reviews/manifest.gen.ts) | +| Resend | App for sending emails using [https://resend.com/](https://resend.com/) | [manifest](/resend/manifest.gen.ts) | +| EmailJS | App for sending emails using [https://www.emailjs.com/](https://www.emailjs.com/) | [manifest](/emailjs/manifest.gen.ts) | +| Shopify | The app for e-commerce that uses Shopify as a platform. | [manifest](/shopify/manifest.gen.ts) | +| sourei | Digitalize your business with Sourei. Offering a wide range of digital solutions, from domain registration to advanced project infrastructure. | [manifest](/sourei/manifest.gen.ts) | +| typesense | Typesense search integration. Provides loaders and workflows for searching and indexing on Typesense. | [manifest](/typesense/manifest.gen.ts) | +| Verified Reviews | An app that uses extension block to add Aggregate Rating to products by integrating with the "[OpiniΓ΅es Verificadas](https://www.opinioes-verificadas.com.br/br/)" provider. | [manifest](/verified-reviews/manifest.gen.ts) | +| VNDA | The app for e-commerce that uses VNDA as a platform. | [manifest](/vnda/manifest.gen.ts) | +| VTEX | The app for e-commerce that uses VTEX as a platform. | [manifest](/vtex/manifest.gen.ts) | +| Wake | The app for e-commerce that uses Wake as a platform. | [manifest](/wake/manifest.gen.ts) | +| Weather | Weather is an application that provides accurate and up-to-date weather information. | [manifest](/weather/manifest.gen.ts) | +| Website | The base app of any website. Contains `Page.tsx` block and other common loaders like image and fonts. | [manifest](/website/manifest.gen.ts) | +| Workflows | App for managing workflows. | [manifest](/workflows/manifest.gen.ts) | ## E-commerce Integrations - Status -| Integrations | Home | PLP | PDP | Cart | Checkout proxy | Order placed proxy | My account proxy | -|:------------------------------------------------------------------------------------------------|:-------|:------|:------|:-------|:-----------------|:---------------------|:-------------------| -| [VTEX](https://github.com/deco-cx/apps/blob/main/vtex/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | -| [VNDA](https://github.com/deco-cx/apps/blob/main/vnda/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | -| [Shopify](https://github.com/deco-cx/apps/blob/b072c1fdfab8d5f1647ed42f9dbaae618f28f05f/shopify/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | ⚠️ | -| [Linx](https://github.com/deco-cx/apps/blob/main/linx/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | -| Linx impulse | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | -| [Nuvemshop](https://github.com/deco-cx/apps/blob/main/nuvemshop/README.MD) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | ⚠️ | -| [Wake](https://github.com/deco-cx/apps/blob/main/wake/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| Integrations | Home | PLP | PDP | Cart | Checkout proxy | Order placed proxy | My account proxy | +| :--------------------------------------------------------------------------------------------------------- | :--- | :-- | :-- | :--- | :------------- | :----------------- | :--------------- | +| [VTEX](https://github.com/deco-cx/apps/blob/main/vtex/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| [VNDA](https://github.com/deco-cx/apps/blob/main/vnda/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| [Shopify](https://github.com/deco-cx/apps/blob/b072c1fdfab8d5f1647ed42f9dbaae618f28f05f/shopify/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | ⚠️ | +| [Linx](https://github.com/deco-cx/apps/blob/main/linx/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| Linx impulse | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| [Nuvemshop](https://github.com/deco-cx/apps/blob/main/nuvemshop/README.MD) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | ⚠️ | +| [Wake](https://github.com/deco-cx/apps/blob/main/wake/README.md) | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | ## Review Integrations - Status -| Integrations | Extension PDP | Extension ProductList | Extension Listing Page | Submit Review | -|:------------------------------------------------------------------------------------------------|:-------|:------|:------|:-------| -| [Power Reviews](https://github.com/deco-cx/apps/blob/main/power-reviews/README.md) | βœ… | βœ… | βœ… | βœ… | -| [Verified Reviews](https://github.com/deco-cx/apps/blob/main/verified-reviews/README.md) | βœ… | βœ… | βœ… | πŸ”΄ | -| [Konfidency](https://github.com/deco-cx/apps/blob/main/konfidency/README.md) | βœ… | πŸ”΄ | πŸ”΄ | πŸ”΄ | - +| Integrations | Extension PDP | Extension ProductList | Extension Listing Page | Submit Review | +| :--------------------------------------------------------------------------------------- | :------------ | :-------------------- | :--------------------- | :------------ | +| [Power Reviews](https://github.com/deco-cx/apps/blob/main/power-reviews/README.md) | βœ… | βœ… | βœ… | βœ… | +| [Verified Reviews](https://github.com/deco-cx/apps/blob/main/verified-reviews/README.md) | βœ… | βœ… | βœ… | πŸ”΄ | +| [Konfidency](https://github.com/deco-cx/apps/blob/main/konfidency/README.md) | βœ… | πŸ”΄ | πŸ”΄ | πŸ”΄ | #### Adding a new app to Deco Hub @@ -118,4 +91,4 @@ In order to make your app available to be installable in any deco site, just imp - \ No newline at end of file + diff --git a/admin/types.ts b/admin/types.ts index 75ad8e5df..ad617df3b 100644 --- a/admin/types.ts +++ b/admin/types.ts @@ -1,23 +1,19 @@ -import { type Resolvable } from "deco/engine/core/resolver.ts"; import { type fjp } from "./deps.ts"; - +import { type Resolvable } from "@deco/deco"; export interface Pagination { data: T[]; page: number; pageSize: number; total: number; } - export interface PatchState { type: "patch-state"; payload: fjp.Operation[]; revision: string; } - export interface FetchState { type: "fetch-state"; } - export interface StatePatched { type: "state-patched"; payload: fjp.Operation[]; @@ -25,24 +21,21 @@ export interface StatePatched { // Maybe add data and user info in here metadata?: unknown; } - export interface StateFetched { type: "state-fetched"; payload: State; } - export interface OperationFailed { type: "operation-failed"; code: "UNAUTHORIZED" | "INTERNAL_SERVER_ERROR"; reason: string; } - -export type Acked = T & { ack: string }; - +export type Acked = T & { + ack: string; +}; export interface State { decofile: Record; revision: string; } - export type Commands = PatchState | FetchState; export type Events = StatePatched | StateFetched | OperationFailed; diff --git a/admin/widgets.ts b/admin/widgets.ts index a0d39eefe..dfaaa4fbd 100644 --- a/admin/widgets.ts +++ b/admin/widgets.ts @@ -24,6 +24,16 @@ export type SiteRoute = string; */ export type MapWidget = string; +/** + * @format date + */ +export type DateWidget = string; + +/** + * @format date-time + */ +export type DateTimeWidget = string; + /** * @format textarea */ @@ -57,3 +67,15 @@ export type TypeScript = string; * @language json */ export type Json = string; + +/** + * @format file-uri + * @accept text/csv + */ +export type CSVWidget = string; + +/** + * @format file-uri + * @accept application/pdf + */ +export type PDFWidget = string; diff --git a/ai-assistants/actions/awsUploadImage.ts b/ai-assistants/actions/awsUploadImage.ts index a5904a065..34eb20426 100644 --- a/ai-assistants/actions/awsUploadImage.ts +++ b/ai-assistants/actions/awsUploadImage.ts @@ -1,22 +1,17 @@ -import { logger } from "deco/observability/otel/config.ts"; -import { meter } from "deco/observability/otel/metrics.ts"; import base64ToBlob from "../utils/blobConversion.ts"; import { AssistantIds } from "../types.ts"; -import { ValueType } from "deco/deps.ts"; import { AppContext } from "../mod.ts"; - +import { logger, meter, ValueType } from "@deco/deco/o11y"; const stats = { awsUploadImageError: meter.createCounter("assistant_aws_upload_error", { unit: "1", valueType: ValueType.INT, }), }; - export interface AWSUploadImageProps { file: string | ArrayBuffer | null; assistantIds?: AssistantIds; } - // TODO(ItamarRocha): Check if possible to upload straight to bucket instead of using presigned url async function getSignedUrl( mimetype: string, @@ -24,7 +19,6 @@ async function getSignedUrl( ): Promise { const randomID = crypto.randomUUID(); const name = `${randomID}.${mimetype.split("/")[1]}`; - // Get signed URL from S3 const s3Params = { Bucket: ctx.assistantAwsProps?.assistantBucketName.get?.() ?? "", @@ -32,16 +26,13 @@ async function getSignedUrl( ContentType: mimetype, ACL: "public-read", }; - const uploadURL = await ctx.s3?.getSignedUrlPromise("putObject", s3Params); return uploadURL as string; } - async function uploadFileToS3(presignedUrl: string, data: Blob) { const response = await fetch(presignedUrl, { method: "PUT", body: data }); return response; } - // TODO(ItamarRocha): Rate limit export default async function awsUploadImage( awsUploadImageProps: AWSUploadImageProps, @@ -57,7 +48,6 @@ export default async function awsUploadImage( ); const uploadURL = await getSignedUrl(blobData.type, ctx); const uploadResponse = await uploadFileToS3(uploadURL, blobData); - if (!uploadResponse.ok) { stats.awsUploadImageError.add(1, { assistantId, diff --git a/ai-assistants/actions/chat.ts b/ai-assistants/actions/chat.ts index 00aac7ec5..10f777d37 100644 --- a/ai-assistants/actions/chat.ts +++ b/ai-assistants/actions/chat.ts @@ -1,15 +1,12 @@ import { AppContext } from "../mod.ts"; - -import { badRequest, notFound } from "deco/mod.ts"; import { messageProcessorFor } from "../chat/messages.ts"; import { Notify, Queue } from "../deps.ts"; - +import { badRequest, notFound } from "@deco/deco"; export interface Props { thread?: string; assistant: string; message?: string; } - /** * Processes messages from the message queue. * @param {Queue} q - The message queue. @@ -35,18 +32,15 @@ const process = async ( ]); } }; - export interface MessageContentText { type: "text"; value: string; options?: string[]; } - export interface MessageContentFile { type: "file"; fileId: string; } - export interface ReplyMessage { threadId: string; messageId: string; @@ -54,16 +48,13 @@ export interface ReplyMessage { content: Array; role: "user" | "assistant"; } - export interface FunctionCall { name: string; props: unknown; } - export interface FunctionCallReply extends FunctionCall { response: T; } - export interface ReplyStartFunctionCall { threadId: string; messageId: string; @@ -76,17 +67,14 @@ export interface ReplyFunctionCalls { type: "function_calls"; content: FunctionCallReply[]; } - export type Reply = | ReplyMessage | ReplyFunctionCalls | ReplyStartFunctionCall; - export interface ChatMessage { text: string; reply: (reply: Reply) => void; } - /** * Initializes a WebSocket chat connection and processes incoming messages. * @param {Props} props - The properties for the chat session. @@ -98,7 +86,12 @@ export default async function openChat( props: Props, req: Request, ctx: AppContext, -): Promise[]; thread: string }> { +): Promise< + Response | { + replies: Reply[]; + thread: string; + } +> { if (!props.assistant) { notFound(); } @@ -106,13 +99,11 @@ export default async function openChat( if (!assistant) { notFound(); } - const threads = ctx.openAI.beta.threads; const threadId = props.thread; const threadPromise = threadId ? threads.retrieve(threadId) : threads.create(); - const processorPromise = assistant.then(async (aiAssistant) => messageProcessorFor(aiAssistant, ctx, await threadPromise) ); @@ -128,7 +119,6 @@ export default async function openChat( }); return { replies, thread: (await threadPromise).id }; } - const { socket, response } = Deno.upgradeWebSocket(req); const abort = new Notify(); const messagesQ = new Queue(); @@ -138,7 +128,6 @@ export default async function openChat( reply: (replyMsg) => socket.send(JSON.stringify(replyMsg)), }); } - /** * Handles the WebSocket connection on open event. */ @@ -156,19 +145,17 @@ export default async function openChat( }), ); assistant.then((aiAssistant) => { - socket.send( - JSON.stringify({ - isWelcomeMessage: true, - threadId: aiAssistant.threadId, - assistantId: aiAssistant.id, - type: "message", - content: [{ - type: "text", - value: aiAssistant.welcomeMessage ?? "Welcome to the chat!", - }], - role: "assistant", - }), - ); + socket.send(JSON.stringify({ + isWelcomeMessage: true, + threadId: aiAssistant.threadId, + assistantId: aiAssistant.id, + type: "message", + content: [{ + type: "text", + value: aiAssistant.welcomeMessage ?? "Welcome to the chat!", + }], + role: "assistant", + })); }); }; /** @@ -177,7 +164,6 @@ export default async function openChat( socket.onclose = () => { abort.notifyAll(); }; - /** * Handles the WebSocket connection on message event. * @param {MessageEvent} event - The WebSocket message event. diff --git a/ai-assistants/actions/describeImage.ts b/ai-assistants/actions/describeImage.ts index edaa5d76d..c5661f712 100644 --- a/ai-assistants/actions/describeImage.ts +++ b/ai-assistants/actions/describeImage.ts @@ -1,9 +1,7 @@ -import { logger } from "deco/observability/otel/config.ts"; -import { meter } from "deco/observability/otel/metrics.ts"; import { AssistantIds } from "../types.ts"; -import { ValueType } from "deco/deps.ts"; import { AppContext } from "../mod.ts"; -import { shortcircuit } from "deco/engine/errors.ts"; +import { logger, meter, ValueType } from "@deco/deco/o11y"; +import { shortcircuit } from "@deco/deco"; const stats = { promptTokens: meter.createHistogram("assistant_image_prompt_tokens", { @@ -20,13 +18,11 @@ const stats = { valueType: ValueType.INT, }), }; - export interface DescribeImageProps { uploadURL: string; userPrompt: string; assistantIds?: AssistantIds; } - // TODO(ItamarRocha): Rate limit // TODO(@ItamarRocha): Refactor to use https://github.com/deco-cx/apps/blob/main/openai/loaders/vision.ts export default async function describeImage( @@ -83,13 +79,18 @@ export default async function describeImage( }); return response; } catch (error) { + const errorObj = error as { + error: { message: string }; + status: number; + headers: Headers; + }; stats.describeImageError.add(1, { assistantId, }); shortcircuit( - new Response(JSON.stringify({ error: error.error.message }), { - status: error.status, - headers: error.headers, + new Response(JSON.stringify({ error: errorObj.error.message }), { + status: errorObj.status, + headers: errorObj.headers, }), ); } diff --git a/ai-assistants/actions/transcribeAudio.ts b/ai-assistants/actions/transcribeAudio.ts index f2489a311..453417b22 100644 --- a/ai-assistants/actions/transcribeAudio.ts +++ b/ai-assistants/actions/transcribeAudio.ts @@ -1,10 +1,7 @@ -import { logger } from "deco/observability/otel/config.ts"; import base64ToBlob from "../utils/blobConversion.ts"; -import { meter } from "deco/observability/otel/metrics.ts"; import { AssistantIds } from "../types.ts"; -import { ValueType } from "deco/deps.ts"; import { AppContext } from "../mod.ts"; - +import { logger, meter, ValueType } from "@deco/deco/o11y"; const stats = { audioSize: meter.createHistogram("assistant_transcribe_audio_size", { description: @@ -20,13 +17,11 @@ const stats = { }, ), }; - export interface TranscribeAudioProps { file: string | ArrayBuffer | null; assistantIds?: AssistantIds; audioDuration: number; } - // TODO(ItamarRocha): Rate limit export default async function transcribeAudio( transcribeAudioProps: TranscribeAudioProps, @@ -41,14 +36,12 @@ export default async function transcribeAudio( }); throw new Error("Audio file is empty"); } - const blobData = base64ToBlob( transcribeAudioProps.file, "audio", transcribeAudioProps.assistantIds, ); const file = new File([blobData], "input.wav", { type: "audio/wav" }); - stats.audioSize.record(transcribeAudioProps.audioDuration, { assistant_id: assistantId, }); diff --git a/ai-assistants/chat/messages.ts b/ai-assistants/chat/messages.ts index 0dc3a5327..169eab460 100644 --- a/ai-assistants/chat/messages.ts +++ b/ai-assistants/chat/messages.ts @@ -1,22 +1,20 @@ -import { - AssistantCreateParams, - RequiredActionFunctionToolCall, - Thread, -} from "../deps.ts"; -import { threadMessageToReply, Tokens } from "../loaders/messages.ts"; -import { JSONSchema7, ValueType, weakcache } from "deco/deps.ts"; -import { lazySchemaFor } from "deco/engine/schema/lazy.ts"; -import { Context } from "deco/live.ts"; -import { meter } from "deco/observability/otel/metrics.ts"; +import { Context, type JSONSchema7, lazySchemaFor } from "@deco/deco"; +import { meter, ValueType } from "@deco/deco/o11y"; +import { weakcache } from "../../utils/weakcache.ts"; import { ChatMessage, FunctionCallReply, Reply, ReplyMessage, } from "../actions/chat.ts"; +import { + AssistantCreateParams, + RequiredActionFunctionToolCall, + Thread, +} from "../deps.ts"; +import { threadMessageToReply, Tokens } from "../loaders/messages.ts"; import { AIAssistant, AppContext } from "../mod.ts"; import { dereferenceJsonSchema } from "../schema.ts"; - const stats = { latency: meter.createHistogram("assistant_latency", { description: @@ -25,24 +23,20 @@ const stats = { valueType: ValueType.DOUBLE, }), }; - // Max length of instructions. The maximum context of the assistant is 32K chars. We use 25K for instructions to be safe. const MAX_INSTRUCTIONS_LENGTH = 25000; - const notUndefined = (v: T | undefined): v is T => v !== undefined; - const toolsCache = new weakcache.WeakLRUCache({ cacheSize: 16, // up to 16 different schemas stored here. }); - /** * Builds assistant tools that can be used by OpenAI assistant to execute actions based on users requests. * @param assistant the assistant that will handle the request * @returns an array of available functions that can be used. */ -const appTools = async (assistant: AIAssistant): Promise< - AssistantCreateParams.AssistantToolsFunction[] -> => { +const appTools = async ( + assistant: AIAssistant, +): Promise => { const ctx = Context.active(); const assistantsKey = assistant.availableFunctions?.join(",") ?? "all"; const revision = await ctx.release!.revision(); @@ -56,60 +50,57 @@ const appTools = async (assistant: AIAssistant): Promise< ...runtime.manifest.loaders, ...runtime.manifest.actions, }); - const tools = functionKeys.map( - (functionKey) => { - const functionDefinition = btoa(functionKey); - const schema = schemas.definitions[functionDefinition]; - if ((schema as { ignoreAI?: boolean })?.ignoreAI) { - return undefined; - } - const propsRef = (schema?.allOf?.[0] as JSONSchema7)?.$ref; - if (!propsRef) { - return undefined; - } - const dereferenced = dereferenceJsonSchema({ - $ref: propsRef, - ...schemas, - }); - if ( - dereferenced.type !== "object" || - (dereferenced.oneOf || dereferenced.anyOf || - dereferenced?.allOf || dereferenced?.enum || dereferenced?.not) - ) { - return undefined; - } - return { - type: "function" as const, - function: { - name: functionKey, - description: - `Usage for: ${schema?.description}. Example: ${schema?.examples}`, - parameters: { - ...dereferenced, - definitions: undefined, - root: undefined, - }, + const tools = functionKeys.map((functionKey) => { + const functionDefinition = btoa(functionKey); + const schema = schemas.definitions[functionDefinition]; + if ( + (schema as { + ignoreAI?: boolean; + })?.ignoreAI + ) { + return undefined; + } + const propsRef = (schema?.allOf?.[0] as JSONSchema7)?.$ref; + if (!propsRef) { + return undefined; + } + const dereferenced = dereferenceJsonSchema({ + $ref: propsRef, + ...schemas, + }); + if ( + dereferenced.type !== "object" || + (dereferenced.oneOf || dereferenced.anyOf || + dereferenced?.allOf || dereferenced?.enum || dereferenced?.not) + ) { + return undefined; + } + return { + type: "function" as const, + function: { + name: functionKey, + description: + `Usage for: ${schema?.description}. Example: ${schema?.examples}`, + parameters: { + ...dereferenced, + definitions: undefined, + root: undefined, }, - }; - }, - ).filter(notUndefined); - + }, + }; + }).filter(notUndefined); toolsCache.set(ctx, tools); return tools; }); toolsCache.set(cacheKey, toolsPromise); return toolsPromise; }; - export interface ProcessorOpts { assistantId: string; instructions: string; } - const sleep = (ns: number) => new Promise((resolve) => setTimeout(resolve, ns)); - const cache: Record = {}; - const invokeFor = ( ctx: AppContext, assistant: AIAssistant, @@ -126,9 +117,7 @@ const invokeFor = ( return async (call: RequiredActionFunctionToolCall) => { try { const props = JSON.parse(call.function.arguments || "{}"); - const cacheKey = `${call.function.arguments}${call.function.name}`; - const assistantProps = assistant?.useProps?.(props) ?? props; cache[cacheKey] ??= ctx.invoke( // deno-lint-ignore no-explicit-any @@ -180,11 +169,9 @@ export const messageProcessorFor = async ( `; const assistantId = await ctx.assistant.then((assistant) => assistant.id); const tools = await appTools(assistant); - // Update the assistant object with the thread and assistant id assistant.threadId = thread.id; assistant.id = assistantId; - /** * Processes an incoming chat message. * @param {ChatMessage} message - The incoming chat message. @@ -206,11 +193,9 @@ export const messageProcessorFor = async ( instructions: instructions.slice(0, MAX_INSTRUCTIONS_LENGTH), tools, }); - const messageId = run.id; // Wait for the assistant answer const functionCallReplies: FunctionCallReply[] = []; - // Reply to the user const reply = (message: Reply) => { assistant.onMessageSent?.({ @@ -222,7 +207,6 @@ export const messageProcessorFor = async ( }); return _reply(message); }; - assistant.onMessageReceived?.({ assistantId: run.assistant_id, threadId: thread.id, @@ -230,7 +214,6 @@ export const messageProcessorFor = async ( model: run.model, message: { type: "text", value: content }, }); - const invoke = invokeFor(ctx, assistant, (call, props) => { reply({ threadId: thread.id, @@ -252,21 +235,13 @@ export const messageProcessorFor = async ( response, }); }); - let runStatus; do { - runStatus = await threads.runs.retrieve( - thread.id, - run.id, - ); - + runStatus = await threads.runs.retrieve(thread.id, run.id); if (runStatus.status === "requires_action") { const actions = runStatus.required_action!; const outputs = actions.submit_tool_outputs; - - const tool_outputs = await Promise.all( - outputs.tool_calls.map(invoke), - ); + const tool_outputs = await Promise.all(outputs.tool_calls.map(invoke)); if (tool_outputs.length === 0) { const message: ReplyMessage = { messageId: Date.now().toString(), @@ -278,28 +253,19 @@ export const messageProcessorFor = async ( reply(message); return; } - await threads.runs.submitToolOutputs( - thread.id, - run.id, - { - tool_outputs, - }, - ); - runStatus = await threads.runs.retrieve( - thread.id, - run.id, - ); + await threads.runs.submitToolOutputs(thread.id, run.id, { + tool_outputs, + }); + runStatus = await threads.runs.retrieve(thread.id, run.id); } await sleep(500); } while (["in_progress", "queued"].includes(runStatus.status)); - const messages = await threads.messages.list(thread.id); const threadMessages = messages.data; const lastMsg = threadMessages .findLast((message) => message.run_id == run.id && message.role === "assistant" ); - if (!lastMsg) { // TODO(@mcandeia) in some cases the bot didn't respond anything. const message: ReplyMessage = { @@ -312,9 +278,7 @@ export const messageProcessorFor = async ( reply(message); return; } - const replyMessage = threadMessageToReply(lastMsg); - // multi tool use parallel seems to be some sort of openai bug, and it seems to have no solution yet. // https://community.openai.com/t/model-tries-to-call-unknown-function-multi-tool-use-parallel/490653 // It's an error that only happens every now and then. Open ai tries to call "multi_tool_use.parallel" function that doesn't even exist and isn't even in the OpenAI documentation @@ -339,7 +303,6 @@ export const messageProcessorFor = async ( assistant_id: run.assistant_id, }); } - if (functionCallReplies.length > 0) { reply({ threadId: thread.id, diff --git a/ai-assistants/mod.ts b/ai-assistants/mod.ts index 535b99ac5..d9db17ecd 100644 --- a/ai-assistants/mod.ts +++ b/ai-assistants/mod.ts @@ -1,9 +1,4 @@ -import AWS from "https://esm.sh/aws-sdk@2.1585.0"; -import { asResolved, isDeferred } from "deco/engine/core/resolver.ts"; -import { isAwaitable } from "deco/engine/core/utils.ts"; -import type { App, AppContext as AC } from "deco/mod.ts"; -import { AvailableActions, AvailableLoaders } from "deco/utils/invoke.types.ts"; -import { AppManifest } from "deco/types.ts"; +import AWS from "npm:aws-sdk@2.1585.0"; import { deferred } from "std/async/deferred.ts"; import openai, { Props as OpenAIProps, @@ -12,7 +7,17 @@ import openai, { import { Assistant } from "./deps.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { Secret } from "../website/loaders/secret.ts"; - +import { PreviewContainer } from "../utils/preview.tsx"; +import { + type App, + type AppContext as AC, + type AppManifest, + asResolved, + type AvailableActions, + type AvailableLoaders, + isDeferred, +} from "@deco/deco"; +import { isAwaitable } from "@deco/deco/utils"; export type GPTModel = | "gpt-4-0613" | "gpt-4-0314" @@ -32,29 +37,24 @@ export interface AIAssistant { * The name of the AI Assistant. */ name: string; - /** * Optional instructions or guidelines for the AI Assistant. */ instructions?: string; - /** * Optional array of prompts to provide context for the AI Assistant. */ prompts?: Prompt[]; - /** * Optional welcome message to be displayed when the chat session starts. */ welcomeMessage?: string; - /** * Optional list of available functions (actions or loaders) that the AI Assistant can perform. */ availableFunctions?: Array< AvailableActions | AvailableLoaders >; - /** * Optional function to customize the handling of properties (props) passed to the AI Assistant. * It takes a set of properties and returns a modified set of properties. @@ -62,37 +62,33 @@ export interface AIAssistant { * @returns {unknown} - The modified properties. */ useProps?: (props: unknown) => unknown; - /** * Optional function to log the received messages from the user. * @param {Log} logInfo - User message / information. * @returns {void} - The modified properties. */ onMessageReceived?: (logInfo: Log) => void; - /** * Optional function to log the received messages sent by the assistant. * @param {Log} logInfo - Assistant message / information. * @returns {void} - The modified properties. */ onMessageSent?: (logInfo: Log) => void; - /** * The GPT model that will be used, if not specified the assistant model will be used. */ - model?: GPTModel | { custom: string }; - + model?: GPTModel | { + custom: string; + }; /** * The Id of the assistant */ id?: string; - /** * The Id of the assistant thread */ threadId?: string; } - export interface Log { assistantId: string; threadId: string; @@ -100,19 +96,16 @@ export interface Log { model: string; message: object; } - export interface Prompt { content: string; context: string; } - export interface AssistantAwsProps { assistantBucketRegion: Secret; accessKeyId: Secret; secretAccessKey: Secret; assistantBucketName: Secret; } - export interface Props extends OpenAIProps { /** * @description the assistant Id @@ -126,7 +119,6 @@ export interface Props extends OpenAIProps { assistantAwsProps?: AssistantAwsProps; s3?: AWS.S3; } - export interface State extends OpenAIState { instructions?: string; assistant: Promise; @@ -134,16 +126,15 @@ export interface State extends OpenAIState { assistantAwsProps?: AssistantAwsProps; s3?: AWS.S3; } - /** * @title Deco AI Assistant * @description Create AI assistants on deco.cx. * @category Tool * @logo https://raw.githubusercontent.com/deco-cx/apps/main/ai-assistants/logo.png */ -export default function App( - state: Props, -): App]> { +export default function App(state: Props): App, +]> { const openAIApp = openai(state); const assistantsAPI = openAIApp.state.openAI.beta.assistants; // Sets assistantId only if state.assistants exists @@ -188,10 +179,7 @@ export default function App( dependencies: [openAIApp], }; } - -export const onBeforeResolveProps = ( - props: Props, -) => { +export const onBeforeResolveProps = (props: Props) => { if (Array.isArray(props?.assistants)) { return { ...props, @@ -202,5 +190,18 @@ export const onBeforeResolveProps = ( } return props; }; - export type AppContext = AC>; +export const preview = () => { + return { + Component: PreviewContainer, + props: { + name: "Deco AI Assistant", + owner: "deco.cx", + description: "Create AI assistants on deco.cx.", + logo: + "https://raw.githubusercontent.com/deco-cx/apps/main/ai-assistants/logo.png", + images: [], + tabs: [], + }, + }; +}; diff --git a/ai-assistants/runtime.ts b/ai-assistants/runtime.ts index 41d65a98d..da42a2435 100644 --- a/ai-assistants/runtime.ts +++ b/ai-assistants/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy(); diff --git a/ai-assistants/schema.ts b/ai-assistants/schema.ts index 674d776db..493aff815 100644 --- a/ai-assistants/schema.ts +++ b/ai-assistants/schema.ts @@ -1,15 +1,13 @@ -import { JSONSchema7 } from "deco/deps.ts"; - -const isJSONSchema = ( - v: unknown | JSONSchema7, -): v is JSONSchema7 & { +import { type JSONSchema7 } from "@deco/deco"; +const isJSONSchema = (v: unknown | JSONSchema7): v is JSONSchema7 & { $ref: string; } => { return (typeof v === "object" && ((v as JSONSchema7)?.$ref !== undefined)); }; - export function dereferenceJsonSchema( - schema: JSONSchema7 & { definitions?: Record }, + schema: JSONSchema7 & { + definitions?: Record; + }, ) { const resolveReference = ( obj: unknown, @@ -30,6 +28,5 @@ export function dereferenceJsonSchema( } return obj as JSONSchema7; }; - return resolveReference(schema, {}); } diff --git a/ai-assistants/utils/blobConversion.ts b/ai-assistants/utils/blobConversion.ts index 2b08f6d4f..826cba88c 100644 --- a/ai-assistants/utils/blobConversion.ts +++ b/ai-assistants/utils/blobConversion.ts @@ -1,6 +1,5 @@ -import { logger } from "deco/observability/otel/config.ts"; import { AssistantIds } from "../types.ts"; - +import { logger } from "@deco/deco/o11y"; export default function base64ToBlob( base64: string | ArrayBuffer | null, context: string, @@ -23,7 +22,6 @@ export default function base64ToBlob( }`); throw new Error("Expected a base64 string"); } - const parts = base64.match(regex); if (!parts || parts.length !== 3) { logger.error(`${ @@ -38,21 +36,16 @@ export default function base64ToBlob( `${context} Base64 string is not properly formatted: ${parts}`, ); } - const mimeType = parts[1]; // e.g., 'audio/png' or 'video/mp4' or 'audio/mp3' or 'image/png' const mediaData = parts[2]; - // Convert the base64 encoded data to a binary string const binaryStr = atob(mediaData); - // Convert the binary string to an array of bytes (Uint8Array) const length = binaryStr.length; const arrayBuffer = new Uint8Array(new ArrayBuffer(length)); - for (let i = 0; i < length; i++) { arrayBuffer[i] = binaryStr.charCodeAt(i); } - // Create and return the Blob object return new Blob([arrayBuffer], { type: mimeType }); } diff --git a/algolia/README.md b/algolia/README.md index c45995ccc..b20a965d8 100644 --- a/algolia/README.md +++ b/algolia/README.md @@ -1,23 +1,7 @@ -

-

- - Algolia - -

-

- -

- - AI Search & Discovery platform - -

-

- Loaders, actions and workflows for adding Agolia search, the leader in globally scalable, secure, digital search and discovery experiences that are ultrafast and reliable, to your deco.cx website. -

- -

+ +Loaders, actions and workflows for adding Agolia search, the leader in globally scalable, secure, digital search and discovery experiences that are ultrafast and reliable, to your deco.cx website. + Algolia is a general purpose indexer. This means you can save any Json document and later retrieve it using the Search API. Although being a simpler solution than competing alternatives like Elastic Search or Solar, setting up an index on Algolia still requires some software engineering, like setting up searchable fields, facet fields and sorting indices. Hopefully, deco.cx introduces canonical types, like Product, ProductGroup, etc. These schemas allow this app to built solutions for common use-cases, like indexing Products for a Product Listing Page. -

# Installation 1. Install via decohub diff --git a/algolia/actions/index/product.ts b/algolia/actions/index/product.ts index ab815f0cc..cb266d05c 100644 --- a/algolia/actions/index/product.ts +++ b/algolia/actions/index/product.ts @@ -1,11 +1,13 @@ import { ApiError } from "npm:@algolia/transporter@4.20.0"; import { Product } from "../../../commerce/types.ts"; import { AppContext } from "../../mod.ts"; -import { Indices, toIndex } from "../../utils/product.ts"; +import { toIndex } from "../../utils/product.ts"; +import { INDEX_NAME } from "../../loaders/product/list.ts"; interface Props { product: Product; action: "DELETE" | "UPSERT"; + indexName?: string; } // deno-lint-ignore no-explicit-any @@ -13,10 +15,8 @@ const isAPIError = (x: any): x is ApiError => typeof x?.status === "number" && x.name === "ApiError"; -const indexName: Indices = "products"; - const action = async (props: Props, _req: Request, ctx: AppContext) => { - const { product, action } = props; + const { indexName = INDEX_NAME, product, action } = props; const { client } = ctx; try { diff --git a/algolia/actions/index/wait.ts b/algolia/actions/index/wait.ts index f1b137a35..31d4a2336 100644 --- a/algolia/actions/index/wait.ts +++ b/algolia/actions/index/wait.ts @@ -1,14 +1,14 @@ +import { INDEX_NAME } from "../../loaders/product/list.ts"; import { AppContext } from "../../mod.ts"; -import { Indices } from "../../utils/product.ts"; interface Props { taskID: number; + indexName?: string; } -const indexName: Indices = "products"; - const action = async (props: Props, _req: Request, ctx: AppContext) => { const { client } = ctx; + const { indexName = INDEX_NAME } = props; await client.initIndex(indexName).waitTask(props.taskID); }; diff --git a/algolia/loaders/product/list.ts b/algolia/loaders/product/list.ts index 96991f8e9..766e706f4 100644 --- a/algolia/loaders/product/list.ts +++ b/algolia/loaders/product/list.ts @@ -23,9 +23,10 @@ interface Props { /** @description Full text search query */ term?: string; + indexName?: string; } -const indexName: Indices = "products"; +export const INDEX_NAME: Indices = "products"; /** * @title Algolia Integration @@ -36,6 +37,7 @@ const loader = async ( ctx: AppContext, ): Promise => { const { client } = ctx; + const { indexName = INDEX_NAME } = props; const { results } = await client.search([{ indexName, diff --git a/algolia/loaders/product/listingPage.ts b/algolia/loaders/product/listingPage.ts index 2e61002cc..30bc32186 100644 --- a/algolia/loaders/product/listingPage.ts +++ b/algolia/loaders/product/listingPage.ts @@ -224,7 +224,7 @@ const loader = async ( ]); const [ - { hits, page, nbPages, queryID, nbHits, hitsPerPage }, + { hits, page = 0, nbPages = 1, queryID, nbHits = 12, hitsPerPage = 12 }, { facets }, ] = results as SearchResponse[]; diff --git a/algolia/mod.ts b/algolia/mod.ts index 199cff680..e607bcef2 100644 --- a/algolia/mod.ts +++ b/algolia/mod.ts @@ -1,26 +1,23 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; -import { createFetchRequester } from "npm:@algolia/requester-fetch@4.20.0"; import algolia from "https://esm.sh/algoliasearch@4.20.0"; +import { createFetchRequester } from "npm:@algolia/requester-fetch@4.20.0"; +import { Markdown } from "../decohub/components/Markdown.tsx"; +import { PreviewContainer } from "../utils/preview.tsx"; import type { Secret } from "../website/loaders/secret.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; -import { previewFromMarkdown } from "../utils/preview.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export type AppContext = AC>; - export interface State { /** * @title Your Algolia App ID * @description https://dashboard.algolia.com/account/api-keys/all */ applicationId: string; - /** * @title Search API Key * @description https://dashboard.algolia.com/account/api-keys/all * @format password */ searchApiKey: string; - /** * @title Admin API Key * @description https://dashboard.algolia.com/account/api-keys/all @@ -28,32 +25,24 @@ export interface State { */ adminApiKey: Secret; } - /** * @title Algolia * @description Product search & discovery that increases conversions at scale. * @category Search * @logo https://raw.githubusercontent.com/deco-cx/apps/main/algolia/logo.png */ -export default function App( - props: State, -) { +export default function App(props: State) { const { applicationId, adminApiKey, searchApiKey } = props; - if (!adminApiKey) { throw new Error("Missing admin API key"); } - const stringAdminApiKey = typeof adminApiKey === "string" ? adminApiKey : adminApiKey?.get?.() ?? ""; - const client = algolia(applicationId, stringAdminApiKey, { requester: createFetchRequester(), // Fetch makes it perform mutch better }); - const state = { client, applicationId, searchApiKey }; - const app: App = { manifest: { ...manifest, @@ -72,10 +61,28 @@ export default function App( }, state, }; - return app; } - -export const preview = previewFromMarkdown( - new URL("./README.md", import.meta.url), -); +export const preview = async () => { + const markdownContent = await Markdown( + new URL("./README.md", import.meta.url).href, + ); + return { + Component: PreviewContainer, + props: { + name: "Algolia", + owner: "deco.cx", + description: + "Product search & discovery that increases conversions at scale.", + logo: + "https://raw.githubusercontent.com/deco-cx/apps/main/algolia/logo.png", + images: [], + tabs: [ + { + title: "About", + content: markdownContent(), + }, + ], + }, + }; +}; diff --git a/algolia/sections/Analytics/Algolia.tsx b/algolia/sections/Analytics/Algolia.tsx index 26d5a218b..d752c3038 100644 --- a/algolia/sections/Analytics/Algolia.tsx +++ b/algolia/sections/Analytics/Algolia.tsx @@ -1,5 +1,3 @@ -import { SectionProps } from "deco/blocks/section.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; import insights from "npm:search-insights@2.9.0"; import { AddToCartEvent, @@ -8,13 +6,13 @@ import { ViewItemListEvent, } from "../../../commerce/types.ts"; import { AppContext } from "../../mod.ts"; - +import { type SectionProps } from "@deco/deco"; +import { useScriptAsDataURI } from "@deco/deco/hooks"; declare global { interface Window { aa: typeof insights.default; } } - const setupAndListen = (appId: string, apiKey: string, version: string) => { function setupScriptTag() { globalThis.window.AlgoliaAnalyticsObject = "aa"; @@ -26,7 +24,6 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { ); }; globalThis.window.aa.version = version; - const script = document.createElement("script"); script.setAttribute("async", ""); script.setAttribute( @@ -35,7 +32,6 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { ); document.head.appendChild(script); } - function createUserToken() { if ( typeof crypto !== "undefined" && @@ -43,67 +39,55 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { ) { return crypto.randomUUID(); } - return (Math.random() * 1e9).toFixed(); } - function setupSession() { globalThis.window.aa("init", { appId, apiKey }); - const userToken = localStorage.getItem("ALGOLIA_USER_TOKEN") || createUserToken(); localStorage.setItem("ALGOLIA_USER_TOKEN", userToken); globalThis.window.aa("setUserToken", userToken); } - function setupEventListeners() { function attributesFromURL(href: string) { const url = new URL(href); const queryID = url.searchParams.get("algoliaQueryID"); const indexName = url.searchParams.get("algoliaIndex"); - // Not comming from an algolia search page if (!queryID || !indexName) { return null; } - return { queryID, indexName }; } - // deno-lint-ignore no-explicit-any function isSelectItemEvent(event: any): event is SelectItemEvent { return event.name === "select_item"; } - // deno-lint-ignore no-explicit-any function isAddToCartEvent(event: any): event is AddToCartEvent { return event.name === "add_to_cart"; } - function isViewItem( // deno-lint-ignore no-explicit-any event: any, ): event is ViewItemEvent | ViewItemListEvent { return event.name === "view_item" || event.name === "view_item_list"; } - - type WithID = T & { item_id: string }; - + type WithID = T & { + item_id: string; + }; const hasItemId = (item: T): item is WithID => // deno-lint-ignore no-explicit-any typeof (item as any).item_id === "string"; - const PRODUCTS = "products"; const MAX_BATCH_SIZE = 20; - globalThis.window.DECO.events.subscribe((event) => { - if (!event) return; - + if (!event) { + return; + } const eventName = event.name; - if (isSelectItemEvent(event)) { const [item] = event.params.items; - if ( !item || !hasItemId(item) || @@ -115,9 +99,7 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { JSON.stringify(event, null, 2), ); } - const attr = attributesFromURL(item.item_url); - if (attr) { globalThis.window.aa("clickedObjectIDsAfterSearch", { eventName, @@ -134,16 +116,13 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { }); } } - if (isAddToCartEvent(event)) { const [item] = event.params.items; - const attr = attributesFromURL(globalThis.window.location.href) || attributesFromURL(item.item_url || ""); const objectIDs = event.params.items .filter(hasItemId) .map((i) => i.item_id); - if (attr) { globalThis.window.aa("convertedObjectIDsAfterSearch", { eventName, @@ -159,12 +138,10 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { }); } } - if (isViewItem(event)) { const objectIDs = event.params.items .filter(hasItemId) .map((i) => i.item_id); - for (let it = 0; it < objectIDs.length; it += MAX_BATCH_SIZE) { globalThis.window.aa("viewedObjectIDs", { eventName, @@ -175,16 +152,13 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { } }); } - setupScriptTag(); setupSession(); setupEventListeners(); }; - -function Analytics({ - applicationId, - searchApiKey, -}: SectionProps) { +function Analytics( + { applicationId, searchApiKey }: SectionProps, +) { return ( `; - const flagsScript = ``; - return dnsPrefetchLink + preconnectLink + plausibleScript + flagsScript; }; return ({ src: transformReq }); }; - export default loader; diff --git a/analytics/mod.ts b/analytics/mod.ts index 9f0eba399..24b6df52c 100644 --- a/analytics/mod.ts +++ b/analytics/mod.ts @@ -1,19 +1,30 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { PreviewContainer } from "../utils/preview.tsx"; +import { type App, type AppContext as AC } from "@deco/deco"; export type AppContext = AC>; - // deno-lint-ignore no-explicit-any export type State = any; - /** * @title Deco Analytics * @description Measure your site traffic at a glance in a simple and modern web analytics dashboard. * @category Analytics * @logo https://raw.githubusercontent.com/deco-cx/apps/main/analytics/logo.png */ -export default function App( - state: State, -): App { +export default function App(state: State): App { return { manifest, state }; } +export const preview = () => { + return { + Component: PreviewContainer, + props: { + name: "Deco Analytics", + owner: "deco.cx", + description: + "Measure your site traffic at a glance in a simple and modern web analytics dashboard.", + logo: + "https://raw.githubusercontent.com/deco-cx/apps/main/analytics/logo.png", + images: [], + tabs: [], + }, + }; +}; diff --git a/anthropic/actions/code.ts b/anthropic/actions/code.ts index 80e312c78..5c0e88550 100644 --- a/anthropic/actions/code.ts +++ b/anthropic/actions/code.ts @@ -1,6 +1,17 @@ -import { shortcircuit } from "deco/engine/errors.ts"; +import { shortcircuit } from "@deco/deco"; import { AppContext } from "../mod.ts"; import { Anthropic } from "../deps.ts"; + +export const allowedModels = [ + "claude-3-5-sonnet-20240620", + "claude-3-opus-20240229", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307", + "claude-2.1", + "claude-2.0", + "claude-instant-1.2", +] as const; + export interface Props { /** * @description The system prompt to be used for the AI Assistant. @@ -13,14 +24,7 @@ export interface Props { /** * @description The model that will complete your prompt. */ - model?: - | "claude-3-5-sonnet-20240620" - | "claude-3-opus-20240229" - | "claude-3-sonnet-20240229" - | "claude-3-haiku-20240307" - | "claude-2.1" - | "claude-2.0" - | "claude-instant-1.2"; + model?: typeof allowedModels[number]; /** * @description The maximum number of tokens to generate. * @@ -28,6 +32,7 @@ export interface Props { * [models](https://docs.anthropic.com/en/docs/models-overview) for details. */ max_tokens?: number; + temperature?: number; } export default async function chat( @@ -36,6 +41,7 @@ export default async function chat( messages, model = "claude-3-opus-20240229", max_tokens = 4096, + temperature = 0.0, }: Props, _req: Request, ctx: AppContext, @@ -49,6 +55,7 @@ export default async function chat( model, max_tokens, messages, + temperature, }); return msg; diff --git a/anthropic/actions/invoke.ts b/anthropic/actions/invoke.ts new file mode 100644 index 000000000..57744dade --- /dev/null +++ b/anthropic/actions/invoke.ts @@ -0,0 +1,77 @@ +import { shortcircuit } from "@deco/deco"; +import { AppContext } from "../mod.ts"; +import { Anthropic } from "../deps.ts"; +import { getAppTools } from "../utils.ts"; + +export interface Props { + /** + * @description The system prompt to be used for the AI Assistant. + */ + system?: string; + /** + * @description The messages to be processed by the AI Assistant. + */ + messages: Anthropic.MessageParam[]; + /** + * @description The model that will complete your prompt. + */ + model?: Anthropic.Model; + /** + * @description The maximum number of tokens to generate. + */ + max_tokens?: number; + /** + * @description Optional list of available functions (actions or loaders) that the AI Assistant can perform. + */ + availableFunctions?: string[]; + /** + * @description The tool choice to be used for the AI Assistant. + */ + tool_choice?: Anthropic.MessageCreateParams["tool_choice"]; + temperature: number; +} + +export default async function invoke( + { + system, + messages, + model = "claude-3-5-sonnet-20240620", + max_tokens = 4096, + availableFunctions = [], + tool_choice = { type: "auto" }, + temperature = 0.0, + }: Props, + _req: Request, + ctx: AppContext, +) { + if (!messages) { + return shortcircuit(new Response("No messages provided", { status: 400 })); + } + + const tools = await getAppTools(availableFunctions ?? []); + + const params: Anthropic.MessageCreateParams = { + system, + model, + max_tokens, + messages, + temperature, + }; + + if (tools?.length) { + params.tools = tools; + params.tool_choice = tool_choice; + } + + console.log(tools); + + try { + const msg = await ctx.anthropic.messages.create(params); + return msg; + } catch (error) { + console.error("Error calling Anthropic API:", error); + return shortcircuit( + new Response("Error processing request", { status: 500 }), + ); + } +} diff --git a/anthropic/actions/stream.ts b/anthropic/actions/stream.ts index 3c14286af..d9a1d2f33 100644 --- a/anthropic/actions/stream.ts +++ b/anthropic/actions/stream.ts @@ -1,11 +1,8 @@ -import { JSONSchema7 } from "deco/deps.ts"; -import { shortcircuit } from "deco/engine/errors.ts"; -import { lazySchemaFor } from "deco/engine/schema/lazy.ts"; -import { Context } from "deco/live.ts"; -import { readFromStream } from "deco/utils/http.ts"; -import { dereferenceJsonSchema } from "../../ai-assistants/schema.ts"; +import { shortcircuit } from "@deco/deco"; +import { readFromStream } from "@deco/deco/utils"; import { Anthropic } from "../deps.ts"; import { AppContext } from "../mod.ts"; +import { getAppTools } from "../utils.ts"; export interface Props { /** @@ -19,7 +16,7 @@ export interface Props { /** * @description The messages to be processed by the AI Assistant. */ - messages: Anthropic.Beta.Tools.ToolsBetaMessageParam[]; + messages: Anthropic.MessageParam[]; /** * Optional list of available functions (actions or loaders) that the AI Assistant can perform. */ @@ -27,14 +24,7 @@ export interface Props { /** * @description The model that will complete your prompt. */ - model?: - | "claude-3-5-sonnet-20240620" - | "claude-3-opus-20240229" - | "claude-3-sonnet-20240229" - | "claude-3-haiku-20240307" - | "claude-2.1" - | "claude-2.0" - | "claude-instant-1.2"; + model?: Anthropic.Model; /** * @description The maximum number of tokens to generate. * @@ -42,84 +32,8 @@ export interface Props { * [models](https://docs.anthropic.com/en/docs/models-overview) for details. */ max_tokens?: number; - enableTools?: boolean; - temperature?: number; } -const notUndefined = (v: T | undefined): v is T => v !== undefined; - -const pathFormatter = { - encode: (path: string): string => - path.replace(/\.ts/g, "").replace(/\//g, "__"), - decode: (encodedPath: string): string => - encodedPath.replace(/__/g, "/") + ".ts", -}; - -/** - * Retrieves the available tools for the AI Assistant. - * @param availableFunctions List of functions available for the AI Assistant. - * @returns Promise resolving to a list of tools. - */ -const getAppTools = async ( - availableFunctions: string[], -): Promise => { - const ctx = Context.active(); - const runtime = await ctx.runtime!; - const schemas = await lazySchemaFor(ctx).value; - - const functionKeys = availableFunctions ?? - Object.keys({ - ...runtime.manifest.loaders, - ...runtime.manifest.actions, - }); - - const tools = functionKeys - .map((functionKey) => { - const functionDefinition = btoa(functionKey); - const schema = schemas.definitions[functionDefinition]; - - if ((schema as { ignoreAI?: boolean })?.ignoreAI) { - return undefined; - } - - const propsRef = (schema?.allOf?.[0] as JSONSchema7)?.$ref; - if (!propsRef) { - return undefined; - } - - const dereferenced = dereferenceJsonSchema({ - $ref: propsRef, - ...schemas, - }); - - if ( - dereferenced.type !== "object" || - dereferenced.oneOf || - dereferenced.anyOf || - dereferenced.allOf || - dereferenced.enum || - dereferenced.not - ) { - return undefined; - } - - return { - name: pathFormatter.encode(functionKey), - description: - `Usage for: ${schema?.description}. Example: ${schema?.examples}`, - input_schema: { - ...dereferenced, - definitions: undefined, - root: undefined, - title: undefined, - }, - }; - }) - .filter(notUndefined); - - return tools as Anthropic.Beta.Tools.Tool[] | undefined; -}; - /** * @title Anthropic chat streaming * @description Sends messages to the Anthropic API for processing. @@ -131,8 +45,6 @@ export default async function chat( availableFunctions, model = "claude-3-5-sonnet-20240620", max_tokens = 1024, - temperature = 1.0, - enableTools, }: Props, _req: Request, ctx: AppContext, @@ -146,27 +58,20 @@ export default async function chat( const headers = { "anthropic-version": "2023-06-01", "content-type": "application/json", - "anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15", "x-api-key": ctx.token ?? "", }; - let payload: Anthropic.Beta.Tools.MessageCreateParamsStreaming = { + const payload: Anthropic.MessageCreateParamsStreaming = { system, messages, model, max_tokens, - temperature, + temperature: 0.5, stream: true, + tools, + tool_choice: { type: "auto" }, }; - if (enableTools) { - payload = { - ...payload, - tools, - tool_choice: { type: "auto" }, - }; - } - const response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers, @@ -174,6 +79,7 @@ export default async function chat( }); if (!response.ok) { + console.error("Failed to send messages to Anthropic API:", response.text); return shortcircuit( new Response(await response.text(), { status: response.status }), ); diff --git a/anthropic/deps.ts b/anthropic/deps.ts index 2a569e5a7..c5abca0ca 100644 --- a/anthropic/deps.ts +++ b/anthropic/deps.ts @@ -1 +1 @@ -export { default as Anthropic } from "https://esm.sh/@anthropic-ai/sdk@0.21.1"; +export { default as Anthropic } from "https://esm.sh/@anthropic-ai/sdk@0.28.0"; diff --git a/anthropic/manifest.gen.ts b/anthropic/manifest.gen.ts index fb69af8b9..00473ae22 100644 --- a/anthropic/manifest.gen.ts +++ b/anthropic/manifest.gen.ts @@ -3,12 +3,14 @@ // This file is automatically updated during development when running `dev.ts`. import * as $$$$$$$$$0 from "./actions/code.ts"; -import * as $$$$$$$$$1 from "./actions/stream.ts"; +import * as $$$$$$$$$1 from "./actions/invoke.ts"; +import * as $$$$$$$$$2 from "./actions/stream.ts"; const manifest = { "actions": { "anthropic/actions/code.ts": $$$$$$$$$0, - "anthropic/actions/stream.ts": $$$$$$$$$1, + "anthropic/actions/invoke.ts": $$$$$$$$$1, + "anthropic/actions/stream.ts": $$$$$$$$$2, }, "name": "anthropic", "baseUrl": import.meta.url, diff --git a/anthropic/mod.ts b/anthropic/mod.ts index c338c4453..aba45f11a 100644 --- a/anthropic/mod.ts +++ b/anthropic/mod.ts @@ -1,20 +1,17 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { Secret } from "../website/loaders/secret.ts"; import { Anthropic } from "./deps.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export interface Props { /** * @title Anthropic API Key */ apiKey?: Secret; } - export interface State { anthropic: Anthropic; token?: string; } - /** * @title Anthropic * @description Interact with the Anthropic API. diff --git a/anthropic/utils.ts b/anthropic/utils.ts new file mode 100644 index 000000000..9adb16eb0 --- /dev/null +++ b/anthropic/utils.ts @@ -0,0 +1,92 @@ +import { Context, type JSONSchema7, lazySchemaFor } from "@deco/deco"; + +import { dereferenceJsonSchema } from "../ai-assistants/schema.ts"; +import { Anthropic } from "./deps.ts"; + +const notUndefined = (v: T | undefined): v is T => v !== undefined; + +/** + * Utility object for encoding and decoding file paths. + */ +const pathFormatter = { + /** + * Encodes a file path by removing ".ts" and replacing slashes with "__". + * @param path - The file path to encode. + * @returns The encoded file path. + */ + encode: (path: string): string => + path.replace(/\.ts/g, "").replace(/\//g, "__"), + + /** + * Decodes an encoded file path by replacing "__" with slashes and adding ".ts". + * @param encodedPath - The encoded file path to decode. + * @returns The decoded file path. + */ + decode: (encodedPath: string): string => + encodedPath.replace(/__/g, "/") + ".ts", +}; + +/** + * Retrieves the available tools for the AI Assistant. + * @param availableFunctions List of functions available for the AI Assistant. + * @returns Promise resolving to a list of tools. + */ +export const getAppTools = async ( + availableFunctions: string[], +): Promise => { + const ctx = Context.active(); + const runtime = await ctx.runtime!; + const schemas = await lazySchemaFor(ctx).value; + + const functionKeys = availableFunctions ?? + Object.keys({ + ...runtime.manifest.loaders, + ...runtime.manifest.actions, + }); + + const tools = functionKeys + .map((functionKey) => { + const functionDefinition = btoa(functionKey); + const schema = schemas.definitions[functionDefinition]; + + if ((schema as { ignoreAI?: boolean })?.ignoreAI) { + return undefined; + } + + const propsRef = (schema?.allOf?.[0] as JSONSchema7)?.$ref; + if (!propsRef) { + return undefined; + } + + const dereferenced = dereferenceJsonSchema({ + $ref: propsRef, + ...schemas, + }); + + if ( + dereferenced.type !== "object" || + dereferenced.oneOf || + dereferenced.anyOf || + dereferenced.allOf || + dereferenced.enum || + dereferenced.not + ) { + return undefined; + } + + return { + name: pathFormatter.encode(functionKey), + description: + `Usage for: ${schema?.description}. Example: ${schema?.examples}`, + input_schema: { + ...dereferenced, + definitions: undefined, + root: undefined, + title: undefined, + }, + }; + }) + .filter(notUndefined); + + return tools as Anthropic.Tool[] | undefined; +}; diff --git a/blog/loaders/GetCategories.ts b/blog/loaders/GetCategories.ts new file mode 100644 index 000000000..c2a2080b7 --- /dev/null +++ b/blog/loaders/GetCategories.ts @@ -0,0 +1,69 @@ +/** + * Retrieves a listing page of blog posts. + * + * @param props - The props for the blog post listing. + * @param req - The request object. + * @param ctx - The application context. + * @returns A promise that resolves to an array of blog posts. + */ +import { RequestURLParam } from "../../website/functions/requestToParam.ts"; +import { AppContext } from "../mod.ts"; +import { Category } from "../types.ts"; +import { getRecordsByPath } from "../utils/records.ts"; + +const COLLECTION_PATH = "collections/blog/categories"; +const ACCESSOR = "category"; + +export interface Props { + /** + * @title Category Slug + * @description Get the category data from a specific slug. + */ + slug?: RequestURLParam; + /** + * @title Items count + * @description Number of categories to return + */ + count?: number; + /** + * @title Sort + * @description The sorting option. Default is "title_desc" + */ + sortBy?: "title_asc" | "title_desc"; +} + +/** + * @title BlogPostList + * @description Retrieves a list of blog posts. + * + * @param props - The props for the blog post list. + * @param req - The request object. + * @param ctx - The application context. + * @returns A promise that resolves to an array of blog posts. + */ +export default async function GetCategories( + { count, slug, sortBy = "title_desc" }: Props, + _req: Request, + ctx: AppContext, +): Promise { + const categories = await getRecordsByPath( + ctx, + COLLECTION_PATH, + ACCESSOR, + ); + + if (!categories?.length) { + return null; + } + + if (slug) { + return categories.filter((c) => c.slug === slug); + } + + const sortedCategories = categories.sort((a, b) => { + const comparison = a.name.localeCompare(b.name); + return sortBy.endsWith("_desc") ? comparison : -comparison; + }); + + return count ? sortedCategories.slice(0, count) : sortedCategories; +} diff --git a/blog/manifest.gen.ts b/blog/manifest.gen.ts index 4cf5c47c0..157fa04d7 100644 --- a/blog/manifest.gen.ts +++ b/blog/manifest.gen.ts @@ -9,6 +9,7 @@ import * as $$$4 from "./loaders/BlogpostList.ts"; import * as $$$5 from "./loaders/BlogpostListing.ts"; import * as $$$2 from "./loaders/BlogPostPage.ts"; import * as $$$6 from "./loaders/Category.ts"; +import * as $$$7 from "./loaders/GetCategories.ts"; import * as $$$$$$0 from "./sections/Seo/SeoBlogPost.tsx"; import * as $$$$$$1 from "./sections/Seo/SeoBlogPostListing.tsx"; import * as $$$$$$2 from "./sections/Template.tsx"; @@ -22,6 +23,7 @@ const manifest = { "blog/loaders/BlogpostListing.ts": $$$5, "blog/loaders/BlogPostPage.ts": $$$2, "blog/loaders/Category.ts": $$$6, + "blog/loaders/GetCategories.ts": $$$7, }, "sections": { "blog/sections/Seo/SeoBlogPost.tsx": $$$$$$0, diff --git a/blog/mod.ts b/blog/mod.ts index 035d9cb5f..257937f83 100644 --- a/blog/mod.ts +++ b/blog/mod.ts @@ -1,19 +1,29 @@ -import type { App, FnContext } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { PreviewContainer } from "../utils/preview.tsx"; +import { type App, type FnContext } from "@deco/deco"; // deno-lint-ignore no-explicit-any export type State = any; - export type AppContext = FnContext; - /** * @title Deco Blog * @description Manage your posts. * @category Tool * @logo https://raw.githubusercontent.com/deco-cx/apps/main/weather/logo.png */ -export default function App( - state: State, -): App { +export default function App(state: State): App { return { manifest, state }; } +export const preview = () => { + return { + Component: PreviewContainer, + props: { + name: "Deco Blog", + owner: "deco.cx", + description: "Manage your posts, categories and authors.", + logo: + "https://raw.githubusercontent.com/deco-cx/apps/main/weather/logo.png", + images: [], + tabs: [], + }, + }; +}; diff --git a/blog/sections/Template.tsx b/blog/sections/Template.tsx index d1aab8f58..4ee094d3b 100644 --- a/blog/sections/Template.tsx +++ b/blog/sections/Template.tsx @@ -14,6 +14,7 @@ export default function Template({ post }: Props) { excerpt = "Excerpt", date, image, + alt, } = post; return ( @@ -32,7 +33,11 @@ export default function Template({ post }: Props) { : ""}

{image && ( - {title} + {alt )}
diff --git a/blog/types.ts b/blog/types.ts index d3cafbb4d..222b953e0 100644 --- a/blog/types.ts +++ b/blog/types.ts @@ -22,6 +22,10 @@ export interface BlogPost { title: string; excerpt: string; image?: ImageWidget; + /** + * @title Alt text for the image + */ + alt?: string; /** * @widget blog * @collection authors @@ -46,7 +50,21 @@ export interface BlogPost { * @title SEO */ seo?: Seo; + /** + * @title ReadTime in minutes + */ + readTime?: number; + /** + * @title Extra Props + */ + extraProps?: ExtraProps[]; +} + +export interface ExtraProps { + key: string; + value: string; } + export interface Seo { title?: string; description?: string; diff --git a/blog/utils/handlePosts.ts b/blog/utils/handlePosts.ts index a908d0e94..405fa325b 100644 --- a/blog/utils/handlePosts.ts +++ b/blog/utils/handlePosts.ts @@ -9,6 +9,7 @@ import { VALID_SORT_ORDERS } from "./constants.ts"; */ export const sortPosts = (blogPosts: BlogPost[], sortBy: SortBy) => { const splittedSort = sortBy.split("_"); + const sortMethod = splittedSort[0] in blogPosts[0] ? splittedSort[0] as keyof BlogPost : "date"; @@ -26,8 +27,12 @@ export const sortPosts = (blogPosts: BlogPost[], sortBy: SortBy) => { if (!b[sortMethod]) { return -1; // If post b doesn't have sort method, put it after post a } - const comparison = new Date(b.date).getTime() - - new Date(a.date).getTime(); // Sort in descending order + const comparison = sortMethod === "date" + ? new Date(b.date).getTime() - + new Date(a.date).getTime() + : a[sortMethod]?.toString().localeCompare( + b[sortMethod]?.toString() ?? "", + ) ?? 0; return sortOrder === "desc" ? comparison : -comparison; // Invert sort depending of desc or asc }); }; diff --git a/blog/utils/records.ts b/blog/utils/records.ts index 02744803c..6e19e463b 100644 --- a/blog/utils/records.ts +++ b/blog/utils/records.ts @@ -1,6 +1,5 @@ import { AppContext } from "../mod.ts"; -import { Resolvable } from "deco/engine/core/resolver.ts"; - +import { type Resolvable } from "@deco/deco"; export async function getRecordsByPath( ctx: AppContext, path: string, @@ -9,10 +8,8 @@ export async function getRecordsByPath( const resolvables: Record> = await ctx.get({ __resolveType: "resolvables", }); - const current = Object.entries(resolvables).flatMap(([key, value]) => { return key.startsWith(path) ? value : []; }); - return (current as Record[]).map((item) => item[accessor]); } diff --git a/brand-assistant/loaders/assistant.ts b/brand-assistant/loaders/assistant.ts index ea0295bd9..5d914dc94 100644 --- a/brand-assistant/loaders/assistant.ts +++ b/brand-assistant/loaders/assistant.ts @@ -1,12 +1,12 @@ // deno-lint-ignore-file ban-unused-ignore no-explicit-any -import type { ManifestOf } from "deco/mod.ts"; -import { logger } from "deco/observability/otel/config.ts"; import type { AIAssistant, Log, Prompt } from "../../ai-assistants/mod.ts"; import type { Category, Product, Suggestion } from "../../commerce/types.ts"; import type { Manifest as OpenAIManifest } from "../../openai/manifest.gen.ts"; import type vtex from "../../vtex/mod.ts"; import { Tokens } from "../../ai-assistants/loaders/messages.ts"; import type { AssistantPersonalization } from "../../ai-assistants/types.ts"; +import { type ManifestOf } from "@deco/deco"; +import { logger } from "@deco/deco/o11y"; export interface Props { name: string; productsSample?: Product[] | null; @@ -29,14 +29,11 @@ const removePropertiesRecursively = (category: T): T => { if (typeof category !== "object" || category === null) { return category; } - const { hasChildren: _ignoreHasChildren, url: _ignoreUrl, ...rest } = category as any; - rest.children = rest.children.map(removePropertiesRecursively); return rest; }; - type VTEXManifest = ManifestOf>; // TODO(ItamarRocha): Add store name in props or gather it from elsewhere. const BASE_INSTRUCTIONS = diff --git a/brand-assistant/mod.ts b/brand-assistant/mod.ts index cadccd709..eeb17be85 100644 --- a/brand-assistant/mod.ts +++ b/brand-assistant/mod.ts @@ -1,23 +1,33 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { PreviewContainer } from "../utils/preview.tsx"; +import { type App, type AppContext as AC } from "@deco/deco"; // deno-lint-ignore no-empty-interface export interface Props { } - /** * @title Deco Brand Assistant * @description A concierge for your ecommerce. * @category Sales channel * @logo https://raw.githubusercontent.com/deco-cx/apps/main/brand-assistant/logo.png */ -export default function App( - state: Props, -): App { +export default function App(state: Props): App { return { manifest, state, }; } - export type AppContext = AC>; +export const preview = () => { + return { + Component: PreviewContainer, + props: { + name: "Deco Brand Assistant", + owner: "deco.cx", + description: "A concierge for your ecommerce.", + logo: + "https://raw.githubusercontent.com/deco-cx/apps/main/brand-assistant/logo.png", + images: [], + tabs: [], + }, + }; +}; diff --git a/commerce/loaders/navbar.ts b/commerce/loaders/navbar.ts index 317ffa124..5e183222e 100644 --- a/commerce/loaders/navbar.ts +++ b/commerce/loaders/navbar.ts @@ -7,4 +7,6 @@ export interface Props { const loader = ({ items }: Props): SiteNavigationElement[] | null => items ?? null; +export const cache = "no-cache"; + export default loader; diff --git a/commerce/loaders/product/extensions/detailsPage.ts b/commerce/loaders/product/extensions/detailsPage.ts index c0e5cedcc..b530747d8 100644 --- a/commerce/loaders/product/extensions/detailsPage.ts +++ b/commerce/loaders/product/extensions/detailsPage.ts @@ -12,3 +12,5 @@ export default function ProductDetailsExt( ): Promise { return extend(props); } + +export const cache = "no-cache"; diff --git a/commerce/loaders/product/extensions/listingPage.ts b/commerce/loaders/product/extensions/listingPage.ts index 15bfc6a2a..2c0537f7a 100644 --- a/commerce/loaders/product/extensions/listingPage.ts +++ b/commerce/loaders/product/extensions/listingPage.ts @@ -12,3 +12,5 @@ export default function ProductDetailsExt( ): Promise { return extend(props); } + +export const cache = "no-cache"; diff --git a/commerce/mod.ts b/commerce/mod.ts index d9ecc00b0..3155c2920 100644 --- a/commerce/mod.ts +++ b/commerce/mod.ts @@ -1,4 +1,3 @@ -import { App, FnContext } from "deco/mod.ts"; import shopify, { Props as ShopifyProps } from "../shopify/mod.ts"; import vnda, { Props as VNDAProps } from "../vnda/mod.ts"; import vtex, { Props as VTEXProps } from "../vtex/mod.ts"; @@ -6,44 +5,39 @@ import wake, { Props as WakeProps } from "../wake/mod.ts"; import website, { Props as WebsiteProps } from "../website/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { bgYellow } from "std/fmt/colors.ts"; - +import { type App, type FnContext } from "@deco/deco"; export type AppContext = FnContext; - type CustomPlatform = { platform: "other"; }; - export type Props = WebsiteProps & { /** @deprecated Use selected commerce instead */ - commerce?: - | VNDAProps - | VTEXProps - | ShopifyProps - | WakeProps - | CustomPlatform; + commerce?: VNDAProps | VTEXProps | ShopifyProps | WakeProps | CustomPlatform; }; - type WebsiteApp = ReturnType; type CommerceApp = | ReturnType | ReturnType | ReturnType | ReturnType; - -export default function Site( - state: Props, -): App { +export default function Site(state: Props): App< + Manifest, + Props, + [ + WebsiteApp, + ] | [ + WebsiteApp, + CommerceApp, + ] +> { const { commerce } = state; - const site = website(state); - if (commerce && commerce.platform !== "other") { console.warn( bgYellow("Deprecated"), "Commerce prop is now deprecated. Delete this prop and install the commerce platform app instead. This will be removed in the future", ); } - const ecommerce = commerce?.platform === "vnda" ? vnda(commerce) : commerce?.platform === "vtex" @@ -53,7 +47,6 @@ export default function Site( : commerce?.platform === "shopify" ? shopify(commerce) : null; - return { state, manifest: { @@ -81,5 +74,4 @@ export default function Site( dependencies: ecommerce ? [site, ecommerce] : [site], }; } - export { onBeforeResolveProps } from "../website/mod.ts"; diff --git a/commerce/types.ts b/commerce/types.ts index 1ce8f5e52..81967c7be 100644 --- a/commerce/types.ts +++ b/commerce/types.ts @@ -1,10 +1,8 @@ -import { type Flag } from "deco/types.ts"; - +import { type Flag } from "@deco/deco"; /** Used at the top-level node to indicate the context for the JSON-LD objects used. The context provided in this type is compatible with the keys and URLs in the rest of this generated file. */ export declare type WithContext = T & { "@context": "https://schema.org"; }; - /** * An store category */ @@ -23,9 +21,7 @@ export interface Category { */ children?: Category[]; } - export declare type Things = Thing | Product | BreadcrumbList; - export interface Thing { "@type": "Thing"; /** IRI identifying the canonical address of this object. */ @@ -41,8 +37,8 @@ export interface Thing { /** The identifier property represents any kind of identifier for any kind of {@link https://schema.org/Thing Thing}, such as ISBNs, GTIN codes, UUIDs etc. Schema.org provides dedicated properties for representing many of these, either as textual strings or as URL (URI) links. See {@link /docs/datamodel.html#identifierBg background notes} for more details. */ identifier?: string; /** An image of the item. This can be a {@link https://schema.org/URL URL} or a fully described {@link https://schema.org/ImageObject ImageObject}. */ - image?: ImageObject[]; - video?: VideoObject[]; + image?: ImageObject[] | null; + video?: VideoObject[] | null; /** The name of the item. */ name?: string; /** URL of a reference Web page that unambiguously indicates the item's identity. E.g. the URL of the item's Wikipedia page, Wikidata entry, or official website. */ @@ -52,7 +48,6 @@ export interface Thing { /** URL of the item. */ url?: string; } - export interface MediaObject { /** Media type typically expressed using a MIME format (see IANA site and MDN reference) */ encodingFormat?: string; @@ -61,12 +56,10 @@ export interface MediaObject { /** Actual bytes of the media object, for example the image file or video file. */ contentUrl?: string; } - export interface CreativeWork { /** A thumbnail image relevant to the Thing */ thumbnailUrl?: string; } - export interface VideoObject extends MediaObject, CreativeWork, Omit { /** @@ -83,7 +76,6 @@ export interface VideoObject */ duration?: string; } - export interface ImageObject extends MediaObject, CreativeWork, Omit { /** @@ -95,7 +87,6 @@ export interface ImageObject */ url?: string; } - export interface PropertyValue extends Omit { "@type": "PropertyValue"; /** The upper value of some characteristic or property. */ @@ -119,7 +110,6 @@ export interface PropertyValue extends Omit { /** A secondary value that provides additional information on the original value, e.g. a reference temperature or a type of measurement. */ valueReference?: string; } - export interface AggregateRating { "@type": "AggregateRating"; /** The count of total number of ratings. */ @@ -135,7 +125,6 @@ export interface AggregateRating { /** A short explanation (e.g. one to two sentences) providing background context and other information that led to the conclusion expressed in the rating. This is particularly applicable to ratings associated with "fact check" markup using ClaimReview. */ ratingExplanation?: string; } - export declare type ItemAvailability = | "https://schema.org/BackOrder" | "https://schema.org/Discontinued" @@ -147,17 +136,14 @@ export declare type ItemAvailability = | "https://schema.org/PreOrder" | "https://schema.org/PreSale" | "https://schema.org/SoldOut"; - export declare type OfferItemCondition = | "https://schema.org/DamagedCondition" | "https://schema.org/NewCondition" | "https://schema.org/RefurbishedCondition" | "https://schema.org/UsedCondition"; - export interface QuantitativeValue { value?: number; } - export declare type PriceTypeEnumeration = | "https://schema.org/InvoicePrice" | "https://schema.org/ListPrice" @@ -165,7 +151,6 @@ export declare type PriceTypeEnumeration = | "https://schema.org/MSRP" | "https://schema.org/SalePrice" | "https://schema.org/SRP"; - export declare type PriceComponentTypeEnumeration = | "https://schema.org/ActivationFee" | "https://schema.org/CleaningFee" @@ -173,7 +158,22 @@ export declare type PriceComponentTypeEnumeration = | "https://schema.org/Downpayment" | "https://schema.org/Installment" | "https://schema.org/Subscription"; - +export declare type ReturnFeesEnumeration = + | "https://schema.org/FreeReturn" + | "https://schema.org/OriginalShippingFees" + | "https://schema.org/RestockingFees" + | "https://schema.org/ReturnFeesCustomerResponsibility" + | "https://schema.org/ReturnShippingFees"; +export declare type ReturnMethodEnumeration = + | "https://schema.org/KeepProduct" + | "https://schema.org/ReturnAtKiosk" + | "https://schema.org/ReturnByMail" + | "https://schema.org/ReturnInStore"; +export declare type MerchantReturnEnumeration = + | "https://schema.org/MerchantReturnFiniteReturnWindow" + | "https://schema.org/MerchantReturnNotPermitted" + | "https://schema.org/MerchantReturnUnlimitedWindow" + | "https://schema.org/MerchantReturnUnspecified"; export interface PriceSpecification extends Omit { "@type": "PriceSpecification"; /** The interval and unit of measurement of ordering quantities for which the offer or price specification is valid. This allows e.g. specifying that a certain freight charge is valid only for a certain quantity. */ @@ -195,7 +195,6 @@ export interface PriceSpecification extends Omit { */ priceCurrency?: string; } - export interface UnitPriceSpecification extends Omit { "@type": "UnitPriceSpecification"; @@ -208,28 +207,58 @@ export interface UnitPriceSpecification /** This property specifies the minimal quantity and rounding increment that will be the basis for the billing. The unit of measurement is specified by the unitCode property. */ billingIncrement?: number; } - export interface TeasersParameters { name: string; value: string; } - export interface TeasersConditions { minimumQuantity: number; parameters: TeasersParameters[]; } - export interface TeasersEffect { parameters: TeasersParameters[]; } - export interface Teasers { name: string; generalValues?: unknown; conditions: TeasersConditions; effects: TeasersEffect; } - +export interface MonetaryAmount extends Omit { + /** + * The currency in which the monetary amount is expressed. + * + * Use standard formats: ISO 4217 currency format, e.g. "USD"; Ticker symbol for cryptocurrencies, e.g. "BTC"; well known names for Local Exchange Trading Systems (LETS) and other currency types, e.g. "Ithaca HOUR". + */ + currency: string; + /** + * The upper value of some characteristic or property. + */ + maxValue: number; + /** The lower value of some characteristic or property. */ + minValue: number; + /** The date when the item becomes valid. */ + validFrom: string; + /** The date after when the item is not valid. For example the end of an offer, salary period, or a period of opening hours. */ + validThrough: string; + /** The value of a QuantitativeValue (including Observation) or property value node. */ + value: boolean | number | string; +} +export interface MerchantReturnPolicy extends Omit { + "@type": "MerchantReturnPolicy"; + /** Specifies either a fixed return date or the number of days (from the delivery date) that a product can be returned. Used when the returnPolicyCategory property is specified as MerchantReturnFiniteReturnWindow. Supersedes productReturnDays */ + merchantReturnDays?: number; + /** A country where a particular merchant return policy applies to, for example the two-letter ISO 3166-1 alpha-2 country code. */ + applicableCountry: string; + /** The type of return fees for purchased products (for any return reason). */ + returnFees?: ReturnFeesEnumeration; + /** The type of return method offered, specified from an enumeration. */ + returnMethod?: ReturnMethodEnumeration; + /** Specifies an applicable return policy (from an enumeration). */ + returnPolicyCategory: MerchantReturnEnumeration; + /** Amount of shipping costs for product returns (for any reason). Applicable when property returnFees equals ReturnShippingFees. */ + returnShippingFeesAmount?: MonetaryAmount; +} export interface Offer extends Omit { "@type": "Offer"; /** The availability of this itemβ€”for example In stock, Out of stock, Pre-order, etc. */ @@ -270,8 +299,9 @@ export interface Offer extends Omit { giftSkuIds?: string[]; /** Used by some ecommerce providers (e.g: VTEX) to describe special promotions that depend on some conditions */ teasers?: Teasers[]; + /** Specifies a MerchantReturnPolicy that may be applicable. */ + hasMerchantReturnPolicy?: MerchantReturnPolicy; } - export interface AggregateOffer { "@type": "AggregateOffer"; /** @@ -300,8 +330,9 @@ export interface AggregateOffer { * Use standard formats: {@link http://en.wikipedia.org/wiki/ISO_4217 ISO 4217 currency format} e.g. "USD"; {@link https://en.wikipedia.org/wiki/List_of_cryptocurrencies Ticker symbol} for cryptocurrencies e.g. "BTC"; well known names for {@link https://en.wikipedia.org/wiki/Local_exchange_trading_system Local Exchange Tradings Systems} (LETS) and other currency types e.g. "Ithaca HOUR". */ priceCurrency?: string; + /** Specifies a MerchantReturnPolicy that may be applicable. */ + hasMerchantReturnPolicy?: MerchantReturnPolicy; } - export interface ReviewPageResults { currentPageNumber?: number; nextPageUrl?: string; @@ -309,14 +340,12 @@ export interface ReviewPageResults { pagesTotal?: number; totalResults?: number; } - export interface ReviewPage { page: ReviewPageResults; id: string; review?: Review[]; aggregateRating?: AggregateRating; } - export interface Review extends Omit { "@type": "Review"; id?: string; @@ -345,7 +374,6 @@ export interface Review extends Omit { /** Medias */ media?: ReviewMedia[]; } - export interface ReviewMedia { type: "image" | "video"; url?: string; @@ -353,7 +381,6 @@ export interface ReviewMedia { likes?: number; unlikes?: number; } - export interface ReviewBrand { /** Brand Name */ name: string; @@ -362,14 +389,12 @@ export interface ReviewBrand { /** Brand website url */ url: string; } - export interface ReviewTag { /** Label of specific topic */ label?: string; /** Caracteristics about the topic */ value?: string[]; } - /** https://schema.org/Person */ export interface Person extends Omit { /** Email address. */ @@ -381,11 +406,10 @@ export interface Person extends Omit { /** Gender of something, typically a Person, but possibly also fictional characters, animals, etc */ gender?: "https://schema.org/Male" | "https://schema.org/Female"; /** An image of the item. This can be a URL or a fully described ImageObject. **/ - image?: ImageObject[]; + image?: ImageObject[] | null; /** The Tax / Fiscal ID of the organization or person, e.g. the TIN in the US or the CIF/NIF in Spain. */ taxID?: string; } - // NON SCHEMA.ORG Compliant. Should be removed ASAP export interface Author extends Omit { "@type": "Author"; @@ -398,11 +422,10 @@ export interface Author extends Omit { /** Author location */ location?: string; } - // TODO: fix this hack and use Product directly where it appears // Hack to prevent type self referencing and we end up with an infinite loop -export interface ProductLeaf extends Omit {} - +export interface ProductLeaf extends Omit { +} export interface ProductGroup extends Omit { "@type": "ProductGroup"; /** Indicates a {@link https://schema.org/Product Product} that is a member of this {@link https://schema.org/ProductGroup ProductGroup} (or {@link https://schema.org/ProductModel ProductModel}). */ @@ -418,13 +441,11 @@ export interface ProductGroup extends Omit { /** docs https://schema.org/gtin */ model?: string; } - export interface Brand extends Omit { "@type": "Brand"; /** Brand's image url */ logo?: string; } - export interface Answer extends Omit { text: string; /** The date that the anwser was published, in ISO 8601 date format.*/ @@ -434,7 +455,6 @@ export interface Answer extends Omit { /** Author of the */ author?: Author[]; } - export interface Question extends Omit { "@type": "Question"; answerCount: number; @@ -451,7 +471,6 @@ export interface Question extends Omit { /** Author of the */ author?: Author[]; } - export interface Product extends Omit { "@type": "Product"; /** @@ -474,9 +493,9 @@ export interface Product extends Omit { inProductGroupWithID?: string; // TODO: Make json schema generator support self-referencing types // /** A pointer to another, somehow related product (or multiple products). */ - isRelatedTo?: Product[]; + isRelatedTo?: Product[] | null; /** A pointer to another, functionally similar product (or multiple products). */ - isSimilarTo?: Product[]; + isSimilarTo?: Product[] | null; /** Indicates the kind of product that this is a variant of. In the case of {@link https://schema.org/ProductModel ProductModel}, this is a pointer (from a ProductModel) to a base product from which this product is a variant. It is safe to infer that the variant inherits all product features from the base model, unless defined locally. This is not transitive. In the case of a {@link https://schema.org/ProductGroup ProductGroup}, the group description also serves as a template, representing a set of Products that vary on explicitly defined, specific dimensions only (so it defines both a set of variants, as well as which values distinguish amongst those variants). When used with {@link https://schema.org/ProductGroup ProductGroup}, this property can apply to any {@link https://schema.org/Product Product} included in the group. */ isVariantOf?: ProductGroup; /** An offer to provide this itemβ€”for example, an offer to sell a product, rent the DVD of a movie, perform a service, or give away tickets to an event. Use {@link https://schema.org/businessFunction businessFunction} to indicate the kind of transaction offered, i.e. sell, lease, etc. This property can also be used to describe a {@link https://schema.org/Demand Demand}. While this property is listed as expected on a number of common types, it can be used in others. In that case, using a second type, such as Product or a subtype of Product, can clarify the nature of the offer. */ @@ -492,11 +511,9 @@ export interface Product extends Omit { /** The Stock Keeping Unit (SKU), i.e. a merchant-specific identifier for a product or service, or the product to which the offer refers. */ sku: string; /** A pointer to another product (or multiple products) for which this product is an accessory or spare part. */ - isAccessoryOrSparePartFor?: Product[]; - + isAccessoryOrSparePartFor?: Product[] | null; questions?: Question[]; } - export interface ListItem extends Omit { "@type": "ListItem"; /** An entity represented by an entry in a list or data feed (e.g. an 'artist' in a list of 'artists')’. */ @@ -504,7 +521,6 @@ export interface ListItem extends Omit { /** The position of an item in a series or sequence of items. */ position: number; } - export interface ItemList extends Omit { "@type": "ItemList"; /** @@ -518,11 +534,9 @@ export interface ItemList extends Omit { /** The number of items in an ItemList. Note that some descriptions might not fully describe all items in a list (e.g., multi-page pagination); in such cases, the numberOfItems would be for the entire list. */ numberOfItems: number; } - export interface BreadcrumbList extends Omit { "@type": "BreadcrumbList"; } - export type DayOfWeek = | "Monday" | "Tuesday" @@ -532,7 +546,6 @@ export type DayOfWeek = | "Saturday" | "Sunday" | "PublicHolidays"; - export interface OpeningHoursSpecification extends Omit { "@type": "OpeningHoursSpecification"; /** The closing hour of the place or service on the given day(s) of the week. */ @@ -546,7 +559,6 @@ export interface OpeningHoursSpecification extends Omit { /** The date after when the item is not valid. For example the end of an offer, salary period, or a period of opening hours. */ validThrough?: string; } - export interface ContactPoint extends Omit { "@type": "ContactPoint"; /** The geographic area where a service or offered item is provided. */ @@ -568,7 +580,6 @@ export interface ContactPoint extends Omit { /** The telephone number. */ telephone?: string; } - export interface PostalAddress extends Omit { "@type": "PostalAddress"; /** The country. For example, USA. You can also provide the two-letter ISO 3166-1 alpha-2 country code. */ @@ -582,7 +593,6 @@ export interface PostalAddress extends Omit { /** The street address. For example, 1600 Amphitheatre Pkwy. */ streetAddress?: string; } - export interface LocationFeatureSpecification extends Omit { "@type": "LocationFeatureSpecification"; @@ -593,7 +603,6 @@ export interface LocationFeatureSpecification /** The date after when the item is not valid. For example the end of an offer, salary period, or a period of opening hours. */ validThrough?: string; } - export interface GeoCoordinates extends Omit { "@type": "GeoCoordinates"; /** The geographic area where a service or offered item is provided. */ @@ -609,7 +618,6 @@ export interface GeoCoordinates extends Omit { /** The postal code. For example, 94043. */ postalCode?: string; } - export interface GeoShape extends Omit { "@type": "GeoShape"; /** The GeoShape for the GeoCoordinates or GeoCircle. */ @@ -625,11 +633,9 @@ export interface GeoShape extends Omit { /** The postal code. For example, 94043. */ postalCode?: string; } - export interface About extends Omit { "@type": "About"; } - export interface Rating extends Omit { "@type": "Rating"; /** The author of this content or rating. Please note that author is special in that HTML 5 provides a special mechanism for indicating authorship via the rel tag. That is equivalent to this and may be used interchangeably. */ @@ -652,19 +658,15 @@ export interface Rating extends Omit { /** The lowest value allowed in this rating system. */ worstRating?: number; } - export interface Organization extends Omit { "@type": "Organization"; } - export interface AdministrativeArea extends Omit { "@type": "AdministrativeArea"; } - export type CertificationStatus = | "CertificationActive" | "CertificationInactive"; - export interface Certification extends Omit { "@type": "Certification"; /** The subject matter of the content. */ @@ -692,7 +694,6 @@ export interface Certification extends Omit { /** The geographic area where the item is valid. Applies for example to a Permit, a Certification, or an EducationalOccupationalCredential. */ validIn?: AdministrativeArea; } - export interface PlaceLeaf extends Omit { "@type": "Place"; /** A property-value pair representing an additional characteristics of the entitity, e.g. a product feature or another characteristic for which there is no matching property in schema.org. */ @@ -752,7 +753,6 @@ export interface PlaceLeaf extends Omit { /** A page providing information on how to book a tour of some Place, such as an Accommodation or ApartmentComplex in a real estate setting, as well as other kinds of tours as appropriate. */ tourBookingPage?: string; } - /** Entities that have a somewhat fixed, physical extension. */ export interface Place extends PlaceLeaf { /** The basic containment relation between a place and one that contains it. Supersedes containedIn. Inverse property: containsPlace. */ @@ -778,7 +778,6 @@ export interface Place extends PlaceLeaf { /** Represents a relationship between two geometries (or the places they represent), relating a geometry to another that lies on it. As defined in DE-9IM. */ geoWithin?: PlaceLeaf; } - export interface FilterToggleValue { quantity: number; label: string; @@ -787,37 +786,34 @@ export interface FilterToggleValue { url: string; children?: Filter | null; } - export interface FilterRangeValue { min: number; max: number; } - export interface FilterBase { label: string; key: string; } - export interface FilterToggle extends FilterBase { "@type": "FilterToggle"; values: FilterToggleValue[]; quantity: number; } - export interface FilterRange extends FilterBase { "@type": "FilterRange"; values: FilterRangeValue; } - export type Filter = FilterToggle | FilterRange; -export type SortOption = { value: string; label: string }; +export type SortOption = { + value: string; + label: string; +}; export interface ProductDetailsPage { "@type": "ProductDetailsPage"; breadcrumbList: BreadcrumbList; product: Product; seo?: Seo | null; } - export type PageType = | "Brand" | "Category" @@ -828,7 +824,6 @@ export type PageType = | "Cluster" | "Search" | "Unknown"; - export interface PageInfo { currentPage: number; nextPage: string | undefined; @@ -837,7 +832,6 @@ export interface PageInfo { recordPerPage?: number | undefined; pageTypes?: PageType[]; } - export interface ProductListingPage { "@type": "ProductListingPage"; breadcrumb: BreadcrumbList; @@ -847,27 +841,26 @@ export interface ProductListingPage { sortOptions: SortOption[]; seo?: Seo | null; } - export interface Seo { title: string; description: string; canonical: string; noIndexing?: boolean; } - export interface Search { term: string; href?: string; hits?: number; - facets?: Array<{ key: string; values: string[] }>; + facets?: Array<{ + key: string; + values: string[]; + }>; } - export interface Suggestion { searches?: Search[]; - products?: Product[]; + products?: Product[] | null; hits?: number; } - /** @titleBy url */ export interface SiteNavigationElementLeaf { /** @@ -879,13 +872,12 @@ export interface SiteNavigationElementLeaf { /** The identifier property represents any kind of identifier for any kind of {@link https://schema.org/Thing Thing}, such as ISBNs, GTIN codes, UUIDs etc. Schema.org provides dedicated properties for representing many of these, either as textual strings or as URL (URI) links. See {@link /docs/datamodel.html#identifierBg background notes} for more details. */ identifier?: string; /** An image of the item. This can be a {@link https://schema.org/URL URL} or a fully described {@link https://schema.org/ImageObject ImageObject}. */ - image?: ImageObject[]; + image?: ImageObject[] | null; /** The name of the item. */ name?: string; /** URL of the item. */ url?: string; } - export interface SiteNavigationElement extends SiteNavigationElementLeaf { // TODO: The schema generator is not handling recursive types leading to an infinite loop // Lets circunvent this issue by enumerating the max allowed depth @@ -907,41 +899,36 @@ export interface SiteNavigationElement extends SiteNavigationElementLeaf { } >; } - /** @deprecated Use SiteNavigationElement instead */ export interface NavItem { label: string; href: string; - image?: { src?: string; alt?: string }; + image?: { + src?: string; + alt?: string; + }; } - /** @deprecated Use SiteNavigationElement instead */ export interface Navbar extends NavItem { // TODO: The schema generator is not handling recursive types leading in a infinite recursion loop // deno-lint-ignore no-explicit-any children?: any[]; } - // deno-lint-ignore no-explicit-any export interface IEvent { name: string; params: Params; } - // 3 letter ISO 4217 - Doc: https://en.wikipedia.org/wiki/ISO_4217#Active_codes type Currency = string; type Value = number; - interface WithItemId { item_id: string; } - interface WithItemName { item_name: string; } - type ItemIdentifier = WithItemId | WithItemName; - interface AnalyticsItemWithoutIdentifier { affiliation?: string; coupon?: string; @@ -962,9 +949,7 @@ interface AnalyticsItemWithoutIdentifier { price?: Value; quantity: number; } - export type AnalyticsItem = AnalyticsItemWithoutIdentifier & ItemIdentifier; - export interface AddShippingInfoParams { currency?: Currency; value?: Value; @@ -972,46 +957,38 @@ export interface AddShippingInfoParams { shipping_tier?: string; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_shipping_info */ export interface AddShippingInfoEvent extends IEvent { name: "add_shipping_info"; } - export interface AddToCartParams { currency?: Currency; value?: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_to_cart */ export interface AddToCartEvent extends IEvent { name: "add_to_cart"; } - export interface AddToWishlistParams { currency?: Currency; value?: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_to_wishlist */ export interface AddToWishlistEvent extends IEvent { name: "add_to_wishlist"; } - export interface BeginCheckoutParams { currency: Currency; value: Value; items: AnalyticsItem[]; coupon?: string; } - /** docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#begin_checkout */ export interface BeginCheckoutEvent extends IEvent { name: "begin_checkout"; } - export interface LoginParams { method?: string; } @@ -1019,37 +996,30 @@ export interface LoginParams { export interface LoginEvent extends IEvent { name: "login"; } - export interface RemoveFromCartParams { currency?: Currency; value?: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#remove_from_cart */ export interface RemoveFromCartEvent extends IEvent { name: "remove_from_cart"; } - export interface SearchParams { search_term: string; } - export interface SearchEvent extends IEvent { name: "search"; } - export interface SelectItemParams { item_list_id?: string; item_list_name?: string; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#select_item */ export interface SelectItemEvent extends IEvent { name: "select_item"; } - export interface SelectPromotionParams { creative_name?: string; creative_slot?: string; @@ -1057,45 +1027,37 @@ export interface SelectPromotionParams { promotion_name?: string; items?: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#select_promotion */ export interface SelectPromotionEvent extends IEvent { name: "select_promotion"; } - export interface ViewCartParams { currency: Currency; value: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#view_cart */ export interface ViewCartEvent extends IEvent { name: "view_cart"; } - export interface ViewItemParams { currency?: Currency; value?: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#view_item */ export interface ViewItemEvent extends IEvent { name: "view_item"; } - export interface ViewItemListParams { item_list_id?: string; item_list_name?: string; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#view_item_list */ export interface ViewItemListEvent extends IEvent { name: "view_item_list"; } - export interface ViewPromotionParams { creative_name?: string; creative_slot?: string; @@ -1103,26 +1065,21 @@ export interface ViewPromotionParams { promotion_name?: string; items?: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#view_promotion */ export interface ViewPromotionEvent extends IEvent { name: "view_promotion"; } - export interface Page { id: string | number; pathTemplate?: string; } - export interface Deco { flags: Flag[]; page: Page; } - export interface DecoEvent extends IEvent { name: "deco"; } - export type AnalyticsEvent = | AddShippingInfoEvent | AddToCartEvent diff --git a/compat/$live/actions/secrets/encrypt.ts b/compat/$live/actions/secrets/encrypt.ts new file mode 100644 index 000000000..d49fc24b0 --- /dev/null +++ b/compat/$live/actions/secrets/encrypt.ts @@ -0,0 +1,2 @@ +export * from "../../../../website/actions/secrets/encrypt.ts"; +export { default } from "../../../../website/actions/secrets/encrypt.ts"; diff --git a/compat/$live/actions/workflows/cancel.ts b/compat/$live/actions/workflows/cancel.ts new file mode 100644 index 000000000..94fc4bda7 --- /dev/null +++ b/compat/$live/actions/workflows/cancel.ts @@ -0,0 +1,2 @@ +export * from "../../../../workflows/actions/cancel.ts"; +export { default } from "../../../../workflows/actions/cancel.ts"; diff --git a/compat/$live/actions/workflows/signal.ts b/compat/$live/actions/workflows/signal.ts new file mode 100644 index 000000000..763612819 --- /dev/null +++ b/compat/$live/actions/workflows/signal.ts @@ -0,0 +1,2 @@ +export * from "../../../../workflows/actions/signal.ts"; +export { default } from "../../../../workflows/actions/signal.ts"; diff --git a/compat/$live/actions/workflows/start.ts b/compat/$live/actions/workflows/start.ts new file mode 100644 index 000000000..dec179989 --- /dev/null +++ b/compat/$live/actions/workflows/start.ts @@ -0,0 +1,2 @@ +export * from "../../../../workflows/actions/start.ts"; +export { default } from "../../../../workflows/actions/start.ts"; diff --git a/compat/$live/flags/audience.ts b/compat/$live/flags/audience.ts new file mode 100644 index 000000000..814ea30d0 --- /dev/null +++ b/compat/$live/flags/audience.ts @@ -0,0 +1,2 @@ +export * from "../../../website/flags/audience.ts"; +export { default } from "../../../website/flags/audience.ts"; diff --git a/compat/$live/flags/everyone.ts b/compat/$live/flags/everyone.ts new file mode 100644 index 000000000..86196faad --- /dev/null +++ b/compat/$live/flags/everyone.ts @@ -0,0 +1,2 @@ +export * from "../../../website/flags/everyone.ts"; +export { default } from "../../../website/flags/everyone.ts"; diff --git a/compat/$live/flags/flag.ts b/compat/$live/flags/flag.ts new file mode 100644 index 000000000..b624c2b72 --- /dev/null +++ b/compat/$live/flags/flag.ts @@ -0,0 +1,2 @@ +export * from "../../../website/flags/flag.ts"; +export { default } from "../../../website/flags/flag.ts"; diff --git a/compat/$live/flags/multivariate.ts b/compat/$live/flags/multivariate.ts new file mode 100644 index 000000000..9486a0757 --- /dev/null +++ b/compat/$live/flags/multivariate.ts @@ -0,0 +1,2 @@ +export * from "../../../website/flags/multivariate.ts"; +export { default } from "../../../website/flags/multivariate.ts"; diff --git a/compat/$live/handlers/devPage.ts b/compat/$live/handlers/devPage.ts index 4ee13a6b8..1fd994e21 100644 --- a/compat/$live/handlers/devPage.ts +++ b/compat/$live/handlers/devPage.ts @@ -1,14 +1,12 @@ -import { Page } from "deco/blocks/page.tsx"; -import { context } from "deco/live.ts"; -import { adminUrlFor, isAdmin } from "deco/utils/admin.ts"; import Fresh from "../../../website/handlers/fresh.ts"; import { pageIdFromMetadata } from "../../../website/pages/Page.tsx"; import { AppContext } from "../mod.ts"; - +import { type Page } from "@deco/deco/blocks"; +import { context } from "@deco/deco"; +import { adminUrlFor, isAdmin } from "@deco/deco/utils"; export interface DevConfig { page: Page; } - /** * @title Private Fresh Page * @description Useful for pages under development. @@ -19,18 +17,13 @@ export default function DevPage(devConfig: DevConfig, ctx: AppContext) { const referer = req.headers.get("origin") ?? req.headers.get("referer"); const isOnAdmin = referer && isAdmin(referer); const pageId = pageIdFromMetadata(devConfig.page.metadata); - - if ( - context.isDeploy - ) { + if (context.isDeploy) { if (!referer || !isOnAdmin) { if (pageId === -1) { return Response.error(); } // redirect - return Response.redirect( - adminUrlFor(pageId, context.siteId), - ); + return Response.redirect(adminUrlFor(pageId, context.siteId)); } } return freshHandler(req, ctx); diff --git a/compat/$live/handlers/fresh.ts b/compat/$live/handlers/fresh.ts new file mode 100644 index 000000000..c5fa2beec --- /dev/null +++ b/compat/$live/handlers/fresh.ts @@ -0,0 +1,2 @@ +export * from "../../../website/handlers/fresh.ts"; +export { default } from "../../../website/handlers/fresh.ts"; diff --git a/compat/$live/handlers/proxy.ts b/compat/$live/handlers/proxy.ts new file mode 100644 index 000000000..f2b156e02 --- /dev/null +++ b/compat/$live/handlers/proxy.ts @@ -0,0 +1,2 @@ +export * from "../../../website/handlers/proxy.ts"; +export { default } from "../../../website/handlers/proxy.ts"; diff --git a/compat/$live/handlers/redirect.ts b/compat/$live/handlers/redirect.ts new file mode 100644 index 000000000..321532476 --- /dev/null +++ b/compat/$live/handlers/redirect.ts @@ -0,0 +1,2 @@ +export * from "../../../website/handlers/redirect.ts"; +export { default } from "../../../website/handlers/redirect.ts"; diff --git a/compat/$live/handlers/router.ts b/compat/$live/handlers/router.ts index 08a5b9671..37205272a 100644 --- a/compat/$live/handlers/router.ts +++ b/compat/$live/handlers/router.ts @@ -1,19 +1,16 @@ -import { Handler } from "deco/blocks/handler.ts"; -import { FnContext } from "deco/types.ts"; import { Routes } from "../../../website/flags/audience.ts"; import { router } from "../../../website/handlers/router.ts"; - +import { type Handler } from "@deco/deco/blocks"; +import { type FnContext } from "@deco/deco"; export interface RouterConfig { base?: string; routes: Routes; } - -export default function Router({ - routes: entrypoints, - base, -}: RouterConfig, ctx: FnContext): Handler { +export default function Router( + { routes: entrypoints, base }: RouterConfig, + ctx: FnContext, +): Handler { let routes = entrypoints; - if (base) { routes = []; for (const route of routes) { @@ -24,6 +21,5 @@ export default function Router({ ]; } } - return router(routes, {}, ctx.get.bind(ctx)); } diff --git a/compat/$live/handlers/routesSelection.ts b/compat/$live/handlers/routesSelection.ts new file mode 100644 index 000000000..6c4d73a6a --- /dev/null +++ b/compat/$live/handlers/routesSelection.ts @@ -0,0 +1,2 @@ +export * from "../../../website/handlers/router.ts"; +export { default } from "../../../website/handlers/router.ts"; diff --git a/compat/$live/handlers/workflowRunner.ts b/compat/$live/handlers/workflowRunner.ts new file mode 100644 index 000000000..20aae32d6 --- /dev/null +++ b/compat/$live/handlers/workflowRunner.ts @@ -0,0 +1,2 @@ +export * from "../../../workflows/handlers/workflowRunner.ts"; +export { default } from "../../../workflows/handlers/workflowRunner.ts"; diff --git a/compat/$live/loaders/state.ts b/compat/$live/loaders/state.ts index ca2dc9a87..029c4d81c 100644 --- a/compat/$live/loaders/state.ts +++ b/compat/$live/loaders/state.ts @@ -1,11 +1,12 @@ -import { Accounts } from "deco/blocks/account.ts"; -import { Flag } from "deco/blocks/flag.ts"; -import { Loader } from "deco/blocks/loader.ts"; -import { Page } from "deco/blocks/page.tsx"; -import { Section } from "deco/blocks/section.ts"; -import { Resolvable } from "deco/engine/core/resolver.ts"; -import { Apps, LoaderContext } from "deco/mod.ts"; - +import { + type Accounts, + type Apps, + type Flag, + type Loader, + type Page, + type Section, +} from "@deco/deco/blocks"; +import { type LoaderContext, type Resolvable } from "@deco/deco"; /** * @titleBy key */ @@ -17,7 +18,6 @@ export interface Props { state: StateProp[]; apps?: Apps[]; } - /** * @title Shared application State Loader. * @description Set the application state using resolvables. @@ -27,15 +27,19 @@ export default async function StateLoader( _req: Request, { get }: LoaderContext, ): Promise { - const mState: Promise<[string, Resolvable]>[] = []; - + const mState: Promise<[ + string, + Resolvable, + ]>[] = []; for (const { key, value } of state) { const resolved = get(value).then((resolved) => - [key, resolved] as [string, Resolvable] + [key, resolved] as [ + string, + Resolvable, + ] ); mState.push(resolved); } - return { state: Object.fromEntries(await Promise.all(mState)), apps, diff --git a/compat/$live/loaders/workflows/events.ts b/compat/$live/loaders/workflows/events.ts new file mode 100644 index 000000000..515e4e23e --- /dev/null +++ b/compat/$live/loaders/workflows/events.ts @@ -0,0 +1,2 @@ +export * from "../../../../workflows/loaders/events.ts"; +export { default } from "../../../../workflows/loaders/events.ts"; diff --git a/compat/$live/loaders/workflows/get.ts b/compat/$live/loaders/workflows/get.ts new file mode 100644 index 000000000..be7b05309 --- /dev/null +++ b/compat/$live/loaders/workflows/get.ts @@ -0,0 +1,2 @@ +export * from "../../../../workflows/loaders/get.ts"; +export { default } from "../../../../workflows/loaders/get.ts"; diff --git a/compat/$live/manifest.gen.ts b/compat/$live/manifest.gen.ts index 4a586532e..670b55a2d 100644 --- a/compat/$live/manifest.gen.ts +++ b/compat/$live/manifest.gen.ts @@ -2,10 +2,37 @@ // This file SHOULD be checked into source version control. // This file is automatically updated during development when running `dev.ts`. +import * as $$$$$$$$$0 from "./actions/secrets/encrypt.ts"; +import * as $$$$$$$$$1 from "./actions/workflows/cancel.ts"; +import * as $$$$$$$$$2 from "./actions/workflows/signal.ts"; +import * as $$$$$$$$$3 from "./actions/workflows/start.ts"; +import * as $$$$$$$$0 from "./flags/audience.ts"; +import * as $$$$$$$$1 from "./flags/everyone.ts"; +import * as $$$$$$$$2 from "./flags/flag.ts"; +import * as $$$$$$$$3 from "./flags/multivariate.ts"; import * as $$$$0 from "./handlers/devPage.ts"; -import * as $$$$1 from "./handlers/router.ts"; +import * as $$$$1 from "./handlers/fresh.ts"; +import * as $$$$2 from "./handlers/proxy.ts"; +import * as $$$$3 from "./handlers/redirect.ts"; +import * as $$$$4 from "./handlers/router.ts"; +import * as $$$$5 from "./handlers/routesSelection.ts"; +import * as $$$$6 from "./handlers/workflowRunner.ts"; import * as $$$0 from "./loaders/secret.ts"; import * as $$$1 from "./loaders/state.ts"; +import * as $$$2 from "./loaders/workflows/events.ts"; +import * as $$$3 from "./loaders/workflows/get.ts"; +import * as $$$$$$$0 from "./matchers/MatchAlways.ts"; +import * as $$$$$$$1 from "./matchers/MatchCron.ts"; +import * as $$$$$$$2 from "./matchers/MatchDate.ts"; +import * as $$$$$$$3 from "./matchers/MatchDevice.ts"; +import * as $$$$$$$4 from "./matchers/MatchEnvironment.ts"; +import * as $$$$$$$5 from "./matchers/MatchHost.ts"; +import * as $$$$$$$6 from "./matchers/MatchLocation.ts"; +import * as $$$$$$$7 from "./matchers/MatchMulti.ts"; +import * as $$$$$$$8 from "./matchers/MatchRandom.ts"; +import * as $$$$$$$9 from "./matchers/MatchSite.ts"; +import * as $$$$$$$10 from "./matchers/MatchUserAgent.ts"; +import * as $$$$$0 from "./pages/LivePage.tsx"; import * as $$$$$$0 from "./sections/EmptySection.tsx"; import * as $$$$$$1 from "./sections/PageInclude.tsx"; import * as $$$$$$2 from "./sections/Slot.tsx"; @@ -14,16 +41,51 @@ const manifest = { "loaders": { "$live/loaders/secret.ts": $$$0, "$live/loaders/state.ts": $$$1, + "$live/loaders/workflows/events.ts": $$$2, + "$live/loaders/workflows/get.ts": $$$3, }, "handlers": { "$live/handlers/devPage.ts": $$$$0, - "$live/handlers/router.ts": $$$$1, + "$live/handlers/fresh.ts": $$$$1, + "$live/handlers/proxy.ts": $$$$2, + "$live/handlers/redirect.ts": $$$$3, + "$live/handlers/router.ts": $$$$4, + "$live/handlers/routesSelection.ts": $$$$5, + "$live/handlers/workflowRunner.ts": $$$$6, + }, + "pages": { + "$live/pages/LivePage.tsx": $$$$$0, }, "sections": { "$live/sections/EmptySection.tsx": $$$$$$0, "$live/sections/PageInclude.tsx": $$$$$$1, "$live/sections/Slot.tsx": $$$$$$2, }, + "matchers": { + "$live/matchers/MatchAlways.ts": $$$$$$$0, + "$live/matchers/MatchCron.ts": $$$$$$$1, + "$live/matchers/MatchDate.ts": $$$$$$$2, + "$live/matchers/MatchDevice.ts": $$$$$$$3, + "$live/matchers/MatchEnvironment.ts": $$$$$$$4, + "$live/matchers/MatchHost.ts": $$$$$$$5, + "$live/matchers/MatchLocation.ts": $$$$$$$6, + "$live/matchers/MatchMulti.ts": $$$$$$$7, + "$live/matchers/MatchRandom.ts": $$$$$$$8, + "$live/matchers/MatchSite.ts": $$$$$$$9, + "$live/matchers/MatchUserAgent.ts": $$$$$$$10, + }, + "flags": { + "$live/flags/audience.ts": $$$$$$$$0, + "$live/flags/everyone.ts": $$$$$$$$1, + "$live/flags/flag.ts": $$$$$$$$2, + "$live/flags/multivariate.ts": $$$$$$$$3, + }, + "actions": { + "$live/actions/secrets/encrypt.ts": $$$$$$$$$0, + "$live/actions/workflows/cancel.ts": $$$$$$$$$1, + "$live/actions/workflows/signal.ts": $$$$$$$$$2, + "$live/actions/workflows/start.ts": $$$$$$$$$3, + }, "name": "$live", "baseUrl": import.meta.url, }; diff --git a/compat/$live/matchers/MatchAlways.ts b/compat/$live/matchers/MatchAlways.ts new file mode 100644 index 000000000..04d08bf87 --- /dev/null +++ b/compat/$live/matchers/MatchAlways.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/always.ts"; +export { default } from "../../../website/matchers/always.ts"; diff --git a/compat/$live/matchers/MatchCron.ts b/compat/$live/matchers/MatchCron.ts new file mode 100644 index 000000000..8fdb476b1 --- /dev/null +++ b/compat/$live/matchers/MatchCron.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/cron.ts"; +export { default } from "../../../website/matchers/cron.ts"; diff --git a/compat/$live/matchers/MatchDate.ts b/compat/$live/matchers/MatchDate.ts new file mode 100644 index 000000000..91cdd0f3f --- /dev/null +++ b/compat/$live/matchers/MatchDate.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/date.ts"; +export { default } from "../../../website/matchers/date.ts"; diff --git a/compat/$live/matchers/MatchDevice.ts b/compat/$live/matchers/MatchDevice.ts new file mode 100644 index 000000000..384436ffc --- /dev/null +++ b/compat/$live/matchers/MatchDevice.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/device.ts"; +export { default } from "../../../website/matchers/device.ts"; diff --git a/compat/$live/matchers/MatchEnvironment.ts b/compat/$live/matchers/MatchEnvironment.ts new file mode 100644 index 000000000..fc5188513 --- /dev/null +++ b/compat/$live/matchers/MatchEnvironment.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/environment.ts"; +export { default } from "../../../website/matchers/environment.ts"; diff --git a/compat/$live/matchers/MatchHost.ts b/compat/$live/matchers/MatchHost.ts new file mode 100644 index 000000000..2d6bb0662 --- /dev/null +++ b/compat/$live/matchers/MatchHost.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/host.ts"; +export { default } from "../../../website/matchers/host.ts"; diff --git a/compat/$live/matchers/MatchLocation.ts b/compat/$live/matchers/MatchLocation.ts new file mode 100644 index 000000000..5f7eb197b --- /dev/null +++ b/compat/$live/matchers/MatchLocation.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/location.ts"; +export { default } from "../../../website/matchers/location.ts"; diff --git a/compat/$live/matchers/MatchMulti.ts b/compat/$live/matchers/MatchMulti.ts new file mode 100644 index 000000000..640242011 --- /dev/null +++ b/compat/$live/matchers/MatchMulti.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/multi.ts"; +export { default } from "../../../website/matchers/multi.ts"; diff --git a/compat/$live/matchers/MatchRandom.ts b/compat/$live/matchers/MatchRandom.ts new file mode 100644 index 000000000..7f08007d5 --- /dev/null +++ b/compat/$live/matchers/MatchRandom.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/random.ts"; +export { default } from "../../../website/matchers/random.ts"; diff --git a/compat/$live/matchers/MatchSite.ts b/compat/$live/matchers/MatchSite.ts new file mode 100644 index 000000000..5f6e6ae1b --- /dev/null +++ b/compat/$live/matchers/MatchSite.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/site.ts"; +export { default } from "../../../website/matchers/site.ts"; diff --git a/compat/$live/matchers/MatchUserAgent.ts b/compat/$live/matchers/MatchUserAgent.ts new file mode 100644 index 000000000..104f9e90b --- /dev/null +++ b/compat/$live/matchers/MatchUserAgent.ts @@ -0,0 +1,2 @@ +export * from "../../../website/matchers/userAgent.ts"; +export { default } from "../../../website/matchers/userAgent.ts"; diff --git a/compat/$live/mod.ts b/compat/$live/mod.ts index 451eea68e..84e0687ac 100644 --- a/compat/$live/mod.ts +++ b/compat/$live/mod.ts @@ -1,143 +1,22 @@ -// deno-lint-ignore-file no-explicit-any -import type { App } from "deco/mod.ts"; -// this should not be changed to $live because newly created versions might not include live.gen.ts. -import { buildImportMap } from "deco/blocks/utils.tsx"; - -import type { Manifest as WebSiteManifest } from "../../website/manifest.gen.ts"; import webSite, { Props } from "../../website/mod.ts"; -import type { Manifest as WorkflowsManifest } from "../../workflows/manifest.gen.ts"; - -import { AppContext as AC, ImportMap } from "deco/blocks/app.ts"; import workflows from "../../workflows/mod.ts"; -import manifest, { Manifest as _Manifest } from "./manifest.gen.ts"; - +import manifest, { Manifest } from "./manifest.gen.ts"; +import { type App, type AppContext as AC } from "@deco/deco"; export { onBeforeResolveProps } from "../../website/mod.ts"; export type AppContext = AC>; -export type Manifest = _Manifest; - -export type ManifestMappings = Partial< - { - [ - blockType in keyof Omit< - Manifest, - "name" | "baseUrl" | "routes" | "islands" - > - ]: Partial< - { - [ - blockKey in keyof Omit< - Manifest, - "name" | "baseUrl" | "routes" | "islands" - >[blockType] - ]: blockType extends keyof (WebSiteManifest | WorkflowsManifest) - ? keyof (WebSiteManifest[blockType] & WorkflowsManifest[blockType]) - : blockType extends keyof WebSiteManifest - ? keyof (WebSiteManifest[blockType]) - : blockType extends keyof WorkflowsManifest - ? keyof (WorkflowsManifest[blockType]) - : never; - } - >; - } ->; - -const manifestMappings = { - pages: { - "$live/pages/LivePage.tsx": "website/pages/Page.tsx", - }, - loaders: { - "$live/loaders/workflows/events.ts": "workflows/loaders/events.ts", - "$live/loaders/workflows/get.ts": "workflows/loaders/get.ts", - }, - handlers: { - "$live/handlers/fresh.ts": "website/handlers/fresh.ts", - "$live/handlers/proxy.ts": "website/handlers/proxy.ts", - "$live/handlers/routesSelection.ts": "website/handlers/router.ts", - "$live/handlers/redirect.ts": "website/handlers/redirect.ts", - "$live/handlers/workflowRunner.ts": "workflows/handlers/workflowRunner.ts", - }, - matchers: { - "$live/matchers/MatchAlways.ts": "website/matchers/always.ts", - "$live/matchers/MatchCron.ts": "website/matchers/cron.ts", - "$live/matchers/MatchDate.ts": "website/matchers/date.ts", - "$live/matchers/MatchDevice.ts": "website/matchers/device.ts", - "$live/matchers/MatchEnvironment.ts": "website/matchers/environment.ts", - "$live/matchers/MatchHost.ts": "website/matchers/host.ts", - "$live/matchers/MatchLocation.ts": "website/matchers/location.ts", - "$live/matchers/MatchMulti.ts": "website/matchers/multi.ts", - "$live/matchers/MatchRandom.ts": "website/matchers/random.ts", - "$live/matchers/MatchSite.ts": "website/matchers/site.ts", - "$live/matchers/MatchUserAgent.ts": "website/matchers/userAgent.ts", - }, - actions: { - "$live/actions/secrets/encrypt.ts": "website/actions/secrets/encrypt.ts", - "$live/actions/workflows/cancel.ts": "workflows/actions/cancel.ts", - "$live/actions/workflows/signal.ts": "workflows/actions/signal.ts", - "$live/actions/workflows/start.ts": "workflows/actions/start.ts", - }, - flags: { - "$live/flags/audience.ts": "website/flags/audience.ts", - "$live/flags/everyone.ts": "website/flags/everyone.ts", - "$live/flags/flag.ts": "website/flags/flag.ts", - "$live/flags/multivariate.ts": "website/flags/multivariate.ts", - }, -}; - export type { Props }; /** * @title $live */ -export default function App( - state: Props, -): App< - Manifest, - Props, - [ReturnType, ReturnType] -> { +export default function App(state: Props): App, + ReturnType, +]> { const { resolvables: _ignoreResolvables, ...webSiteApp } = webSite(state); const workflowsApp = workflows({}); - const webSiteManifest = webSiteApp.manifest; - const workflowsManifest = workflowsApp.manifest; - const webSiteManifestImportMap = buildImportMap(webSiteManifest); - const workflowsManifestImportMap = buildImportMap(workflowsManifest); - const importMap: ImportMap = { - ...webSiteManifestImportMap, - ...workflowsManifestImportMap, - imports: { - ...webSiteManifestImportMap.imports, - ...workflowsManifestImportMap.imports, - }, - }; - const _manifest = { ...manifest }; - - for (const [_blockKey, blockMappings] of Object.entries(manifestMappings)) { - const blockKey = _blockKey as keyof _Manifest; - _manifest[blockKey] = { ...(manifest as any)[blockKey] ?? {} }; - for (const [from, to] of Object.entries(blockMappings)) { - if (to.startsWith("website")) { - // @ts-ignore: blockkeys and from/to always exists for those types - _manifest[blockKey][from] = webSiteManifest[blockKey][to]; - importMap.imports[from] = webSiteManifestImportMap.imports[to]; - } else if (to.startsWith("workflows")) { - // @ts-ignore: blockkeys and from/to always exists for those types - _manifest[blockKey][from] = workflowsManifest[blockKey][to]; - importMap.imports[from] = workflowsManifestImportMap.imports[to]; - } - } - } - - const liveImportMap = buildImportMap(manifest); return { state, - importMap: { - ...liveImportMap, - ...importMap, - imports: { - ...liveImportMap.imports, - ...importMap.imports, - }, - }, - manifest: _manifest as Manifest, + manifest, dependencies: [webSiteApp, workflowsApp], }; } diff --git a/compat/$live/pages/LivePage.tsx b/compat/$live/pages/LivePage.tsx new file mode 100644 index 000000000..7a55f3057 --- /dev/null +++ b/compat/$live/pages/LivePage.tsx @@ -0,0 +1,21 @@ +import { + default as livepageDefault, + type DefaultPathProps, + loader, + pageIdFromMetadata, + Preview, + type Props, + renderSection, + type Sections, +} from "../../../website/pages/Page.tsx"; + +export default livepageDefault; +export { + DefaultPathProps, + loader, + pageIdFromMetadata, + Preview, + Props, + renderSection, + Sections, +}; diff --git a/compat/$live/sections/PageInclude.tsx b/compat/$live/sections/PageInclude.tsx index b1410bea1..52ab2a6a0 100644 --- a/compat/$live/sections/PageInclude.tsx +++ b/compat/$live/sections/PageInclude.tsx @@ -1,27 +1,21 @@ -/** TODO: Deprecate this file */ -import { Page } from "deco/blocks/page.tsx"; -import { notUndefined } from "deco/engine/core/utils.ts"; - import { Props as LivePageProps, renderSection, } from "../../../website/pages/Page.tsx"; - +import { type Page } from "@deco/deco/blocks"; +import { notUndefined } from "@deco/deco/utils"; export interface Props { page: Page; } - export const isLivePageProps = ( p: Page["props"] | LivePageProps, ): p is LivePageProps => { return (p as LivePageProps)?.sections !== undefined; }; - export default function PageInclude({ page }: Props) { if (!isLivePageProps(page?.props)) { return null; } - return ( <>{(page?.props?.sections ?? []).filter(notUndefined).map(renderSection)} ); diff --git a/compat/$live/sections/Slot.tsx b/compat/$live/sections/Slot.tsx index 227ecbacf..2f04cc1e6 100644 --- a/compat/$live/sections/Slot.tsx +++ b/compat/$live/sections/Slot.tsx @@ -1,5 +1,4 @@ -import { isSection, Section } from "deco/blocks/section.ts"; - +import { isSection, type Section } from "@deco/deco/blocks"; export type WellKnownSlots = | "content" | "footer" @@ -7,7 +6,6 @@ export type WellKnownSlots = | "analytics" | "design-system" | "SEO"; - export interface Props { /** * @description Enforces the slot to be fulfilled. @@ -19,29 +17,22 @@ export interface Props { */ name?: string | WellKnownSlots; } - export const CONTENT_SLOT_NAME = "content"; export const isContentSlot = (s: Section): boolean => { return isSection(s, "$live/sections/Slot.tsx") && s?.props?.name === CONTENT_SLOT_NAME; }; - export default function Slot(p: Props) { if (p?.required) { return ShowSlot(p); } return null; } - function ShowSlot(p: Props) { return ( -