diff --git a/apps/csk/.env.example b/apps/csk/.env.example index 023500601..32fd1cb81 100644 --- a/apps/csk/.env.example +++ b/apps/csk/.env.example @@ -9,4 +9,10 @@ UNIFORM_PREVIEW_SECRET=hello-world # defaults to localhost:3000 if not set. #When set, should resolve to the base url of the app. # This is used to generate the sitemap and robots.txt -BASE_URL= \ No newline at end of file +BASE_URL= + +# OpenAI API key +OPENAI_API_KEY= + +# Postgres data base url +DATABASE_URL= \ No newline at end of file diff --git a/apps/csk/README.md b/apps/csk/README.md index 33667600e..d51bc7ee5 100644 --- a/apps/csk/README.md +++ b/apps/csk/README.md @@ -62,6 +62,58 @@ This integration brings new parameter types for design and layout control via Un ![Your project](https://res.cloudinary.com/uniform-demos/image/upload/csk-v-next/doc/project_page.png) 1. Navigate to the `Integrations` tab, find the `Design Extensions` integration and install it. +## πŸš€ RAG integration + +Retrieval-augmented generation (RAG) keeps your assistant grounded in your content: it fetches the most relevant documents from a Postgres-backed vector store and passes them to the LLM, so answers stay on-topic and up-to-date. + +### Step 4. Create a Postgres database + +You will need a **Postgres** database to complete this tutorial. If you don’t have Postgres locally you can either: + +- Spin up a **free Postgres** instance on [Vercel Postgres](https://vercel.com/postgres). +- Follow [this guide](https://www.postgresqltutorial.com/install-postgresql/) to install Postgres locally. + +Once your database is ready, copy its connection string – you’ll need it in the next step. + +### Step 5. Migrate the database + +1. Open `.env` and paste your **database connection string** after the `DATABASE_URL=` key. +1. Run the migration: + + ```bash + npm run db:migrate + ``` + + The script will: + + - Enable the `pgvector` extension. + - Create tables for the `resources` and `embeddings` schema defined in `src/module/chat/rag/schema` with necessary columns. + +1. Fill the data to database: + ```bash + npm run db:load + ``` + +### Step 6. Add your OpenAI API key + +Some starter features – like AI-Assistant and RAG integration – require an **OpenAI API key**. + +1. Generate a key in your OpenAI dashboard: . +1. Add it to `.env`: + ```bash + OPENAI_API_KEY= + ``` + +### Step 7. Add Webhook (Additional) + +You can configure a webhook so your external database (e.g. the Postgres layer you added above) stays automatically up‑to‑date whenever content is published or deleted in Uniform. + +1. Open Settings β†’ Webhooks +1. Set the Endpoint URL to: `https://your_domain/api/webhook/entry-event` +1. Enable the following events: + - entry.published + - entry.deleted + ## How to sync content The following scripts are created to facilitate sync of content between the `./content` folder and your project. diff --git a/apps/csk/content/asset/0aafe2e9-468c-43e1-81f3-fda6c02388e7.yaml b/apps/csk/content/asset/0aafe2e9-468c-43e1-81f3-fda6c02388e7.yaml index 5e68fb9a0..4d7d4bbbc 100644 --- a/apps/csk/content/asset/0aafe2e9-468c-43e1-81f3-fda6c02388e7.yaml +++ b/apps/csk/content/asset/0aafe2e9-468c-43e1-81f3-fda6c02388e7.yaml @@ -1,5 +1,5 @@ asset: - type: image + type: other _id: 0aafe2e9-468c-43e1-81f3-fda6c02388e7 _name: '' fields: @@ -26,5 +26,5 @@ asset: value: 1059 type: number custom: {} -created: '2025-05-20T11:52:45.655676+00:00' -modified: '2025-05-20T11:52:45.655676+00:00' +created: '2025-05-26T08:40:43.349414+00:00' +modified: '2025-05-26T08:40:43.349414+00:00' diff --git a/apps/csk/content/asset/1697b6ae-aa6f-4bd6-b844-c8fb3c86216c.yaml b/apps/csk/content/asset/1697b6ae-aa6f-4bd6-b844-c8fb3c86216c.yaml index d9da2664a..34272768d 100644 --- a/apps/csk/content/asset/1697b6ae-aa6f-4bd6-b844-c8fb3c86216c.yaml +++ b/apps/csk/content/asset/1697b6ae-aa6f-4bd6-b844-c8fb3c86216c.yaml @@ -1,5 +1,5 @@ asset: - type: image + type: other _id: 1697b6ae-aa6f-4bd6-b844-c8fb3c86216c _name: '' fields: @@ -26,5 +26,5 @@ asset: value: 24 type: number custom: {} -created: '2025-05-20T11:52:45.084371+00:00' -modified: '2025-05-20T11:52:45.084371+00:00' +created: '2025-05-26T08:40:43.907922+00:00' +modified: '2025-05-26T08:40:43.907922+00:00' diff --git a/apps/csk/content/asset/4469d093-0095-43fe-a6f1-9ccd66da351c.yaml b/apps/csk/content/asset/4469d093-0095-43fe-a6f1-9ccd66da351c.yaml index 08927814a..9c6b328da 100644 --- a/apps/csk/content/asset/4469d093-0095-43fe-a6f1-9ccd66da351c.yaml +++ b/apps/csk/content/asset/4469d093-0095-43fe-a6f1-9ccd66da351c.yaml @@ -1,5 +1,5 @@ asset: - type: other + type: image _id: 4469d093-0095-43fe-a6f1-9ccd66da351c _name: '' fields: @@ -26,5 +26,5 @@ asset: value: 24 type: number custom: {} -created: '2025-06-05T12:14:52.403835+00:00' -modified: '2025-06-05T13:26:31.639979+00:00' +created: '2025-05-26T08:40:45.988794+00:00' +modified: '2025-06-05T14:41:54.108127+00:00' diff --git a/apps/csk/content/asset/511356c8-8058-4312-9d7b-6d73218b67c1.yaml b/apps/csk/content/asset/511356c8-8058-4312-9d7b-6d73218b67c1.yaml index 1b1165dc8..b2e96afb2 100644 --- a/apps/csk/content/asset/511356c8-8058-4312-9d7b-6d73218b67c1.yaml +++ b/apps/csk/content/asset/511356c8-8058-4312-9d7b-6d73218b67c1.yaml @@ -1,5 +1,5 @@ asset: - type: image + type: other _id: 511356c8-8058-4312-9d7b-6d73218b67c1 _name: '' fields: @@ -26,5 +26,5 @@ asset: value: 29 type: number custom: {} -created: '2025-01-29T11:08:52.824037+00:00' -modified: '2025-06-05T13:26:33.548976+00:00' +created: '2025-04-24T10:41:24.913841+00:00' +modified: '2025-06-05T14:41:54.567064+00:00' diff --git a/apps/csk/content/asset/7770ab14-93dd-46bb-9ef6-7dab66fff401.yaml b/apps/csk/content/asset/7770ab14-93dd-46bb-9ef6-7dab66fff401.yaml index e4d96ef9e..64de08290 100644 --- a/apps/csk/content/asset/7770ab14-93dd-46bb-9ef6-7dab66fff401.yaml +++ b/apps/csk/content/asset/7770ab14-93dd-46bb-9ef6-7dab66fff401.yaml @@ -1,5 +1,5 @@ asset: - type: other + type: image _id: 7770ab14-93dd-46bb-9ef6-7dab66fff401 _name: '' fields: @@ -26,5 +26,5 @@ asset: value: 24 type: number custom: {} -created: '2025-06-05T12:14:53.976389+00:00' -modified: '2025-06-05T13:26:31.413125+00:00' +created: '2025-05-26T08:40:46.276069+00:00' +modified: '2025-06-05T14:41:53.852796+00:00' diff --git a/apps/csk/content/asset/c13180f0-750f-4fc7-837e-46ade3070952.yaml b/apps/csk/content/asset/c13180f0-750f-4fc7-837e-46ade3070952.yaml index 6c21405fa..7d25f5285 100644 --- a/apps/csk/content/asset/c13180f0-750f-4fc7-837e-46ade3070952.yaml +++ b/apps/csk/content/asset/c13180f0-750f-4fc7-837e-46ade3070952.yaml @@ -1,5 +1,5 @@ asset: - type: other + type: image _id: c13180f0-750f-4fc7-837e-46ade3070952 _name: '' fields: @@ -26,5 +26,5 @@ asset: value: 24 type: number custom: {} -created: '2025-06-05T12:14:53.862+00:00' -modified: '2025-06-05T13:26:31.696036+00:00' +created: '2025-05-26T08:40:47.459882+00:00' +modified: '2025-06-05T14:41:54.39118+00:00' diff --git a/apps/csk/content/asset/e69c04dc-5c4f-4951-8102-08d9a8df8913.yaml b/apps/csk/content/asset/e69c04dc-5c4f-4951-8102-08d9a8df8913.yaml index 849a18510..da20cc971 100644 --- a/apps/csk/content/asset/e69c04dc-5c4f-4951-8102-08d9a8df8913.yaml +++ b/apps/csk/content/asset/e69c04dc-5c4f-4951-8102-08d9a8df8913.yaml @@ -1,5 +1,5 @@ asset: - type: image + type: other _id: e69c04dc-5c4f-4951-8102-08d9a8df8913 _name: '' fields: @@ -26,5 +26,5 @@ asset: value: 34 type: number custom: {} -created: '2025-02-05T09:08:07.205461+00:00' -modified: '2025-06-05T13:26:31.921082+00:00' +created: '2025-04-24T10:42:09.850151+00:00' +modified: '2025-06-05T14:41:56.383215+00:00' diff --git a/apps/csk/content/component/aiAssistant.yaml b/apps/csk/content/component/aiAssistant.yaml new file mode 100644 index 000000000..029b82d10 --- /dev/null +++ b/apps/csk/content/component/aiAssistant.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema= +$schema: https://uniform.app/schemas/json-schema/component-definition/v1.json +id: aiAssistant +name: AI Assistant +icon: ghost-character +parameters: + - id: starterPrompts + name: Starter Prompts + type: $block + typeConfig: + allowedTypes: + - starterPrompt +categoryId: 70d03350-bbf9-457d-abc0-cf7d7aa2a3a4 +previewImageUrl: >- + https://res.cloudinary.com/uniform-demos/image/upload/csk-v-next/coffee-shop-template/preview-images/ai-assistant.jpg +useTeamPermissions: true +slots: [] +canBeComposition: false +created: '2025-04-24T11:11:12.057056+00:00' +updated: '2025-04-24T11:11:12.057056+00:00' diff --git a/apps/csk/content/component/aiConfiguration.yaml b/apps/csk/content/component/aiConfiguration.yaml new file mode 100644 index 000000000..2a63ae02b --- /dev/null +++ b/apps/csk/content/component/aiConfiguration.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema= +$schema: https://uniform.app/schemas/json-schema/component-definition/v1.json +id: aiConfiguration +name: AI Configuration +icon: asterisk +parameters: [] +categoryId: c7a1d1d9-cc3f-431c-b55e-6c4af1285108 +previewImageUrl: >- + https://res.cloudinary.com/uniform-demos/image/upload/csk-v-next/coffee-shop-template/preview-images/ai-configuration.jpg +useTeamPermissions: true +slots: + - id: content + name: Content + allowedComponents: [] + allowAllComponents: true + inheritAllowedComponents: false + patternsInAllowedComponents: false +canBeComposition: true +created: '2025-04-24T11:28:17.821117+00:00' +updated: '2025-04-24T11:28:55.575968+00:00' diff --git a/apps/csk/content/component/assistantScrollSection.yaml b/apps/csk/content/component/assistantScrollSection.yaml new file mode 100644 index 000000000..d7ef61d19 --- /dev/null +++ b/apps/csk/content/component/assistantScrollSection.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema= +$schema: https://uniform.app/schemas/json-schema/component-definition/v1.json +id: assistantScrollSection +name: Assistant Scroll Section +icon: rename +parameters: [] +categoryId: 096fd5ed-5e2a-4bfa-834b-fb805d1d1ce9 +previewImageUrl: >- + https://res.cloudinary.com/uniform-demos/image/upload/csk-v-next/coffee-shop-template/preview-images/assistant-scroll-section.jpg +useTeamPermissions: true +slots: + - id: content + name: Content + allowedComponents: [] + allowAllComponents: true + inheritAllowedComponents: false + patternsInAllowedComponents: false +canBeComposition: false +created: '2025-04-28T12:03:20.971965+00:00' +updated: '2025-04-28T12:03:52.061783+00:00' diff --git a/apps/csk/content/component/header.yaml b/apps/csk/content/component/header.yaml index 694d94545..af492dcd4 100644 --- a/apps/csk/content/component/header.yaml +++ b/apps/csk/content/component/header.yaml @@ -75,7 +75,7 @@ slots: - navigationLink - shoppingCartIcon - favoritesIcon - - aiShoppingAssistantButton + - aiAssistant allowAllComponents: false inheritAllowedComponents: false patternsInAllowedComponents: false diff --git a/apps/csk/content/componentPattern/021762f3-8a06-41b4-a4fb-08a837c74415.yaml b/apps/csk/content/componentPattern/021762f3-8a06-41b4-a4fb-08a837c74415.yaml index 8f3cd6d0e..68fd706b4 100644 --- a/apps/csk/content/componentPattern/021762f3-8a06-41b4-a4fb-08a837c74415.yaml +++ b/apps/csk/content/componentPattern/021762f3-8a06-41b4-a4fb-08a837c74415.yaml @@ -304,6 +304,50 @@ composition: value: contrast-[.1] _overridability: hideLockedParameters: true + - _id: 741a47a4-4c3c-4242-bf1a-4c8455fdef37 + type: aiAssistant + parameters: + starterPrompts: + type: $block + value: + - _id: 91405084-7665-4771-b7e0-6172a37484c0 + type: starterPrompt + fields: + value: + type: text + value: Could you show me what’s in my shopping cart right now? + - _id: 13d4f026-9713-4150-874e-b8b0513da1f2 + type: starterPrompt + fields: + value: + type: text + value: >- + Based on what I like, do you have any recommendations for + me? + - _id: 4a765cc7-2cc9-445a-aa7c-eb051b223a51 + type: starterPrompt + fields: + value: + type: text + value: >- + Hey, could you take a look at my shopping cart and suggest + a few products that might suit me? + - _id: 010c7d9c-e45e-4f21-9059-a0261002f95b + type: starterPrompt + fields: + value: + type: text + value: Trying to improve bean grinding technique + - _id: f7108251-4567-4387-8306-8c592fdf27ea + type: starterPrompt + fields: + value: + type: text + value: >- + I am looking for an espresso machine that gives me + precision grinding + _overridability: + hideLockedParameters: true headerCenterContent: - _id: 5832d439-1459-4a01-b422-fd1e66a71cbb type: navigationLink diff --git a/apps/csk/content/componentPattern/19ea7368-29fc-4e61-b730-b78ee47e74de.yaml b/apps/csk/content/componentPattern/19ea7368-29fc-4e61-b730-b78ee47e74de.yaml new file mode 100644 index 000000000..6e713bba4 --- /dev/null +++ b/apps/csk/content/componentPattern/19ea7368-29fc-4e61-b730-b78ee47e74de.yaml @@ -0,0 +1,70 @@ +composition: + _name: Product Recommendation + _id: 19ea7368-29fc-4e61-b730-b78ee47e74de + type: link + parameters: + link: + type: link + value: + path: /${locale}/products/${#jptr:/Single Product Content/entry/_slug} + type: projectMapNode + nodeId: 702b0e0b-3858-4b5d-99e4-bdb98097ca0f + projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 + dynamicInputValues: + locale: ${locale} + product-slug: ${#jptr:/Single Product Content/entry/_slug} + displayName: + type: text + locales: + en: ${#jptr:/Single Product Content/entry/_name} + slots: + linkContent: + - _id: baf65f29-3f6b-460f-8206-f46f74460edd + type: card + _pattern: 665b55aa-f241-413e-8778-cff9a2f1f8aa + _dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: efc7afec-03c4-4fd4-b478-a32708f5852e + isPatternParameter: true + ignorePatternParameterDefault: true + _overrides: + baf65f29-3f6b-460f-8206-f46f74460edd: + parameters: + displayName: + type: text + locales: + en: ${#jptr:/Single Product Content/entry/_name} + baf65f29-3f6b-460f-8206-f46f74460edd|0952417a-061c-4634-91df-1341269e4054: + parameters: + image: + type: asset + value: ${#jptr:/Single Product Content/entry/fields/primaryImage/value} + baf65f29-3f6b-460f-8206-f46f74460edd|8bad3ddd-add0-43d3-a314-5836bf0c7a93: + parameters: + text: + type: text + value: >- + ${#jptr:/Single Product + Content/entry/fields/variants/value/0/fields/currency/value} + ${#jptr:/Single Product + Content/entry/fields/variants/value/0/fields/price/value} + baf65f29-3f6b-460f-8206-f46f74460edd|988f7805-9e0e-4b30-acaf-f97d7dd5099f: + parameters: + text: + type: text + value: >- + Vintage Drip Coffee Maker${#jptr:/Single Product + Content/entry/fields/variants/value/0/fields/title/value} + _overridability: + hideLockedParameters: true + _locales: + - en +created: '2025-03-18T20:14:39.454454+00:00' +modified: '2025-03-18T20:23:09.769177+00:00' +pattern: true +previewImageUrl: >- + https://res.cloudinary.com/uniform-demos/image/upload/v1731065901/csk-v-next/jd-template/product-card.jpg +categoryId: 096fd5ed-5e2a-4bfa-834b-fb805d1d1ce9 diff --git a/apps/csk/content/componentPattern/6ac15a92-bef3-4cf1-9bff-a2bcb666b73d.yaml b/apps/csk/content/componentPattern/6ac15a92-bef3-4cf1-9bff-a2bcb666b73d.yaml new file mode 100644 index 000000000..e630e24fa --- /dev/null +++ b/apps/csk/content/componentPattern/6ac15a92-bef3-4cf1-9bff-a2bcb666b73d.yaml @@ -0,0 +1,123 @@ +composition: + _name: Product Recommendation + _id: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + type: flex + parameters: + gap: + type: dex-slider-control-parameter + value: + mobile: '4' + tablet: '4' + desktop: '4' + spacing: + type: dex-space-control-parameter + value: + mobile: {} + tablet: {} + desktop: {} + direction: + type: dex-segmented-control-parameter + value: + mobile: col + tablet: col + desktop: col + alignItems: + type: dex-segmented-control-parameter + value: {} + displayName: + type: text + locales: + en: ${#jptr:/Single Product Content/entry/_name} + fluidContent: + type: checkbox + value: true + justifyContent: + type: dex-segmented-control-parameter + value: + mobile: between + tablet: between + desktop: between + slots: + flexItem: + - _id: 2e6f1f7c-6b5b-4736-9fb7-3fd2b1d129b1 + type: link + _pattern: 1b9cb318-a532-4c1a-af19-c0ee1a66855f + - _id: 0c35961b-b836-40e4-a0f5-e23594ea9462 + type: addToCardButton + _pattern: 20161830-c75e-4695-8622-41032f5be75d + _dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: efc7afec-03c4-4fd4-b478-a32708f5852e + isPatternParameter: true + ignorePatternParameterDefault: true + _overrides: + 0c35961b-b836-40e4-a0f5-e23594ea9462: + parameters: + productSlug: + type: text + locales: + en: ${#jptr:/Single Product Content/entry/_slug} + openMiniCart: + type: checkbox + value: null + 2e6f1f7c-6b5b-4736-9fb7-3fd2b1d129b1: + parameters: + link: + type: link + value: + path: /${locale}/products/${#jptr:/Single Product Content/entry/_slug} + type: projectMapNode + nodeId: 702b0e0b-3858-4b5d-99e4-bdb98097ca0f + projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 + dynamicInputValues: + locale: ${locale} + product-slug: ${#jptr:/Single Product Content/entry/_slug} + displayName: + type: text + locales: + en: ${#jptr:/Single Product Content/entry/_name} + 2e6f1f7c-6b5b-4736-9fb7-3fd2b1d129b1|1af937fb-40ae-4938-b1a8-110b8ff73fc4: + parameters: + displayName: + type: text + value: ${#jptr:/Single Product Content/entry/_name} + 2e6f1f7c-6b5b-4736-9fb7-3fd2b1d129b1|1af937fb-40ae-4938-b1a8-110b8ff73fc4|0952417a-061c-4634-91df-1341269e4054: + parameters: + image: + type: asset + value: ${#jptr:/Single Product Content/entry/fields/primaryImage/value} + 2e6f1f7c-6b5b-4736-9fb7-3fd2b1d129b1|1af937fb-40ae-4938-b1a8-110b8ff73fc4|41f41812-74a8-48f1-9e2a-920dfff8ea04: + parameters: + productSlug: + type: text + locales: + en: ${#jptr:/Single Product Content/entry/_slug} + 2e6f1f7c-6b5b-4736-9fb7-3fd2b1d129b1|1af937fb-40ae-4938-b1a8-110b8ff73fc4|8bad3ddd-add0-43d3-a314-5836bf0c7a93: + parameters: + text: + type: text + value: >- + ${#jptr:/Single Product + Content/entry/fields/variants/value/0/fields/price/value} + ${#jptr:/Single Product + Content/entry/fields/variants/value/0/fields/currency/value} + 2e6f1f7c-6b5b-4736-9fb7-3fd2b1d129b1|1af937fb-40ae-4938-b1a8-110b8ff73fc4|988f7805-9e0e-4b30-acaf-f97d7dd5099f: + parameters: + text: + type: text + value: >- + ${#jptr:/Single Product + Content/entry/fields/variants/value/0/fields/title/value} + _overridability: + hideLockedParameters: true + _locales: + - en +created: '2025-04-28T06:50:46.95151+00:00' +modified: '2025-04-29T15:18:27.0705+00:00' +pattern: true +previewImageUrl: >- + https://res.cloudinary.com/uniform-demos/image/upload/v1731065901/csk-v-next/jd-template/product-card.jpg +categoryId: 096fd5ed-5e2a-4bfa-834b-fb805d1d1ce9 diff --git a/apps/csk/content/componentPattern/9e2c59d1-8dd3-4a13-b5b8-70639cffd7bc.yaml b/apps/csk/content/componentPattern/9e2c59d1-8dd3-4a13-b5b8-70639cffd7bc.yaml index 5cae6aa31..7f55dc3c3 100644 --- a/apps/csk/content/componentPattern/9e2c59d1-8dd3-4a13-b5b8-70639cffd7bc.yaml +++ b/apps/csk/content/componentPattern/9e2c59d1-8dd3-4a13-b5b8-70639cffd7bc.yaml @@ -313,6 +313,50 @@ composition: value: contrast-[.1] _overridability: hideLockedParameters: true + - _id: 803056e4-b10a-4143-b0ac-403aaffcaaef + type: aiAssistant + parameters: + starterPrompts: + type: $block + value: + - _id: d61969dc-dda3-43fb-9ae8-b848006a92f5 + type: starterPrompt + fields: + value: + type: text + value: Could you show me what’s in my shopping cart right now? + - _id: 2c4192c8-1528-4c63-b2dd-7b431857a7e5 + type: starterPrompt + fields: + value: + type: text + value: >- + Based on what I like, do you have any recommendations for + me? + - _id: f267ccf4-3c00-4ba6-9edf-888813a59ba6 + type: starterPrompt + fields: + value: + type: text + value: >- + Hey, could you take a look at my shopping cart and suggest + a few products that might suit me? + - _id: 557f3444-19e2-4bf9-8f44-4b7b568f81b3 + type: starterPrompt + fields: + value: + type: text + value: Trying to improve bean grinding technique + - _id: 37d73a3f-2965-41d5-8acf-9c256e49a464 + type: starterPrompt + fields: + value: + type: text + value: >- + I am looking for an espresso machine that gives me + precision grinding + _overridability: + hideLockedParameters: true headerCenterContent: - _id: 5832d439-1459-4a01-b422-fd1e66a71cbb type: navigationLink @@ -1332,8 +1376,8 @@ composition: hideLockedParameters: true _locales: - en -created: '2025-06-05T12:15:14.683835+00:00' -modified: '2025-06-05T14:37:09.53008+00:00' +created: '2025-05-26T08:41:07.352452+00:00' +modified: '2025-05-26T08:44:18.058021+00:00' pattern: true previewImageUrl: >- https://res.cloudinary.com/uniform-demos/image/upload/csk-v-next/coffee-shop-template/preview-images/featured_header.png diff --git a/apps/csk/content/composition/5288b954-d654-40aa-b439-b01eab5982e9.yaml b/apps/csk/content/composition/5288b954-d654-40aa-b439-b01eab5982e9.yaml new file mode 100644 index 000000000..235955cb7 --- /dev/null +++ b/apps/csk/content/composition/5288b954-d654-40aa-b439-b01eab5982e9.yaml @@ -0,0 +1,148 @@ +composition: + _name: πŸ›’ Cart Recommendations + _id: 5288b954-d654-40aa-b439-b01eab5982e9 + _slug: cart-recommendations + type: aiConfiguration + slots: + content: + - _id: 9798b8c1-c28a-4e94-8d18-198eb7fe3092 + type: assistantScrollSection + slots: + content: + - _id: 566df6bb-c531-4370-979d-c994b066dd12 + type: $loop + slots: + body: + - _id: fa11c470-7a4b-4c64-8f7a-cf00bcd4ea8a + type: flex + slots: + flexItem: + - _id: 727003b8-96f1-465d-b540-12773cf48fd1 + type: link + _pattern: 1b9cb318-a532-4c1a-af19-c0ee1a66855f + - _id: 3832c4ef-355a-42a8-a62a-66e959c4bb31 + type: addToCardButton + _pattern: 20161830-c75e-4695-8622-41032f5be75d + parameters: + gap: + type: dex-slider-control-parameter + value: + mobile: '2' + tablet: '2' + desktop: '2' + spacing: + type: dex-space-control-parameter + value: + mobile: {} + tablet: {} + desktop: {} + direction: + type: dex-segmented-control-parameter + value: + mobile: col + tablet: col + desktop: col + alignItems: + type: dex-segmented-control-parameter + value: + mobile: '' + tablet: '' + desktop: '' + displayName: + type: text + locales: + en: >- + ${#jptr:/Get Entries by Slugs Content entries Loop + Item/entry/_name} + justifyContent: + type: dex-segmented-control-parameter + value: + mobile: between + tablet: between + desktop: between + _dataResources: + Get Entries by Slugs Content entries Loop Item: + type: sys-reserved-loop + variables: + connectedData: '#jptr:/Get Entries by Slugs Content/entries:fa=c' + _dataResources: + Get Entries by Slugs Content: + type: getEntriesBySlugs + variables: + Slugs: ${slugs} + Locale: ${locale} + _overrides: + 3832c4ef-355a-42a8-a62a-66e959c4bb31: + parameters: + productSlug: + type: text + locales: + en: >- + ${#jptr:/Get Entries by Slugs Content entries Loop + Item/entry/_slug} + openMiniCart: + type: checkbox + value: null + 727003b8-96f1-465d-b540-12773cf48fd1: + parameters: + link: + type: link + value: + path: >- + /${locale}/products/${#jptr:/Get Entries by Slugs Content entries + Loop Item/entry/_slug} + type: projectMapNode + nodeId: 702b0e0b-3858-4b5d-99e4-bdb98097ca0f + projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 + dynamicInputValues: + locale: ${locale} + product-slug: >- + ${#jptr:/Get Entries by Slugs Content entries Loop + Item/entry/_slug} + displayName: + type: text + locales: + en: >- + ${#jptr:/Get Entries by Slugs Content entries Loop + Item/entry/_name} + 727003b8-96f1-465d-b540-12773cf48fd1|1af937fb-40ae-4938-b1a8-110b8ff73fc4: + parameters: + displayName: + type: text + value: ${#jptr:/Get Entries by Slugs Content entries Loop Item/entry/_name} + 727003b8-96f1-465d-b540-12773cf48fd1|1af937fb-40ae-4938-b1a8-110b8ff73fc4|0952417a-061c-4634-91df-1341269e4054: + parameters: + image: + type: asset + value: >- + ${#jptr:/Get Entries by Slugs Content entries Loop + Item/entry/fields/primaryImage/value} + 727003b8-96f1-465d-b540-12773cf48fd1|1af937fb-40ae-4938-b1a8-110b8ff73fc4|41f41812-74a8-48f1-9e2a-920dfff8ea04: + parameters: + productSlug: + type: text + locales: + en: >- + ${#jptr:/Get Entries by Slugs Content entries Loop + Item/entry/_slug} + 727003b8-96f1-465d-b540-12773cf48fd1|1af937fb-40ae-4938-b1a8-110b8ff73fc4|8bad3ddd-add0-43d3-a314-5836bf0c7a93: + parameters: + text: + type: text + value: >- + ${#jptr:/Get Entries by Slugs Content entries Loop + Item/entry/fields/variants/value/0/fields/price/value} ${#jptr:/Get + Entries by Slugs Content entries Loop + Item/entry/fields/variants/value/0/fields/currency/value} + 727003b8-96f1-465d-b540-12773cf48fd1|1af937fb-40ae-4938-b1a8-110b8ff73fc4|988f7805-9e0e-4b30-acaf-f97d7dd5099f: + parameters: + text: + type: text + value: >- + ${#jptr:/Get Entries by Slugs Content entries Loop + Item/entry/fields/variants/value/0/fields/title/value} + _locales: + - en +created: '2025-04-24T11:31:28.673404+00:00' +modified: '2025-04-29T15:21:29.001873+00:00' +pattern: false diff --git a/apps/csk/content/composition/5aa09de0-3fac-4ff5-bbbc-52ca41cb4880.yaml b/apps/csk/content/composition/5aa09de0-3fac-4ff5-bbbc-52ca41cb4880.yaml new file mode 100644 index 000000000..022301de2 --- /dev/null +++ b/apps/csk/content/composition/5aa09de0-3fac-4ff5-bbbc-52ca41cb4880.yaml @@ -0,0 +1,312 @@ +composition: + _name: ℹ️ Context Recommendations + _id: 5aa09de0-3fac-4ff5-bbbc-52ca41cb4880 + _slug: contex-recommendations + type: aiConfiguration + slots: + content: + - _id: 1c4f78da-5c3d-4a35-abab-8b0a72ea8bb3 + type: flexibleHero + slots: + flexibleHeroContent: + - _id: 86865ae7-01a5-4645-a35d-86a0023ab08b + type: text + parameters: + tag: + type: dex-segmented-control-parameter + value: h2 + size: + type: dex-segmented-control-parameter + value: + mobile: xl + tablet: xl + desktop: xl + text: + type: text + locales: + en: πŸ‘€ Look what we found for you + color: + type: dex-color-palette-parameter + value: text-primary + weight: + type: dex-segmented-control-parameter + value: medium + - _id: cf208c2e-9c6f-4236-9170-46d88f96b41f + type: text + parameters: + tag: + type: dex-segmented-control-parameter + value: p + size: + type: dex-segmented-control-parameter + value: + mobile: base + tablet: base + desktop: base + text: + type: text + locales: + en: >- + A few handy things related to your question β€” might come + in useful! + weight: + type: dex-segmented-control-parameter + value: normal + parameters: + $viz: + type: $viz + value: + criteria: + clauses: + - op: '!empty' + rule: $dt + value: '' + source: ${#jptr:/Get Entries by Slugs Content/entries/0/entry/_id} + spacing: + type: dex-space-control-parameter + value: + mobile: + marginTop: container-small + tablet: + marginTop: container-small + desktop: + marginTop: container-small + displayName: + type: text + locales: + en: Hero + fluidContent: + type: checkbox + value: true + contentAlignment: + type: dex-segmented-control-parameter + value: left + _overridability: + hideLockedParameters: true + - _id: 42eb443e-d704-471e-8d84-e137cbe194fc + type: assistantScrollSection + slots: + content: + - _id: a869f34a-de26-4367-8dcb-a81d8fd9cb55 + type: $loop + slots: + body: + - _id: b62e09d6-5d79-42b4-89e2-7d72f87b61cf + type: container + slots: + containerContent: + - _id: 7045e9c2-c2d5-49bf-a9b3-859e583de688 + type: link + slots: + linkContent: + - _id: a1977632-b1d2-4487-8aeb-1fc5c8f40f85 + type: image + parameters: + image: + type: asset + value: + - _id: a87c1f69-80b0-4064-8720-08e464d511b2 + type: image + fields: + url: + type: text + value: >- + ${#jptr:/Get Entries by Slugs Content + entries Loop Item/entry/_thumbnail} + _source: custom-url + width: + type: number + value: '300' + height: + type: number + value: '200' + objectFit: + type: dex-segmented-control-parameter + value: cover + - _id: ffbabfc7-4d3f-4f2c-9b95-b55320a6ce88 + type: text + parameters: + tag: + type: dex-segmented-control-parameter + value: h2 + font: + type: dex-token-selector-parameter + value: primary + size: + type: dex-segmented-control-parameter + value: + mobile: xl + tablet: xl + desktop: xl + text: + type: text + locales: + en: >- + ${#jptr:/Get Entries by Slugs Content + entries Loop Item/entry/_name} + color: + type: dex-color-palette-parameter + value: text-primary + weight: + type: dex-segmented-control-parameter + value: bold + transform: + type: dex-segmented-control-parameter + value: '' + _overridability: + parameters: + tag: 'yes' + text: 'yes' + hideLockedParameters: true + parameters: + $viz: + type: $viz + value: + criteria: + clauses: + - op: is + rule: $dt + value: product + source: >- + ${#jptr:/Get Entries by Slugs Content + entries Loop Item/entry/type} + link: + type: link + value: + path: >- + /${locale}/products/${#jptr:/Get Entries by + Slugs Content entries Loop Item/entry/_slug} + type: projectMapNode + nodeId: 702b0e0b-3858-4b5d-99e4-bdb98097ca0f + projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 + dynamicInputValues: + locale: ${locale} + product-slug: >- + ${#jptr:/Get Entries by Slugs Content + entries Loop Item/entry/_slug} + displayName: + type: text + locales: + en: Link for product + - _id: 8b066cef-3d3f-4b30-b282-e8f0b407c234 + type: link + slots: + linkContent: + - _id: 753d7a35-36d9-449c-9ab2-6f9db1ae4926 + type: image + parameters: + image: + type: asset + value: + - _id: a87c1f69-80b0-4064-8720-08e464d511b2 + type: image + fields: + url: + type: text + value: >- + ${#jptr:/Get Entries by Slugs Content + entries Loop Item/entry/_thumbnail} + _source: custom-url + width: + type: number + value: '300' + height: + type: number + value: '200' + objectFit: + type: dex-segmented-control-parameter + value: cover + - _id: 544ba4dc-80de-4604-93ce-67ad05e024d9 + type: text + parameters: + tag: + type: dex-segmented-control-parameter + value: h2 + font: + type: dex-token-selector-parameter + value: primary + size: + type: dex-segmented-control-parameter + value: + mobile: xl + tablet: xl + desktop: xl + text: + type: text + locales: + en: >- + ${#jptr:/Get Entries by Slugs Content + entries Loop Item/entry/_name} + color: + type: dex-color-palette-parameter + value: text-primary + weight: + type: dex-segmented-control-parameter + value: bold + transform: + type: dex-segmented-control-parameter + value: '' + _overridability: + parameters: + tag: 'yes' + text: 'yes' + hideLockedParameters: true + parameters: + $viz: + type: $viz + value: + criteria: + clauses: + - op: is + rule: $dt + value: article + source: >- + ${#jptr:/Get Entries by Slugs Content + entries Loop Item/entry/type} + link: + type: link + value: + path: >- + /${locale}/articles/${#jptr:/Get Entries by + Slugs Content entries Loop Item/entry/_slug} + type: projectMapNode + nodeId: 0b2a6284-3d74-4ef6-a3de-b2617058c861 + projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 + dynamicInputValues: + locale: ${locale} + article-slug: >- + ${#jptr:/Get Entries by Slugs Content + entries Loop Item/entry/_slug} + displayName: + type: text + locales: + en: Link for article + parameters: + spacing: + type: dex-space-control-parameter + value: + mobile: {} + tablet: {} + desktop: {} + displayName: + type: text + locales: + en: >- + ${#jptr:/Get Entries by Slugs Content entries Loop + Item/entry/_name} + _dataResources: + Get Entries by Slugs Content entries Loop Item: + type: sys-reserved-loop + variables: + connectedData: '#jptr:/Get Entries by Slugs Content/entries:fa=c' + _dataResources: + Get Entries by Slugs Content: + type: getEntriesBySlugs + variables: + Slugs: ${slugs} + Locale: ${locale} + _locales: + - en +created: '2025-05-06T09:51:14.967555+00:00' +modified: '2025-05-07T09:29:36.419885+00:00' +pattern: false diff --git a/apps/csk/content/composition/759a8084-68e2-40b8-9615-45c7789aabd3.yaml b/apps/csk/content/composition/759a8084-68e2-40b8-9615-45c7789aabd3.yaml new file mode 100644 index 000000000..8a74cfa68 --- /dev/null +++ b/apps/csk/content/composition/759a8084-68e2-40b8-9615-45c7789aabd3.yaml @@ -0,0 +1,179 @@ +composition: + _name: πŸ›’ See my cart + _id: 759a8084-68e2-40b8-9615-45c7789aabd3 + _slug: see-my-cart + type: aiConfiguration + slots: + content: + - _id: 03179326-b7ed-4645-add2-65fdf06b9ead + type: shoppingCart + slots: + checkoutButton: + - _id: 8a3d19eb-3c7d-48c6-8c6a-8a30b87973e9 + type: button + _pattern: 646faee6-b476-43c6-a1a8-eebe85699889 + emptyCartContent: + - _id: 0d41b5c4-37f4-4ed5-a48c-f0c3167acea9 + type: flex + slots: + flexItem: + - _id: eb9a72c4-7ad2-4308-b862-5ab7446926cd + type: text + parameters: + tag: + type: dex-segmented-control-parameter + value: span + size: + type: dex-segmented-control-parameter + value: + mobile: 3xl + tablet: 3xl + desktop: 3xl + text: + type: text + locales: + en: Your shopping cart is empty + color: + type: dex-color-palette-parameter + value: text-primary + weight: + type: dex-segmented-control-parameter + value: bold + - _id: bdfa92b1-1cca-4f56-a67f-ade2aeb5657f + type: image + parameters: + image: + type: asset + value: + - _id: 9d34fc65-5359-4543-bdaa-f496a697133c + type: image + fields: + id: + type: text + value: b207e5e2-a19f-4bda-a9c2-e96a64e1d62c + url: + value: >- + https://img.uniform.global/p/xOBo-4wuSIO8vGPQFReOlg/MDJaAZb3SYqNFljZhXI26g-icon-cart.svg + type: text + file: + type: file + value: d23b35bb-c04d-4bde-bf7d-15435850034e + size: + value: 872 + type: number + title: + type: text + value: icon-cart.svg + width: + value: 24 + type: number + height: + value: 24 + type: number + mediaType: + value: image/svg+xml + type: text + _source: uniform-assets + width: + type: number + value: '100' + height: + type: number + value: '100' + objectFit: + type: dex-segmented-control-parameter + value: cover + - _id: a4eedd91-cb14-491f-b3f6-ce37c42fbc97 + type: text + parameters: + tag: + type: dex-segmented-control-parameter + value: span + size: + type: dex-segmented-control-parameter + value: + mobile: lg + tablet: lg + desktop: xl + text: + type: text + locales: + en: Your shopping cart is empty + color: + type: dex-color-palette-parameter + value: text-primary + weight: + type: dex-segmented-control-parameter + value: normal + parameters: + gap: + type: dex-slider-control-parameter + value: + mobile: '8' + tablet: '8' + desktop: '8' + direction: + type: dex-segmented-control-parameter + value: + mobile: col + tablet: col + desktop: col + alignItems: + type: dex-segmented-control-parameter + value: + mobile: center + tablet: center + desktop: center + displayName: + type: text + locales: + en: 'The shopping cart is empty ' + justifyContent: + type: dex-segmented-control-parameter + value: + mobile: center + tablet: center + desktop: center + parameters: + primaryTextColor: + type: dex-color-palette-parameter + value: text-primary + secondaryTextColor: + type: dex-color-palette-parameter + value: text-link + _overrides: + 5667a267-5c8a-48f1-8cde-e7deecc37806: + parameters: + link: + type: link + locales: + en: + path: /${locale}/cart/checkout + type: projectMapNode + nodeId: 56d212ca-516a-42ee-86e6-2df10e284b60 + projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 + dynamicInputValues: + locale: ${locale} + text: + type: text + value: Proceed to Checkout + 8a3d19eb-3c7d-48c6-8c6a-8a30b87973e9: + parameters: + link: + type: link + locales: + en: + path: /${locale}/cart/checkout + type: projectMapNode + nodeId: 56d212ca-516a-42ee-86e6-2df10e284b60 + projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 + dynamicInputValues: + locale: ${locale} + text: + type: text + value: Proceed to Checkout + _locales: + - en +created: '2025-04-24T11:31:53.153196+00:00' +modified: '2025-04-28T14:37:45.895972+00:00' +pattern: false diff --git a/apps/csk/content/composition/f0ad3155-f91e-480f-b7f0-c17ea55e74ee.yaml b/apps/csk/content/composition/f0ad3155-f91e-480f-b7f0-c17ea55e74ee.yaml new file mode 100644 index 000000000..42596c741 --- /dev/null +++ b/apps/csk/content/composition/f0ad3155-f91e-480f-b7f0-c17ea55e74ee.yaml @@ -0,0 +1,187 @@ +composition: + _name: πŸ’‘User Recommendations + _id: f0ad3155-f91e-480f-b7f0-c17ea55e74ee + _slug: user-recommendations + type: aiConfiguration + slots: + content: + - _id: deb54d06-7383-447b-a887-83c59163ebad + type: $personalization + slots: + pz: + - _id: 05b0d6ab-6d7c-4a9f-b2ff-6c4ee77dfc1e + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + - _id: 19736869-a87c-4653-a0b8-f387c508a8a0 + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + - _id: 06d4db25-2982-45e5-94dd-1ed0f3c4ecef + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + - _id: 46dd8de6-d6ff-4f6e-be75-b714153157fb + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + - _id: 8389a989-0906-4838-a43c-1f63892479f0 + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + - _id: b5c786ff-b0ad-431a-8d7b-af5daa2f9dda + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + - _id: c2d6999e-f277-4e64-82c6-4b2c25e0849f + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + - _id: 60e43a43-0e40-4e2a-ba09-c82617c55c2a + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + - _id: 16d6c5ec-276d-4d09-b374-3e91adeaffff + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + - _id: ee80c87c-9651-4b43-ae61-bc5e86561244 + type: flex + _pattern: 6ac15a92-bef3-4cf1-9bff-a2bcb666b73d + parameters: + count: + type: number + value: '3' + algorithm: + type: pzAlgorithm + value: ssc + trackingEventName: + type: text + value: Recommendations + _overrides: + 05b0d6ab-6d7c-4a9f-b2ff-6c4ee77dfc1e: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_arabica + name: arabica 1 + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: f7e77f37-2646-410e-8596-6ae16379d5d9 + 06d4db25-2982-45e5-94dd-1ed0f3c4ecef: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_arabica + name: arabica 3 + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: 79ddd1f3-30a9-4f4d-8a93-0d9e17ad5967 + 16d6c5ec-276d-4d09-b374-3e91adeaffff: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_turkish + name: turkish + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: 368a700a-adfd-4304-9186-534f7cbac088 + 19736869-a87c-4653-a0b8-f387c508a8a0: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_arabica + name: arabica 2 + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: ff365bbd-13c4-43ab-9747-e4cf5a85a4ce + 46dd8de6-d6ff-4f6e-be75-b714153157fb: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_espresso + name: espresso-1 + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: 2f042be4-1e8b-4fae-be0e-a5294b89d8dd + 60e43a43-0e40-4e2a-ba09-c82617c55c2a: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_pour-over + name: Medium Coffee Makers interest + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: 493503ea-3332-4bb9-b995-b311658b26c6 + 8389a989-0906-4838-a43c-1f63892479f0: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_espresso + name: espresso-2 + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: d4af2511-76c3-4c8c-b979-1c3da1c667fb + b5c786ff-b0ad-431a-8d7b-af5daa2f9dda: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_espresso + name: espresso-3 + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: d2719c84-ddf5-4020-8d11-a75fbe3ef2f7 + c2d6999e-f277-4e64-82c6-4b2c25e0849f: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_pour-over + name: High Coffee Makers interest + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: d7047783-160a-4d26-a172-e8330f55c567 + ee80c87c-9651-4b43-ae61-bc5e86561244: + parameters: + $pzCrit: + type: $pzCrit + value: + dim: subCategory_french-press + name: french press + dataResources: + Single Product Content: + type: singleProduct + variables: + locale: ${locale} + entryId: 28bf4036-f588-4ee0-94bb-241d39270972 + _locales: + - en +created: '2025-04-24T11:51:44.798972+00:00' +modified: '2025-04-28T14:27:34.005827+00:00' +pattern: false diff --git a/apps/csk/content/contentType/promptItem.yaml b/apps/csk/content/contentType/promptItem.yaml new file mode 100644 index 000000000..dce1c91d5 --- /dev/null +++ b/apps/csk/content/contentType/promptItem.yaml @@ -0,0 +1,27 @@ +id: promptItem +name: Prompt Item +created: '2025-04-24T10:50:45.169153+00:00' +updated: '2025-04-24T10:50:45.169153+00:00' +slugSettings: {} +useTeamPermissions: true +fields: + - id: name + name: Name + type: text + helpText: Name of the prompt to connect to the model configuration. + typeConfig: null + - id: value + name: Value + type: text + helpText: >- + Provide a text prompt. Create variables by enclosing their name in double + curly braces as in {{variable}}. + typeConfig: + multiline: true + linesCount: 100 +entryName: name +description: '' +icon: rectangle-rounded +type: block +permissions: [] +previewConfigurations: [] diff --git a/apps/csk/content/contentType/promptStore.yaml b/apps/csk/content/contentType/promptStore.yaml new file mode 100644 index 000000000..24058c6f9 --- /dev/null +++ b/apps/csk/content/contentType/promptStore.yaml @@ -0,0 +1,30 @@ +id: promptStore +name: Prompt Store +created: '2025-04-24T10:56:12.927036+00:00' +updated: '2025-04-24T10:56:12.927036+00:00' +slugSettings: {} +useTeamPermissions: true +fields: + - id: name + name: Name + type: text + typeConfig: null + - id: prompts + name: Prompts + type: $block + typeConfig: + allowedTypes: + - promptItem + - id: thumbnail + name: Thumbnail + type: asset + typeConfig: + allowedTypes: + - image +entryName: name +thumbnailField: thumbnail +description: '' +icon: file-remove +type: contentType +permissions: [] +previewConfigurations: [] diff --git a/apps/csk/content/contentType/starterPrompt.yaml b/apps/csk/content/contentType/starterPrompt.yaml new file mode 100644 index 000000000..7453f6a7f --- /dev/null +++ b/apps/csk/content/contentType/starterPrompt.yaml @@ -0,0 +1,21 @@ +id: starterPrompt +name: Starter Prompt +created: '2025-04-24T10:49:28.695485+00:00' +updated: '2025-04-24T10:50:53.775332+00:00' +slugSettings: {} +useTeamPermissions: true +fields: + - id: value + name: Value + type: text + helpText: >- + This prompt will be displayed as one of the quick preset prompts in the AI + chat. + typeConfig: + regex: '' +entryName: value +description: '' +icon: rectangle-rounded +type: block +permissions: [] +previewConfigurations: [] diff --git a/apps/csk/content/dataType/getEntriesBySlugs.yaml b/apps/csk/content/dataType/getEntriesBySlugs.yaml new file mode 100644 index 000000000..fcaefba0e --- /dev/null +++ b/apps/csk/content/dataType/getEntriesBySlugs.yaml @@ -0,0 +1,24 @@ +id: getEntriesBySlugs +displayName: Get Entries by Slugs +ttl: 30 +path: /api/v1/entries +custom: + proposedName: api/v1/entries +method: GET +purgeKey: 211fb981-c72e-4639-ac01-124352bdf466 +archetype: default +variables: + Slugs: + type: text + default: '[]' + Locale: + type: text + default: '' +parameters: + - key: locale + value: ${Locale} + omitIfEmpty: true + - key: filters.slug[in] + value: ${Slugs} +dataSourceId: uniformApp +allowedOnComponents: [] diff --git a/apps/csk/content/dataType/promptStore.yaml b/apps/csk/content/dataType/promptStore.yaml new file mode 100644 index 000000000..770cfcf90 --- /dev/null +++ b/apps/csk/content/dataType/promptStore.yaml @@ -0,0 +1,16 @@ +id: promptStore +displayName: Prompt Store +path: /single +custom: + allowedContentTypes: + - promptStore + uniformAutogenerated: true +method: GET +purgeKey: cc58214a-1c19-47f2-8bac-ffbfd4534824 +archetype: singleEntry +variables: + entryId: + type: text + default: '' +dataSourceId: uniformContent +allowedOnComponents: [] diff --git a/apps/csk/content/dataType/promptStoreList.yaml b/apps/csk/content/dataType/promptStoreList.yaml new file mode 100644 index 000000000..ad501d3b9 --- /dev/null +++ b/apps/csk/content/dataType/promptStoreList.yaml @@ -0,0 +1,28 @@ +id: promptStoreList +displayName: Prompt Store List +path: /query +custom: + allowedContentTypes: + - promptStore + uniformAutogenerated: true +method: GET +purgeKey: 1eee6163-86ee-4d48-984a-8956000b8cc6 +archetype: queryEntries +variables: + limit: + type: text + default: '20' + offset: + type: text + default: '0' + filters: + type: text + default: '{}' + orderBy: + type: text + default: '' + contentTypes: + type: text + default: '' +dataSourceId: uniformContent +allowedOnComponents: [] diff --git a/apps/csk/content/entry/d413614a-ed47-41df-ae27-164e180edb32.yaml b/apps/csk/content/entry/d413614a-ed47-41df-ae27-164e180edb32.yaml new file mode 100644 index 000000000..1955d74b0 --- /dev/null +++ b/apps/csk/content/entry/d413614a-ed47-41df-ae27-164e180edb32.yaml @@ -0,0 +1,245 @@ +created: '2025-04-24T10:56:33.698174+00:00' +modified: '2025-05-16T07:31:44.882609+00:00' +pattern: false +entry: + _id: d413614a-ed47-41df-ae27-164e180edb32 + _name: AI Coffee Assistant Prompts + _slug: ai-coffee-assistant-prompts + _thumbnail: >- + https://img.uniform.global/p/6MJ1ELg6RE6-E2mQbcKLjg/RMrOgC3-TCqwbGHL8Pi-ZA-coffee_ai.png + type: promptStore + fields: + name: + type: text + value: AI Coffee Assistant Prompts + prompts: + type: $block + value: + - _id: b91ea80b-0638-4288-9614-5d7066c0d4ca + type: promptItem + fields: + name: + type: text + value: system + value: + type: text + value: >- + You are an intelligent AI-assistant that: + + - answers topical questions using an internal knowledge base + + - recommends products to users based on their interests + + - describes the user’s cart and recommends additional products + based on items already added + + - updates the user’s interest profile + + + ─────────────────────────────── + + 1. PROCESSING THE INTEREST PROFILE + + ─────────────────────────────── + + Before responding to the user, the AI-assistant must analyze + **every** incoming message to adjust the user’s interest + profile. Always do this silently in the backgroundβ€”do not show + or mention it to the user. + + + Tool for updating the interest profile: + + - \`setUserInterests\` β€” updates the user’s interest profile + + Examples of possible interests you can set: + + {{enrichments}} + + + **Important:** Set interests **only** with the cat/key pairs + listed above. Do **not** invent new categories or keys. + + + Procedure (perform **for every incoming user message** before + your main reply): + + β€’ **Analyze the message** β€” pick the interests that best match + the message. + + β€’ **Compose a new profile** β€” for each relevant category, + calculate \`str\` (0 = no interest, 100 = very interested) and + build an array of \`{ cat, key, str }\`. + + β€’ **Save the profile** β€” **if at least one interest was + identified**, call \`setUserInterests\` with that array to + overwrite the stored interests. If no interests can be inferred, + skip the call. + + β€’ **Proceed** β€” after updating (or skipping) the profile, handle + the visible user request (contextual answer, product + recommendation, cart info, etc.). + + + ─────────────────────────────── + + 2. CONTEXT LOOK-UP AND ANSWERING QUESTIONS + + ─────────────────────────────── + + If the user asks a factual question or requests advice, **do not + invent an answer**. First call \`getContext\` (after any + interest update) to retrieve authoritative information. Use that + information to craft a clear, Markdown-formatted reply that + fully addresses the request and encourages further dialogue or + clarifying questions. + + + If the user request does not clearly fall under cart handling, + recommendations, or another predefined flow, treat it as a + general question and **always call \`getContext\` first** before + answering. + + + ─────────────────────────────── + + 3. RECOMMENDATIONS BASED ON USER INTERESTS + + ─────────────────────────────── + + Call \`getRecommendProducts\` **only** when the user explicitly + asks for product recommendations. Do not invoke this tool for + other kinds of questions. If the returned list is empty, invite + the user to browse more pages or describe their interests, then + ask whether they'd like further assistance finding something + useful. + + + ─────────────────────────────── + + 4. WORKING WITH THE USER’S CART + + ─────────────────────────────── + + When the user inquires about their cart, call \`getCart\` to see + what’s inside and the total cost. Offer to suggest items based + on the existing products. Use \`getRelatedProducts\` to pull in + items related to the cart’s contents. If the cart is empty, + suggest that the user visit the site and pick items they like. + + + ─────────────────────────────── + + 5. RESPONSE FORMATTING FOR ALL ANSWERS + + ─────────────────────────────── + + β€’ **Markdown only.** Format every reply in Markdown so it is + easy to read on the site. + + β€’ **No links.** NEVER include raw URLs or hyperlinks in + responses. + + β€’ Keep messages concise, friendly, and helpful. + - _id: 1900ae3e-4502-42ae-b0a9-0de807c378d1 + type: promptItem + fields: + name: + type: text + value: setUserInterests + value: + type: text + value: >- + Overwrites the user's interest profile. Accepts a new profile in + the format of an array { cat, key, str } where: + + - cat β€” category ID + + - key β€” value ID + + - str β€” strength of preference: + - maximum β€” strong interest + - half of maximum β€” moderate interest + - 0 β€” aversion + - _id: 3f2624ef-221d-439e-83a0-4d50dec320c8 + type: promptItem + fields: + name: + type: text + value: getRecommendProducts + value: + type: text + value: >- + Returns product recommendations based on the user’s current + interest profile. Returns an array of product titles recommended + to the user according to their interests. + - _id: bf54d238-8982-438c-807a-5ecf711d0b0b + type: promptItem + fields: + name: + type: text + value: getCart + value: + type: text + value: >- + Returns information about the user's cart and its total cost. + Also includes product names and brief descriptions. + - _id: 4a2853ea-5ad7-407b-8718-81993ca845be + type: promptItem + fields: + name: + type: text + value: getRelatedProducts + value: + type: text + value: >- + Returns an array of products related to the items currently in + the cart. + - _id: f622bbeb-fd7a-45ab-b244-1c89c428f701 + type: promptItem + fields: + name: + type: text + value: getContext + value: + type: text + value: >- + When the user makes a request, the AI-assistant must: + + 1. Identify the main concepts and intents. + + 2. Add relevant synonyms and related terms. + + 3. Remove irrelevant stop words. + + 4. Structure the query by highlighting key terms. + + 5. Include technical or industry terminology when necessary. + + + Provide **only** the optimized search query with no + explanations, greetings, or extra comments. + + + Example input: β€œhow to fix a flat bicycle tire” + + Example output: β€œbicycle tire repair puncture patch inflate + service flat tire inner tube replacement” + + + Constraints: + + - Output only expanded search queries. + + - Focus on search terms. + + - Include both specific and general terms. + + - Preserve all important meanings from the original query. + + - Ignore slugs since they are not valid for the user. + thumbnail: + type: asset + value: [] + _locales: + - en diff --git a/apps/csk/content/files/L3AvTk53WUtReHRSVlNZLUJhQ1A2N0prdy9uY1VDY0dGQlFPdUV5NjF3dmxRYU93LWNvZmZlZV9haS5wbmc=.png b/apps/csk/content/files/L3AvTk53WUtReHRSVlNZLUJhQ1A2N0prdy9uY1VDY0dGQlFPdUV5NjF3dmxRYU93LWNvZmZlZV9haS5wbmc=.png new file mode 100644 index 000000000..f8f76bdb9 Binary files /dev/null and b/apps/csk/content/files/L3AvTk53WUtReHRSVlNZLUJhQ1A2N0prdy9uY1VDY0dGQlFPdUV5NjF3dmxRYU93LWNvZmZlZV9haS5wbmc=.png differ diff --git a/apps/csk/content/projectMapNode/ai-tools_74ac5157-3a4a-44f4-a9e1-37bb25ba51b4.yaml b/apps/csk/content/projectMapNode/ai-tools_74ac5157-3a4a-44f4-a9e1-37bb25ba51b4.yaml new file mode 100644 index 000000000..033b6cadc --- /dev/null +++ b/apps/csk/content/projectMapNode/ai-tools_74ac5157-3a4a-44f4-a9e1-37bb25ba51b4.yaml @@ -0,0 +1,8 @@ +id: 74ac5157-3a4a-44f4-a9e1-37bb25ba51b4 +name: πŸ€– AI Tools +order: 1400 +path: /:locale/ai-tools +type: placeholder +pathSegment: ai-tools +data: {} +projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 diff --git a/apps/csk/content/projectMapNode/cart-recommendations_309229b7-f5b2-405a-8694-c897db7998bd.yaml b/apps/csk/content/projectMapNode/cart-recommendations_309229b7-f5b2-405a-8694-c897db7998bd.yaml new file mode 100644 index 000000000..1325082db --- /dev/null +++ b/apps/csk/content/projectMapNode/cart-recommendations_309229b7-f5b2-405a-8694-c897db7998bd.yaml @@ -0,0 +1,15 @@ +id: 309229b7-f5b2-405a-8694-c897db7998bd +name: πŸ›’ Cart Recommendations +order: 750 +path: /:locale/ai-tools/cart-recommendations +type: composition +pathSegment: cart-recommendations +data: + queryStrings: + - name: slugs + value: >- + peruvian-blend-coffee-134, house-blend-coffee-131, + kosmic-coffee-barista-express-espresso-machine-115 + helpText: '' +compositionId: 5288b954-d654-40aa-b439-b01eab5982e9 +projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 diff --git a/apps/csk/content/projectMapNode/context-recommendations_82c2b0bb-0b38-4bf9-940b-eebfeed78ae2.yaml b/apps/csk/content/projectMapNode/context-recommendations_82c2b0bb-0b38-4bf9-940b-eebfeed78ae2.yaml new file mode 100644 index 000000000..98ba84c74 --- /dev/null +++ b/apps/csk/content/projectMapNode/context-recommendations_82c2b0bb-0b38-4bf9-940b-eebfeed78ae2.yaml @@ -0,0 +1,14 @@ +id: 82c2b0bb-0b38-4bf9-940b-eebfeed78ae2 +name: ℹ️ Context Recommendations +order: 900 +path: /:locale/ai-tools/context-recommendations +type: composition +pathSegment: context-recommendations +data: + queryStrings: + - name: slugs + value: >- + the-composable-conference-kickoff,kosmic-coffee-barista-express-espresso-machine-115 + helpText: '' +compositionId: 5aa09de0-3fac-4ff5-bbbc-52ca41cb4880 +projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 diff --git a/apps/csk/content/projectMapNode/see-my-cart_6625324a-9372-4bce-b2a4-dc33924fb198.yaml b/apps/csk/content/projectMapNode/see-my-cart_6625324a-9372-4bce-b2a4-dc33924fb198.yaml new file mode 100644 index 000000000..1fd67b559 --- /dev/null +++ b/apps/csk/content/projectMapNode/see-my-cart_6625324a-9372-4bce-b2a4-dc33924fb198.yaml @@ -0,0 +1,9 @@ +id: 6625324a-9372-4bce-b2a4-dc33924fb198 +name: πŸ›’ See my cart +order: 800 +path: /:locale/ai-tools/see-my-cart +type: composition +pathSegment: see-my-cart +data: {} +compositionId: 759a8084-68e2-40b8-9615-45c7789aabd3 +projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 diff --git a/apps/csk/content/projectMapNode/user-recommendations_357de59a-ef74-4258-a856-bac6cb3fba1e.yaml b/apps/csk/content/projectMapNode/user-recommendations_357de59a-ef74-4258-a856-bac6cb3fba1e.yaml new file mode 100644 index 000000000..9ee03be29 --- /dev/null +++ b/apps/csk/content/projectMapNode/user-recommendations_357de59a-ef74-4258-a856-bac6cb3fba1e.yaml @@ -0,0 +1,9 @@ +id: 357de59a-ef74-4258-a856-bac6cb3fba1e +name: πŸ’‘ User Recommendations +order: 700 +path: /:locale/ai-tools/user-recommendations +type: composition +pathSegment: user-recommendations +data: {} +compositionId: f0ad3155-f91e-480f-b7f0-c17ea55e74ee +projectMapId: 537d11ff-9ebe-4420-9682-36694477e2f9 diff --git a/apps/csk/drizzle.config.ts b/apps/csk/drizzle.config.ts new file mode 100644 index 000000000..1098c4cf4 --- /dev/null +++ b/apps/csk/drizzle.config.ts @@ -0,0 +1,10 @@ +import type { Config } from 'drizzle-kit'; + +export default { + schema: './src/modules/chat/rag/db/schema', + dialect: 'postgresql', + out: './src/modules/chat/rag/db/migrations', + dbCredentials: { + url: process.env.DATABASE_URL!, + }, +} satisfies Config; diff --git a/apps/csk/package.json b/apps/csk/package.json index e7f4392d2..cea54e077 100644 --- a/apps/csk/package.json +++ b/apps/csk/package.json @@ -31,9 +31,16 @@ "uniform:pull": "run-s pull:dex pull:content", "uniform:push": "run-s push:dex push:content", "uniform:publish": "uniform context manifest publish", - "recipes": "csk-recipes init" + "recipes": "csk-recipes init", + "db:migrate": "tsx src/modules/chat/rag/db/migrate.ts", + "db:drop": "drizzle-kit drop", + "db:generate": "drizzle-kit generate", + "db:push": "drizzle-kit push", + "db:load": "tsx src/modules/chat/rag/scripts/loadResources.ts" }, "dependencies": { + "@ai-sdk/openai": "^1.2.5", + "@ai-sdk/react": "^1.1.23", "@next/third-parties": "^15.3.2", "@radix-ui/react-slider": "^1.3.2", "@radix-ui/react-slot": "^1.1.1", @@ -41,15 +48,22 @@ "@uniformdev/context-gtag": "^20.20.3", "@uniformdev/csk-components": "*", "@uniformdev/design-extensions-tools": "*", + "ai": "^4.1.61", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^3.6.0", + "drizzle-orm": "^0.43.1", + "drizzle-zod": "^0.7.1", "lucide-react": "^0.474.0", + "nanoid": "^5.1.5", "next": "^15.3.2", "next-intl": "^3.26.3", + "postgres": "^3.4.5", "react": "^19.1.0", "react-day-picker": "^9.6.7", "react-dom": "^19.1.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "ua-parser-js": "^2.0.3", @@ -65,6 +79,7 @@ "@uniformdev/csk-cli": "*", "@uniformdev/csk-recipes": "*", "cross-env": "^7.0.3", + "drizzle-kit": "^0.31.0", "eslint": "^9.19.0", "eslint-config-next": "^15.3.2", "eslint-config-prettier": "^10.0.1", diff --git a/apps/csk/src/app/[[...path]]/page.tsx b/apps/csk/src/app/[[...path]]/page.tsx index e9f8607b3..3b2632420 100644 --- a/apps/csk/src/app/[[...path]]/page.tsx +++ b/apps/csk/src/app/[[...path]]/page.tsx @@ -1,4 +1,5 @@ import { cookies } from 'next/headers'; +import { notFound } from 'next/navigation'; import { ContextUpdateTransfer, PageParameters, @@ -17,11 +18,35 @@ import { DesignExtensionsProvider } from '@uniformdev/design-extensions-tools/co //? } import { componentResolver } from '@/components'; import { DeviceTypeSetter } from '@/components/custom-ui/DeviceTypeSetter'; +//? if (localization) { +import { routing } from '@/i18n/routing'; +//? } +import { isAIAssistantConfigurationPage } from '@/modules/chat/utils'; import { DEVICE_TYPE_COOKIE_NAME } from '@/utils/deviceType'; import { getPreviewViewports } from '@/utils/previewClient'; export default async function Home(props: PageParameters) { const cookieStore = await cookies(); + + const { path = '' } = (await props.params) || {}; + const pathname = typeof path === 'string' ? path : path.join('/'); + + //? if (localization) { + const currentLocale = + Array.isArray(path) && path.length > 0 && routing.locales.includes(path[0]) ? path[0] : routing.defaultLocale; + //? } + + const searchParams = await props.searchParams; + const isPreviewMode = isIncontextEditingEnabled({ searchParams }); + + //? if (localization) { + if (isAIAssistantConfigurationPage(isPreviewMode, pathname, currentLocale)) { + //? } else { + //? write('if (isAIAssistantConfigurationPage(isPreviewMode, pathname)) {\n'); + //? } + return notFound(); + } + const deviceType = cookieStore.get(DEVICE_TYPE_COOKIE_NAME)?.value || ''; //? if (localization) { const route = await retrieveRoute(props); @@ -29,11 +54,9 @@ export default async function Home(props: PageParameters) { //? write('const route = await retrieveRoute(props, locales.defaultLocale);\n'); //? } - const searchParams = await props.searchParams; const serverContext = await createServerUniformContext({ searchParams, }); - const isPreviewMode = isIncontextEditingEnabled({ searchParams }); const previewViewports = await getPreviewViewports(); return ( diff --git a/apps/csk/src/app/api/chat/route.ts b/apps/csk/src/app/api/chat/route.ts new file mode 100644 index 000000000..8cc78fbd4 --- /dev/null +++ b/apps/csk/src/app/api/chat/route.ts @@ -0,0 +1 @@ +export { POST } from '@/modules/chat'; diff --git a/apps/csk/src/app/api/webhook/entry-event/route.ts b/apps/csk/src/app/api/webhook/entry-event/route.ts new file mode 100644 index 000000000..bc40dd245 --- /dev/null +++ b/apps/csk/src/app/api/webhook/entry-event/route.ts @@ -0,0 +1,60 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { CANVAS_PUBLISHED_STATE } from '@uniformdev/canvas'; +import locales from '@/i18n/locales.json'; +import { ENTRY_TYPES_TO_SYNC } from '@/modules/chat/constants'; +import { createOrUpdateResource, deleteResource } from '@/modules/chat/rag/actions/resources'; +import { transformEntryToResource } from '@/modules/chat/utils'; +import { contentClient } from '@/modules/chat/utils/uniformClients'; + +const syncEntryWithDatabase = async (entryId: string): Promise<{ message: string }> => { + const { entries } = await contentClient + .getEntries({ + entryIDs: [entryId], + diagnostics: false, + locale: locales.defaultLocale, + state: CANVAS_PUBLISHED_STATE, + }) + .catch(e => { + if (e.statusCode !== 404) throw e; + return { entries: [] }; + }); + + const deleteAndRespond = async () => { + await deleteResource(entryId); + return { message: `Resource ${entryId} and its embeddings were successfully deleted from database.` }; + }; + + if (entries.length === 0) { + return deleteAndRespond(); + } + + const resource = transformEntryToResource(entries[0]); + if (resource.textContent.length) { + await createOrUpdateResource(resource); + return { message: `Resource ${entryId} and its embeddings were successfully created/updated in database.` }; + } + + return deleteAndRespond(); +}; + +export async function POST(req: NextRequest) { + try { + const payload = await req.json(); + const rawType = typeof payload?.type === 'string' ? payload.type : 'unknown'; + const entryId = typeof payload?.id === 'string' ? payload.id : 'none'; + + console.info(`[webhook][entry-change] Incoming event: type=${rawType}, entryId=${entryId}`); + + if (ENTRY_TYPES_TO_SYNC.includes(rawType)) { + const result = await syncEntryWithDatabase(entryId); + console.info(`[webhook][entry-change] Sync completed for ${rawType} (${entryId}): ${result.message}`); + return NextResponse.json(result, { status: 200 }); + } + + console.warn(`[webhook][entry-change] No handler for entry type="${rawType}"`); + return NextResponse.json({ message: `No action taken for entry type "${rawType}".` }, { status: 200 }); + } catch (error) { + console.error('[webhook][entry-change] Fatal error:', error); + return NextResponse.json({ message: 'Internal server error while syncing entry.' }, { status: 500 }); + } +} diff --git a/apps/csk/src/app/layout.tsx b/apps/csk/src/app/layout.tsx index ef810b89c..a5a75140f 100644 --- a/apps/csk/src/app/layout.tsx +++ b/apps/csk/src/app/layout.tsx @@ -10,6 +10,8 @@ import '@/styles/dimensions.css'; import '@/styles/fonts.css'; import '@/styles/borders.css'; import { customFontVariables } from '@/fonts'; +import { Chat } from '@/modules/chat'; +import { ChatProvider } from '@/modules/chat/providers/ChatProvider'; import { CardProvider } from '@/providers/CardProvider'; import { FavoritesProvider } from '@/providers/FavoritesProvider'; import { UniformClientContext } from '@/utils/clientContext'; @@ -26,7 +28,14 @@ export default function RootLayout({ children }: { children: ReactNode }) { - {children} + + +
+
{children}
+ +
+
+
diff --git a/apps/csk/src/app/sitemap.ts b/apps/csk/src/app/sitemap.ts index fd18e35de..b1495936c 100644 --- a/apps/csk/src/app/sitemap.ts +++ b/apps/csk/src/app/sitemap.ts @@ -1,6 +1,7 @@ import type { MetadataRoute } from 'next'; import { ProjectMapClient, getNodeActiveCompositionEdition } from '@uniformdev/project-map'; import localesConfig from '@/i18n/locales.json'; +import { AI_ASSISTANT_CONFIGURATION_PLACEHOLDER } from '@/modules/chat/constants'; const projectMap = new ProjectMapClient({ apiHost: process.env.UNIFORM_CLI_BASE_URL! || 'https://uniform.app', @@ -24,7 +25,12 @@ export default async function sitemap(): Promise { const isLocalized = localesConfig?.locales?.length > 0; return nodes.flatMap(node => { - if (!isLocalized || !node.path?.includes(':locale')) { + const path = node.path; + if (!path || path.startsWith(AI_ASSISTANT_CONFIGURATION_PLACEHOLDER)) { + return []; + } + + if (!isLocalized || !path.includes(':locale')) { const edition = getNodeActiveCompositionEdition({ node, targetLocale: undefined, @@ -32,7 +38,7 @@ export default async function sitemap(): Promise { return [ { - url: `${domain}${node.path}`, + url: `${domain}${path}`, lastModified: edition?.modified, changeFrequency: 'daily', priority: 1, @@ -47,7 +53,7 @@ export default async function sitemap(): Promise { }); return { - url: `${domain}${node.path?.replace(':locale', locale)}`, + url: `${domain}${path.replace(':locale', locale)}`, lastModified: edition?.modified, changeFrequency: 'daily', priority: 1, diff --git a/apps/csk/src/components/custom-canvas/Recommendations.tsx b/apps/csk/src/components/custom-canvas/Recommendations.tsx new file mode 100644 index 000000000..5bf178d8b --- /dev/null +++ b/apps/csk/src/components/custom-canvas/Recommendations.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { ComponentProps, UniformSlot } from '@uniformdev/canvas-next-rsc/component'; + +enum RecommendationsSlots { + Recommendations = 'recommendations', +} + +const Recommendations: FC, RecommendationsSlots>> = ({ + component, + context, + slots, +}) => ( +
+ +
+); + +export default Recommendations; diff --git a/apps/csk/src/components/index.ts b/apps/csk/src/components/index.ts index 28deabab4..28bf075a0 100644 --- a/apps/csk/src/components/index.ts +++ b/apps/csk/src/components/index.ts @@ -1,10 +1,12 @@ import createComponentResolver, { ComponentMapping } from '@uniformdev/csk-components/utils/createComponentResolver'; import { cskComponentsMapping } from '@/components/canvas'; import { customComponentsMapping } from '@/components/custom-canvas'; +import { aiAssistantComponentsMapping } from '@/modules/chat'; const componentsMapping: ComponentMapping = { ...cskComponentsMapping, ...customComponentsMapping, + ...aiAssistantComponentsMapping, }; export const componentResolver = createComponentResolver(componentsMapping); diff --git a/apps/csk/src/modules/chat/components/canvas/AiAssistant/aiAssistant.tsx b/apps/csk/src/modules/chat/components/canvas/AiAssistant/aiAssistant.tsx new file mode 100644 index 000000000..38bd7c411 --- /dev/null +++ b/apps/csk/src/modules/chat/components/canvas/AiAssistant/aiAssistant.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { FC, useEffect } from 'react'; +import { flattenValues } from '@uniformdev/canvas'; +import ChatButton from '@/modules/chat/components/ui/ChatButton'; +import { useChatProvider } from '@/modules/chat/providers/ChatProvider'; +import { AiAssistantProps } from '.'; + +export const AiAssistant: FC = ({ starterPrompts }) => { + const { isChatActive, setIsAiDrawerOpen, isAiDrawerOpen, setPrompts } = useChatProvider(); + + useEffect(() => { + const prompts = flattenValues(starterPrompts) as unknown as { value: string }[]; + setPrompts(prompts.filter(Boolean)?.map(prompt => prompt?.value) || []); + }, [setPrompts, starterPrompts]); + + return ( + setIsAiDrawerOpen(prev => !prev)} + isAiDrawerOpen={isAiDrawerOpen} + /> + ); +}; diff --git a/apps/csk/src/modules/chat/components/canvas/AiAssistant/index.ts b/apps/csk/src/modules/chat/components/canvas/AiAssistant/index.ts new file mode 100644 index 000000000..c1c177405 --- /dev/null +++ b/apps/csk/src/modules/chat/components/canvas/AiAssistant/index.ts @@ -0,0 +1,10 @@ +import type { DataWithProperties } from '@uniformdev/canvas'; +import { ComponentProps } from '@uniformdev/canvas-next-rsc/component'; + +export type AiAssistantParameters = { + starterPrompts: DataWithProperties; +}; + +export type AiAssistantProps = ComponentProps; + +export { AiAssistant as default } from './aiAssistant'; diff --git a/apps/csk/src/modules/chat/components/canvas/AiConfiguration/aiConfiguration.tsx b/apps/csk/src/modules/chat/components/canvas/AiConfiguration/aiConfiguration.tsx new file mode 100644 index 000000000..04413fdb6 --- /dev/null +++ b/apps/csk/src/modules/chat/components/canvas/AiConfiguration/aiConfiguration.tsx @@ -0,0 +1,7 @@ +import { FC } from 'react'; +import { UniformSlot } from '@uniformdev/canvas-next-rsc/component'; +import { AiConfigurationProps } from '.'; + +export const AiConfiguration: FC = ({ component, context, slots }) => ( + +); diff --git a/apps/csk/src/modules/chat/components/canvas/AiConfiguration/index.ts b/apps/csk/src/modules/chat/components/canvas/AiConfiguration/index.ts new file mode 100644 index 000000000..2a475478d --- /dev/null +++ b/apps/csk/src/modules/chat/components/canvas/AiConfiguration/index.ts @@ -0,0 +1,10 @@ +import { ComponentProps } from '@uniformdev/canvas-next-rsc/component'; +import { PageParameters as CSKPageParameters } from '@uniformdev/csk-components/components/canvas'; + +export enum AiConfigurationSlots { + Content = 'content', +} + +export type AiConfigurationProps = ComponentProps; + +export { AiConfiguration as default } from './aiConfiguration'; diff --git a/apps/csk/src/modules/chat/components/canvas/AssistantScrollSection/assistantSection.tsx b/apps/csk/src/modules/chat/components/canvas/AssistantScrollSection/assistantSection.tsx new file mode 100644 index 000000000..eb1a04462 --- /dev/null +++ b/apps/csk/src/modules/chat/components/canvas/AssistantScrollSection/assistantSection.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { UniformSlot } from '@uniformdev/canvas-next-rsc/component'; + +import { AssistantScrollSectionProps } from '.'; + +export const AssistantScrollSection: FC = ({ component, context, slots }) => ( +
+ {/* TODO: fix height */} +
+ +
+
+); diff --git a/apps/csk/src/modules/chat/components/canvas/AssistantScrollSection/index.ts b/apps/csk/src/modules/chat/components/canvas/AssistantScrollSection/index.ts new file mode 100644 index 000000000..dc42311d9 --- /dev/null +++ b/apps/csk/src/modules/chat/components/canvas/AssistantScrollSection/index.ts @@ -0,0 +1,9 @@ +import { type ComponentProps } from '@uniformdev/canvas-next-rsc/component'; + +enum AssistantScrollSectionSlots { + content = 'content', +} + +export type AssistantScrollSectionProps = ComponentProps, AssistantScrollSectionSlots>; + +export { AssistantScrollSection as default } from './assistantSection'; diff --git a/apps/csk/src/modules/chat/components/canvas/index.ts b/apps/csk/src/modules/chat/components/canvas/index.ts new file mode 100644 index 000000000..a93b2eea1 --- /dev/null +++ b/apps/csk/src/modules/chat/components/canvas/index.ts @@ -0,0 +1,10 @@ +import { ComponentMapping } from '@uniformdev/csk-components/utils/createComponentResolver'; +import AiAssistant from './AiAssistant'; +import AiConfiguration from './AiConfiguration'; +import AssistantScrollSection from './AssistantScrollSection'; + +export const aiAssistantComponentsMapping: ComponentMapping = { + aiAssistant: { component: AiAssistant }, + aiConfiguration: { component: AiConfiguration }, + assistantScrollSection: { component: AssistantScrollSection }, +}; diff --git a/apps/csk/src/modules/chat/components/ui/AiIcon.tsx b/apps/csk/src/modules/chat/components/ui/AiIcon.tsx new file mode 100644 index 000000000..fa6bb4fd8 --- /dev/null +++ b/apps/csk/src/modules/chat/components/ui/AiIcon.tsx @@ -0,0 +1,23 @@ +import { FC } from 'react'; + +export const AiIcon: FC = () => ( +
+ +
+); +AiIcon.displayName = 'AiIcon'; diff --git a/apps/csk/src/modules/chat/components/ui/AiMessage.tsx b/apps/csk/src/modules/chat/components/ui/AiMessage.tsx new file mode 100644 index 000000000..ce3624bf6 --- /dev/null +++ b/apps/csk/src/modules/chat/components/ui/AiMessage.tsx @@ -0,0 +1,112 @@ +import React, { FC, memo, useEffect, useMemo, useState } from 'react'; +import { UIMessage } from 'ai'; +import { AiIcon } from './AiIcon'; +import { Markdown } from './Markdown'; +import { + renderCartComposition, + renderContextRecommendationsComposition, + renderRelatedRecommendationsComposition, + renderUserRecommendationsComposition, +} from '../../server-actions/renderComposition'; +import { + getCartResultFromMessage, + getContextRecommendationsFromMessage, + getRecommendProductsFromMessage, + getRelatedProductsFromMessage, + getUniformScoresFromCookie, +} from '../../utils'; + +type AiMessageProps = { + status: 'submitted' | 'streaming' | 'ready' | 'error'; + message: UIMessage; + isLast: boolean; +}; + +export function useAiMessageUiComponent(message: UIMessage): React.ReactNode | null { + const [uiComponent, setUiComponent] = useState(null); + + const { products: recommendationProducts } = useMemo(() => getRecommendProductsFromMessage(message), [message]); + const { total: cartTotal } = useMemo(() => getCartResultFromMessage(message), [message]); + const { products: relatedProducts } = useMemo(() => getRelatedProductsFromMessage(message), [message]); + const { slugs: contextRecommendations } = useMemo(() => getContextRecommendationsFromMessage(message), [message]); + + useEffect(() => { + const run = async () => { + const component = await renderUserRecommendationsComposition({ + scoreCookie: getUniformScoresFromCookie(document.cookie), + }); + setUiComponent(component); + }; + + if (recommendationProducts?.length > 0 && !uiComponent) { + run(); + } + }, [uiComponent, recommendationProducts?.length]); + + useEffect(() => { + const run = async () => { + const component = await renderCartComposition(); + setUiComponent(component); + }; + + if (cartTotal > 0 && !uiComponent) { + run(); + } + }, [cartTotal, uiComponent]); + + useEffect(() => { + const run = async () => { + const component = await renderRelatedRecommendationsComposition(relatedProducts.map(({ slug }) => slug)); + setUiComponent(component); + }; + + if (relatedProducts?.length > 0 && !uiComponent) { + run(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [uiComponent, relatedProducts?.length]); + + useEffect(() => { + const run = async () => { + const component = await renderContextRecommendationsComposition(contextRecommendations); + setUiComponent(component); + }; + + if (contextRecommendations?.length > 0 && !uiComponent) { + run(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [uiComponent, contextRecommendations?.length]); + + return uiComponent; +} + +const AiMessageComponent: FC = ({ status, message }) => { + const messageUiComponent = useAiMessageUiComponent(message); + + return ( +
+ + + +
+
+

+ Shopping Assistant +

+
+ {message.content} +
+
+ {messageUiComponent && status === 'ready' &&
{messageUiComponent}
} +
+
+ ); +}; +AiMessageComponent.displayName = 'AiMessageComponent'; + +export const AiMessage = memo(AiMessageComponent, (prevProps, nextProps) => { + if (prevProps.message.content !== nextProps.message.content) return false; + if (prevProps.status !== 'ready' && nextProps.status === 'ready' && nextProps.isLast) return false; + return true; +}); diff --git a/apps/csk/src/modules/chat/components/ui/Chat.tsx b/apps/csk/src/modules/chat/components/ui/Chat.tsx new file mode 100644 index 000000000..01a2161e2 --- /dev/null +++ b/apps/csk/src/modules/chat/components/ui/Chat.tsx @@ -0,0 +1,210 @@ +'use client'; + +import { FC, useEffect, useRef, useState } from 'react'; +import { usePathname, useSearchParams } from 'next/navigation'; +import { IN_CONTEXT_EDITOR_QUERY_STRING_PARAM } from '@uniformdev/canvas'; +import { useQuirks, useScores, useUniformContext } from '@uniformdev/canvas-next-rsc-client'; +import { EnrichmentData } from '@uniformdev/context'; +import { Flex } from '@uniformdev/csk-components/components/ui'; +import { cn } from '@uniformdev/csk-components/utils/styling'; +import { Drawers } from '@/modules/chat/components/ui/Drawers'; +import { useCard } from '@/providers/CardProvider'; +import { useChat } from '@ai-sdk/react'; +import { Messages } from './Messages'; +import PresetsSection from './PresetsSection'; +import { SubmitButton } from './SubmitButton'; +import { Textarea } from './Textarea'; +import { AI_TOOL } from '../../constants'; +import { useScrollToBottom } from '../../hooks/useScrollToBottom'; +import { useChatProvider } from '../../providers/ChatProvider'; +import { CartResult, RelatedProductsResult } from '../../types'; +import { getRecommendProductsFromMessage, mergeEnrichments } from '../../utils'; + +const MAX_STEPS = 5; +const AUTO_PROMPT = 'Based on my interests, recommend me some products'; + +const Chat: FC = () => { + const { isAiDrawerOpen, setIsAiDrawerOpen, setIsChatActive, isPinned, setIsPinned, prompts } = useChatProvider(); + + const pathname = usePathname(); + const searchParams = useSearchParams(); + const { cartProducts, total } = useCard(); + const [startConversationIndex, setStartConversationIndex] = useState(-1); + const [containerRef, endRef, scrollToBottom, isAutoScrollEnabled] = useScrollToBottom(); + const scores = useScores(); + const quirks = useQuirks(); + const { context } = useUniformContext(); + + const textareaRef = useRef(null); + const prevScoresRef = useRef(scores); + + const isPreviewMode = searchParams.get(IN_CONTEXT_EDITOR_QUERY_STRING_PARAM) === 'true'; + + const { messages, input, handleInputChange, handleSubmit, append, status } = useChat({ + maxSteps: MAX_STEPS, + async onToolCall({ toolCall }) { + if (toolCall.toolName === AI_TOOL.SET_USER_INTERESTS) { + const { interests = [] } = toolCall.args as { interests: EnrichmentData[] }; + + console.info(`AI-Tool-${toolCall.toolName} - interests: ${JSON.stringify(interests, null, 2)}`); + const enrichments = mergeEnrichments(scores, interests); + await context?.forget(true); + await context?.update({ quirks, enrichments }); + + return JSON.stringify({ success: true, updatedInterests: enrichments }); + } else if (toolCall.toolName === AI_TOOL.GET_CART) { + const result: CartResult = { + products: cartProducts.map(({ slug, title, shortDescription }) => ({ + slug, + title, + shortDescription, + })), + total, + }; + console.info(`${toolCall.toolName}: ${JSON.stringify(result, null, 2)}`); + + return JSON.stringify(result); + } else if (toolCall.toolName === AI_TOOL.GET_RELATED_PRODUCTS) { + const relatedProducts = cartProducts.map(({ recommendations }) => recommendations).flat(); + + const result: RelatedProductsResult = { + products: relatedProducts.map(({ slug, title, shortDescription }) => ({ + slug, + title, + shortDescription, + })), + }; + console.info(`${toolCall.toolName}: ${JSON.stringify(result, null, 2)}`); + + return JSON.stringify(result); + } + }, + }); + + const showThinking = !['ready', 'error'].includes(status); + const isReadyToSubmit = ['ready', 'error'].includes(status) && !!input.trim(); + + useEffect(() => { + if (isAiDrawerOpen && textareaRef.current) { + textareaRef.current.focus(); + } + }, [isAiDrawerOpen]); + + useEffect(() => { + if (!isPinned && isAiDrawerOpen) setIsAiDrawerOpen(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pathname, setIsAiDrawerOpen]); + + useEffect(() => { + if (isAiDrawerOpen && isAutoScrollEnabled && messages.length) { + setTimeout(() => { + scrollToBottom(); + }, 0); + } + }, [isAiDrawerOpen, isAutoScrollEnabled, messages.length, scrollToBottom]); + + useEffect(() => { + if (startConversationIndex !== -1) return; + + const indexWithRecommendation = messages.findIndex(message => { + const { products } = getRecommendProductsFromMessage(message); + return products.length > 0; + }); + + if (indexWithRecommendation !== -1) { + setStartConversationIndex(indexWithRecommendation); + setIsChatActive(true); + } else { + setIsChatActive(false); + } + }, [messages, setIsChatActive, startConversationIndex]); + + useEffect(() => { + if (startConversationIndex !== -1 || isPreviewMode) { + return; + } + + if (JSON.stringify(prevScoresRef.current) !== JSON.stringify(scores)) { + prevScoresRef.current = scores; + + const hasNonZeroScore = scores && Object.values(scores).some(value => value !== 0); + + if (hasNonZeroScore) { + append({ + content: AUTO_PROMPT, + role: 'user', + }); + } + } + }, [scores, startConversationIndex, append, isPreviewMode]); + + const sendPresetPrompt = (prompt: string) => { + if (startConversationIndex === -1) { + setStartConversationIndex(messages.length); + } + + scrollToBottom(); + + append({ + content: prompt, + role: 'user', + }); + }; + + const handleSubmitWithScroll = () => { + if (startConversationIndex === -1) { + setStartConversationIndex(messages.length); + } + handleSubmit(); + scrollToBottom(); + }; + + if (isPreviewMode) return null; + + return ( + + +

JavaDrip Shopping Assistant ✨

+

Powered by Uniform Context

+ + + +
+
+ +