From ab8dd16d9e79d8a85925bb3e06600e9e1cddf37e Mon Sep 17 00:00:00 2001 From: Eugene Date: Thu, 5 Jun 2025 17:38:53 +0300 Subject: [PATCH] feat: ai-assistant --- apps/csk/.env.example | 8 +- apps/csk/README.md | 52 + .../0aafe2e9-468c-43e1-81f3-fda6c02388e7.yaml | 6 +- .../1697b6ae-aa6f-4bd6-b844-c8fb3c86216c.yaml | 6 +- .../4469d093-0095-43fe-a6f1-9ccd66da351c.yaml | 6 +- .../511356c8-8058-4312-9d7b-6d73218b67c1.yaml | 6 +- .../7770ab14-93dd-46bb-9ef6-7dab66fff401.yaml | 6 +- .../c13180f0-750f-4fc7-837e-46ade3070952.yaml | 6 +- .../e69c04dc-5c4f-4951-8102-08d9a8df8913.yaml | 6 +- apps/csk/content/component/aiAssistant.yaml | 20 + .../content/component/aiConfiguration.yaml | 20 + .../component/assistantScrollSection.yaml | 20 + apps/csk/content/component/header.yaml | 2 +- .../021762f3-8a06-41b4-a4fb-08a837c74415.yaml | 44 + .../19ea7368-29fc-4e61-b730-b78ee47e74de.yaml | 70 + .../6ac15a92-bef3-4cf1-9bff-a2bcb666b73d.yaml | 123 + .../9e2c59d1-8dd3-4a13-b5b8-70639cffd7bc.yaml | 48 +- .../5288b954-d654-40aa-b439-b01eab5982e9.yaml | 148 + .../5aa09de0-3fac-4ff5-bbbc-52ca41cb4880.yaml | 312 ++ .../759a8084-68e2-40b8-9615-45c7789aabd3.yaml | 179 ++ .../f0ad3155-f91e-480f-b7f0-c17ea55e74ee.yaml | 187 ++ apps/csk/content/contentType/promptItem.yaml | 27 + apps/csk/content/contentType/promptStore.yaml | 30 + .../content/contentType/starterPrompt.yaml | 21 + .../content/dataType/getEntriesBySlugs.yaml | 24 + apps/csk/content/dataType/promptStore.yaml | 16 + .../csk/content/dataType/promptStoreList.yaml | 28 + .../d413614a-ed47-41df-ae27-164e180edb32.yaml | 245 ++ ...FPdUV5NjF3dmxRYU93LWNvZmZlZV9haS5wbmc=.png | Bin 0 -> 101596 bytes ..._74ac5157-3a4a-44f4-a9e1-37bb25ba51b4.yaml | 8 + ..._309229b7-f5b2-405a-8694-c897db7998bd.yaml | 15 + ..._82c2b0bb-0b38-4bf9-940b-eebfeed78ae2.yaml | 14 + ..._6625324a-9372-4bce-b2a4-dc33924fb198.yaml | 9 + ..._357de59a-ef74-4258-a856-bac6cb3fba1e.yaml | 9 + apps/csk/drizzle.config.ts | 10 + apps/csk/package.json | 17 +- apps/csk/src/app/[[...path]]/page.tsx | 27 +- apps/csk/src/app/api/chat/route.ts | 1 + .../src/app/api/webhook/entry-event/route.ts | 60 + apps/csk/src/app/layout.tsx | 11 +- apps/csk/src/app/sitemap.ts | 12 +- .../custom-canvas/Recommendations.tsx | 18 + apps/csk/src/components/index.ts | 2 + .../canvas/AiAssistant/aiAssistant.tsx | 26 + .../components/canvas/AiAssistant/index.ts | 10 + .../AiConfiguration/aiConfiguration.tsx | 7 + .../canvas/AiConfiguration/index.ts | 10 + .../assistantSection.tsx | 13 + .../canvas/AssistantScrollSection/index.ts | 9 + .../modules/chat/components/canvas/index.ts | 10 + .../src/modules/chat/components/ui/AiIcon.tsx | 23 + .../modules/chat/components/ui/AiMessage.tsx | 112 + .../src/modules/chat/components/ui/Chat.tsx | 210 ++ .../modules/chat/components/ui/ChatButton.tsx | 71 + .../modules/chat/components/ui/CloseIcon.tsx | 18 + .../modules/chat/components/ui/Drawers.tsx | 140 + .../modules/chat/components/ui/Markdown.tsx | 136 + .../modules/chat/components/ui/Messages.tsx | 35 + .../chat/components/ui/PresetsSection.tsx | 69 + .../chat/components/ui/SubmitButton.tsx | 20 + .../modules/chat/components/ui/Textarea.tsx | 16 + .../modules/chat/components/ui/Tooltip.tsx | 24 + .../chat/components/ui/UserMessage.tsx | 35 + apps/csk/src/modules/chat/constants.ts | 44 + .../modules/chat/hooks/useScrollToBottom.ts | 58 + apps/csk/src/modules/chat/index.ts | 5 + .../modules/chat/providers/ChatProvider.tsx | 56 + .../src/modules/chat/rag/actions/resources.ts | 30 + apps/csk/src/modules/chat/rag/ai/embedding.ts | 57 + apps/csk/src/modules/chat/rag/db/index.ts | 5 + apps/csk/src/modules/chat/rag/db/migrate.ts | 32 + .../db/migrations/0000_yielding_bloodaxe.sql | 8 + .../rag/db/migrations/0001_lethal_domino.sql | 14 + .../rag/db/migrations/meta/0000_snapshot.json | 51 + .../rag/db/migrations/meta/0001_snapshot.json | 144 + .../chat/rag/db/migrations/meta/_journal.json | 20 + .../modules/chat/rag/db/schema/embeddings.ts | 18 + .../modules/chat/rag/db/schema/resources.ts | 32 + .../modules/chat/rag/scripts/loadResources.ts | 76 + apps/csk/src/modules/chat/route.ts | 92 + .../chat/server-actions/renderComposition.tsx | 62 + apps/csk/src/modules/chat/types.ts | 38 + apps/csk/src/modules/chat/utils/canvas.ts | 182 ++ apps/csk/src/modules/chat/utils/index.ts | 118 + apps/csk/src/modules/chat/utils/prompts.ts | 31 + .../src/modules/chat/utils/uniformClients.ts | 19 + apps/csk/src/styles/globals.css | 27 +- apps/csk/tailwind.config.ts | 1 + package-lock.json | 2645 +++++++++++++++-- 89 files changed, 6454 insertions(+), 284 deletions(-) create mode 100644 apps/csk/content/component/aiAssistant.yaml create mode 100644 apps/csk/content/component/aiConfiguration.yaml create mode 100644 apps/csk/content/component/assistantScrollSection.yaml create mode 100644 apps/csk/content/componentPattern/19ea7368-29fc-4e61-b730-b78ee47e74de.yaml create mode 100644 apps/csk/content/componentPattern/6ac15a92-bef3-4cf1-9bff-a2bcb666b73d.yaml create mode 100644 apps/csk/content/composition/5288b954-d654-40aa-b439-b01eab5982e9.yaml create mode 100644 apps/csk/content/composition/5aa09de0-3fac-4ff5-bbbc-52ca41cb4880.yaml create mode 100644 apps/csk/content/composition/759a8084-68e2-40b8-9615-45c7789aabd3.yaml create mode 100644 apps/csk/content/composition/f0ad3155-f91e-480f-b7f0-c17ea55e74ee.yaml create mode 100644 apps/csk/content/contentType/promptItem.yaml create mode 100644 apps/csk/content/contentType/promptStore.yaml create mode 100644 apps/csk/content/contentType/starterPrompt.yaml create mode 100644 apps/csk/content/dataType/getEntriesBySlugs.yaml create mode 100644 apps/csk/content/dataType/promptStore.yaml create mode 100644 apps/csk/content/dataType/promptStoreList.yaml create mode 100644 apps/csk/content/entry/d413614a-ed47-41df-ae27-164e180edb32.yaml create mode 100644 apps/csk/content/files/L3AvTk53WUtReHRSVlNZLUJhQ1A2N0prdy9uY1VDY0dGQlFPdUV5NjF3dmxRYU93LWNvZmZlZV9haS5wbmc=.png create mode 100644 apps/csk/content/projectMapNode/ai-tools_74ac5157-3a4a-44f4-a9e1-37bb25ba51b4.yaml create mode 100644 apps/csk/content/projectMapNode/cart-recommendations_309229b7-f5b2-405a-8694-c897db7998bd.yaml create mode 100644 apps/csk/content/projectMapNode/context-recommendations_82c2b0bb-0b38-4bf9-940b-eebfeed78ae2.yaml create mode 100644 apps/csk/content/projectMapNode/see-my-cart_6625324a-9372-4bce-b2a4-dc33924fb198.yaml create mode 100644 apps/csk/content/projectMapNode/user-recommendations_357de59a-ef74-4258-a856-bac6cb3fba1e.yaml create mode 100644 apps/csk/drizzle.config.ts create mode 100644 apps/csk/src/app/api/chat/route.ts create mode 100644 apps/csk/src/app/api/webhook/entry-event/route.ts create mode 100644 apps/csk/src/components/custom-canvas/Recommendations.tsx create mode 100644 apps/csk/src/modules/chat/components/canvas/AiAssistant/aiAssistant.tsx create mode 100644 apps/csk/src/modules/chat/components/canvas/AiAssistant/index.ts create mode 100644 apps/csk/src/modules/chat/components/canvas/AiConfiguration/aiConfiguration.tsx create mode 100644 apps/csk/src/modules/chat/components/canvas/AiConfiguration/index.ts create mode 100644 apps/csk/src/modules/chat/components/canvas/AssistantScrollSection/assistantSection.tsx create mode 100644 apps/csk/src/modules/chat/components/canvas/AssistantScrollSection/index.ts create mode 100644 apps/csk/src/modules/chat/components/canvas/index.ts create mode 100644 apps/csk/src/modules/chat/components/ui/AiIcon.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/AiMessage.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/Chat.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/ChatButton.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/CloseIcon.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/Drawers.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/Markdown.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/Messages.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/PresetsSection.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/SubmitButton.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/Textarea.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/Tooltip.tsx create mode 100644 apps/csk/src/modules/chat/components/ui/UserMessage.tsx create mode 100644 apps/csk/src/modules/chat/constants.ts create mode 100644 apps/csk/src/modules/chat/hooks/useScrollToBottom.ts create mode 100644 apps/csk/src/modules/chat/index.ts create mode 100644 apps/csk/src/modules/chat/providers/ChatProvider.tsx create mode 100644 apps/csk/src/modules/chat/rag/actions/resources.ts create mode 100644 apps/csk/src/modules/chat/rag/ai/embedding.ts create mode 100644 apps/csk/src/modules/chat/rag/db/index.ts create mode 100644 apps/csk/src/modules/chat/rag/db/migrate.ts create mode 100644 apps/csk/src/modules/chat/rag/db/migrations/0000_yielding_bloodaxe.sql create mode 100644 apps/csk/src/modules/chat/rag/db/migrations/0001_lethal_domino.sql create mode 100644 apps/csk/src/modules/chat/rag/db/migrations/meta/0000_snapshot.json create mode 100644 apps/csk/src/modules/chat/rag/db/migrations/meta/0001_snapshot.json create mode 100644 apps/csk/src/modules/chat/rag/db/migrations/meta/_journal.json create mode 100644 apps/csk/src/modules/chat/rag/db/schema/embeddings.ts create mode 100644 apps/csk/src/modules/chat/rag/db/schema/resources.ts create mode 100644 apps/csk/src/modules/chat/rag/scripts/loadResources.ts create mode 100644 apps/csk/src/modules/chat/route.ts create mode 100644 apps/csk/src/modules/chat/server-actions/renderComposition.tsx create mode 100644 apps/csk/src/modules/chat/types.ts create mode 100644 apps/csk/src/modules/chat/utils/canvas.ts create mode 100644 apps/csk/src/modules/chat/utils/index.ts create mode 100644 apps/csk/src/modules/chat/utils/prompts.ts create mode 100644 apps/csk/src/modules/chat/utils/uniformClients.ts 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 0000000000000000000000000000000000000000..f8f76bdb9ac782b58d41a9a73cd80dde0fe22026 GIT binary patch literal 101596 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
!|3FoJ zhd@A~yMyd#TT}d8(7PNv!Rkb|=UUZdnHn*C5t5PEU<*N@eA^fsU6+cMqI#jFx0>Y# z1E#}e_)N;%^w7&^7E#+`5~|BjxH(-^<9TOzuTs0Y->i|Qjs(2Cd^EfT1ZMa5swj-`H;i^jFo7(v`<3ZlzvfxwQSJ)48Gz}l zR(@{|B6)FsUh65Z*(oHllcSD4$TB8pdQ0|ZFxQsXV(Du7Dw}Vi`k=&jkbP8@1 z^-CviYGae;mwWSyTK^*VvVKne9gG`%UGDU(nM+<+A~K5%%0}@1TL+5{0>d-!Bu;6O z?QUR|NQ)gMiPh}~kgNL2bql7s_>+n3no6$u@i1qqU;@TZmmzVj!E)|htpz1r&g3Sw zPIh0qe&x+RI6ijWFgJi0=Yh12{tL*nV-l8CP&`P3|BLya|E8pNxng_y*OuahFWWxKN<}vi<5T3#@jHz z3qy#NWQ`c9UAz15PomIcQ_1oIJ(y&36A_YzW0y@~`ivG-%v&!EkQ%=xz zu@qL3D2|l=oBXv9eVG28h5;tymy_1}7MyQ2`iX?ljJok%Er-0Py7mz7kCFA@2Gby{ z%vfxWKqAxrwvb=Aw0zvK+a<I%cSnPD_X>nF+t^I%&4?v;N`Gww86EF||qr3t$%D?j#GRqD-iYA2zt|3qqaO zpSF$#k_@L*A~nvOW<)}HCZ-_|+V4(@7R`At{^!jc@~Nd#K*jixejK@2U5QZDZLv1y~U zprI|mij>SE7PRua_^#u{Xa)uW$X$JK-o)<@Be-`z0;uT`x)S1WJD&x%hlMd8hsH&5G8cu*$P+q?(z`p;*{MfhGlD zeyR=#$Q8bTBMwb%2EaIbYsU zrf9N(DyT>GNT(Vg`E5K;=p!aqgP~YpkLvi{b=6ZOhtaA^&kf_K{nGVaD$Fw|SetYt zQRkv{NHypS#D#Nz%r9d$a&OQW|DMn{W)99)&VpM?CCgqCPCye}1U|_GX`z6aX~vJyr}DQzaR$r3X>pEoc{3 zrMvwY7oP8{?6MOM&G59k42s#UfXFKZPdU&l^kXG+VO!47?GZLC$y`dCt_r6tv0-e66R|p0Za$M5=GTQHycf?PT}q(aI_pno{ri6^>#1Tk zDikeQ>Gzp>w1qtYEmCy4%!%DJjQ$ER^``itNW)JRSFazrZGP^&M(WumN;SyP{d3d2=lUyk2?x>oeFw zgLNFxU-CDc0df+Yq&hF1a8U#U7hA%-)I1$F4Mps>Gu%Zed6;Ya;0}pZ@qYSeh9Q19 zYB$Z^qhyDdm#5w^R!pX&LSvv_V@q2* zr`7aB-7gY%=A;UEA_p`}Yje0Z@cxNH88^rY7^+^{{ol)(d%Ug|n&>PrwKTw3Nn(Mh z01xYZQZAIn%`VvL`xPB*4>ctw3RR1T2T+2YF`dqPcC(J=t6 z>BdvpVj$JJ7>Db_hQ~Qh+DyGyEbqjt%9h9c@x*g`!ACbq2+0@XYP&xcPtH$DceX6Tu|u%9hjWZa2RvZ{+F^W7?(z>+ zxnysj3B&8vEm^m~9LWGEF>H2|0*gOK(cU}n^3YlI0ZD+ka}3abadFb=J~I ze`OeT_Oj74KiI*APJyKU*z!%Gge zP@O@M3FKWEN0GTV*svUlk~#u%*jehA^H~}b9FU$}>}lnDg$&O;;%lxY{=}mmGIh(4 zm$q z$b<t%KzQtW#(=3#`DqgWgD5%*-*&2d|c`|@s~ zMh5yj=$iA~{4ywzxc>z%K+(T|jOIlk0{X+`uPP;o)OR{bKZI%1gNAZLxTbc}oJV+M z+4oNCkm4s7=YiY5t`>PI6kJ@!r3~l{%91aUF^&`E z-57=WFdTu%{bpX^YdsX`o1unloX9zL6TY9?Ly4sT1ont$EOd)lS62FGye8#i6bTpF zH=V09ESlN5zKVd*%$lkWMjb18dMZthc|OY*RXVtfaIn{u5Q-H8j%)&f*P#(?=(zEt zvFNt|0?da!D(?Ljl>A)@$?ZH*U5P^SpLmJ@9LT%C9+bdKu8lua3a?W5AI0Ro5uq^cz)7x&C z|2tDwQT5(leE2F*-sm?4C*!urcfBUT-`v)a+G;fQMD{EdlEAA*`(OM%S^Z?@mScNr z#Wj{$zt$dw<&qO~yTD{i;&-NP_P+$`ZYKUL7MQjs2aO#a6uv0G@dRAJC7~wy01LuvCDXP3?${ zDm2-v`rpx=H{CCQBwvYa4i{?y@&gZImWSc_ahc58kGO8=O%P;C-nVF3ZqxmUh{stU zD^kuqtz~YcB~rI|P zi*1yaw86;XjSRIYl+{`e4oZ^!6kI;``Bvd2vNPzpCeOZ zIpc%l8+Fuf1Z9lARYiKV|7noBbH0tar&84FshHAHimsW!S&z>d*J>TAI_{IfCBi3FTvBeD0%V<8$-zx3^!9;DOZ1m!Z00S7~p6}PJSFV+0 z1{h9;p`*EzI%X+yZmP7qD|b24$y=`cj&Y9KajMKFp9rH}vB6zu@h*xQ$3` z=94=J~SEc!}^rC=Ur`%F!QZzG1I~c=J4# z7mC9HX{P>;7x;zc?oGy3SaL1Y@0DbDfJ6Ny``U>XRJCmKi!y8cp!b#%*yBSqY!6Bu zX{EtzmqjtYHz}b>V{Z4l-PE|NZ)P@WT6r}I!K&@@rw`Bw1uhy=-s&t7{B@V{YTu`o zB?ol%*`-D0ILI+baqZXB`go-8uILk+F-+%Z6^ZYfY8Qju&MC=qEZ2L+a#>f}&vzQ2 zI7lHxxEV~$S2!^4+*3xmCL);-TjE#rc2!_P7Lx`ZRJ;M#Gq}tg*p8|$0T-l6!5cs7jES z?%5E|Ui}VXOw~ozyi_`6kU?!qe95+(=?wRXiEE@yJiwk*7<2#t01iJO)jtM%Tyz7I zE3TQG+Us@03*X0Pwwz#Ox#5F*ZaJ-*S&ak_rTe67+Q-fN&Q~HLE^xH=k{MCd*8QfL z&ohK6iXR0{fzt{Qzs#FMWYNbmH4^*S0|WFDPhBig8&dr9<;TTGfRu=N#nx#YfUQ0H zJ(1o`RQQ`CZu@#%e~KObk#0%gX&!?L_zwpR|$+DUH`( zi1!paZXVK?cmQJTu`z*O%G1H{t^b^6C4wenc_Qw;&_*OX8%f$nXAg+V(KU1cVcrSh zeSZAosH=XMhtE;T?+&~X*q5}4jsI+aB2@&FcNkOq-h~R~b;my#U`xjb#Pea2y?iqx zxyIQ{#4}3YcGPX@{l;qcNRr7P48b4i|+lUOnm=fnonriJhg25)&x*7Kq!k8Kakw`p8P^oqWV9F__=Hl zzC+7owU5{&^(}ym_3O{LmP&v3bb|G`PzzrcR$gzVB%!S|hWkO6z~g|cvoQ>l$raUc{vbyR*bQESjLsVwCi_9Xd7OnS|P@0a7oHZOkA~m~CK4W2ENh zBBV2NYEVDwTzkvGui|lVzWrJYJ{#3Mxvy)upEev(8Ma9otxvj1b8-9QL$_Oge->u< zR73Y$a`bI3F@4>VWT6dqWs4sLdfS_IAL6Kea1XYv1!hrkiwg_PFpeXOM!^kPs*v21 zc|iHTC(nkz`-cz%lqC^bjlt^fz++z;{y<7+MiA(MB zHO0)YKFEk_h5D}*3#XVfxPY%eZ|jh;EV(kQQU}wkPcEK4G+T=V+u^0vrReH#GYeX= zdbR>((qN}EFkRJnyYP*Bq`(g%XW50RrhQ_!5$=9>eK2^i?vh*Fr`J408b1SSQp7wL z8FmUlAVLgo;)b^l77hPAVb($PdpQRa&=|1mPcV>59LZ<1R+I37OO7Qjt!C+@iuV@( za0Wg!Rwx}Kp$T<`CK`TGmiN5u@9F_gL&}mc_1>OwSIXE<1B#JckS?sksW5JRV+5$Y zMh~8-kK@EWLTp$xr<9G6MAdGTMwRThq5?|A4nu$xB*2g@0=j|@U(JK2t9cp;^Ew>J zSKXhWO1vW8&;(vYUZ~}hP1Pt)BRgyHzE#)a7_Qe*D0`*$*}PMI(nq;@`Gs!J8RUHj z5^#RRuKIq;^+rTk*B6huqEbtoV*#cuI0dZpH1Gh(^Sf4Rejt4hAsa3@huza?#y~SlG1s<*dEFZ_yL_Q#6^>%Lj^QWUCnN zX(Q=-H2has7UBm%yG0hHw)?DHum0dgPh;^E-L>dqCa)HY1WOGCIq98TpDjGH3fm?r zGJk=9N4|&QL$T4N%Hj%s-kLo-hO*IKrpHKMuvC6V8myI>#R|3}%{V&wNS{s=hzTI2 zWx`B?;sq(93*HMVOK1vGNw{3GD{B^v$6*J8o`$SxhL19CS9JuPpY(9JK6#UbD;rSH zIcQJjgPUd?1P@cZ#s0@H2tso(mO17UMLtk%YP z=pXXsof#O{@Llvuzx2d~`ifi3w6m10khlYR-n>c@ChSubJ@|(*#4ig`QsgKMd&)34 z-3gvpX0oM`;$e6~7agE!1is8{;A@m5;g$U1`|b}&DIkqa5oG@ z%cpQH%7iq4*C*ikNi1#6o(X%aZLE4TRxexs$d`0ks#J(%6834R@j|E}*}{(f16BU9 zclv}>e|V$znl}>5u^-2)sGX;-zaET=eP#ge;};Cz43_UrC)Rs%gmQR^x6MeGG81iufOJeIgOm1<4!9 z&%q;FhHGB$1?&i5Fro-q(5ULtK?US)b?L&dU|JXPPJrY1QhTcK4$LpfruCm0^t?i2 zNM(0huQVdQcVLLFmqOnqwyML9g`uZ_)pJO>e1p1N9vA>K0*1kWdjAv2(d`Y6N8>-( zUDg-wfqxWo@9@}S)rRCQ(wYg+3yl4rw+)PNB|RY*MVhu(h! z!f(^>D%kcgk=Q}`20N>$dVVn;faU5~B0pP6oH!{>T<2(?YIJ&xFmcBb#YKj!P*|tL zk@I{5IoYmEEWI7x595Q>__mLJ)WCm?85;JWJj;vshA;SzOMc7LG-De)YR-P+j-i(P zR{%x9UJF+ijJdt(^*uxPYd~6nDZ<=kFYbH}+4vaGeX-4^$x6@O7!>0^$W<-}p_2HI zm#E%Kt4t+g%>Nq(J*7s`6Gicb*`NFfMYYM_#@Z6ne8%ZNgX4Q_x-K~5!I=BrblJ zFkb?%z#Lc7zgx7Ug|PMlcILsyWO+S%=M@t7H0 z^=X|RS7#W;PLP53QK$Ts^OG?v<_dwn6`W2PEqJ5Q?2Yn(T5BoyLT;jP>e-l`C0P(` z=rcEmH22^4)CM0ie4&h)MkGJ4q{ctt*lO(V$8hoX*Bm-g3A-pRE#8+kg6|dw_;z5$ z^&*cZkJH8YeiD%!9l09M91b%I6igM7edD5I?)K*&+?#W@TW)Jj5A_D`gVe1pWZLeJ zAlX){YCfvYX8|XR-P;~;GwASxpKSbG<$TorE?2AxtUQ+Njpc*w%6)2A&XQ9vS5J>3cmq}F(0G0Tx0g+qu()2b4_ry<~x zBllytGvEfmQJzA9Eib9urcC9mM16Q^Rx>jk_3I?M;9239S##5CLHv@ilRz$WZH~BP zH6EqRm~?o)ZS($feXZ|((b`bS$k_^X8@Y9fd(Fm`b`#bq$Gw`gH-FOgnkqAa-DjKy zlMl6f2#q8sg;q zre1(~Vxexx_{9`@ZJ+7}ND*@-E;=?|X5|X+$9?SQ&70dns3@z2VLmUASR6@ox^((> z@h<5pUL63NwRsV+kYwt5w@6wyN_-+I!puFP6#=`1FAXFt(xj^0l7nQ2p-t-5<%V>v zMaWuY^Cki2TTyT5%17iuL`v|BIAfgflV6+R&3(;DA8ghg=2-|44}|i}8T<{w$HUn3 zSHG1CtD5&smtSR6p7I3Z&kRa9T+Fd$JR;|bA9wG`kgFA7+Bj6Ep+?^hlew#{JFI|tX=D7vw%^e1sav9 zAVS!sqjcAKtjE6N-ljUA5QTNP%=!!L+^D*cewG4kuEYZFr#@7cMEg_!@(W}(L({tO zm@H;!n^h@&pr)|Mfq_mLJLiF7VFrp5Q0sfzL5E2W^ zK?M_vzZ^O^7Cf$8)j~?=MCz366Qak~d}JWV0Y{^Wc3(qj@P=o4X=&q+CG$04)Fk^Gv}`knFShaAI3?EyT;DSGGISDufQJ#Ui|fyR zrwba$?HLeH;r10F$wa3P*Zp1}vou1*X6(#DPNdZT;$j`Ox%`-@gF(EvEtSCjnwwiP zsVcO!17N4ViZXacQrOREA!lh+g;oxt?j zc;_XD~QD(^7k!e{+nP!3?l=BHh7}7sXz>n_0 z$xQ7)j!P*<7{G>dU*Tjom{hON{h73H^>qJZwD#z)>r4Oe$3rV+U(Mz9id)ZeomS_C z$`S4^CQ5i(VQ)eLN&8>*I_JAQ=Ar&sG4QKS&VzUjC?Aw&(J}PMtk&T9*@$?`@F?=C zJ{)v)`8pHOF3v6aIC{88lCg{=*4|C(yf^q#2VRCzk+W@^4Z;vuuAhxQVN!TkA?J4~ zl+jF9alid1d>@Y9gApaw8NZZvTVuF%cZMB&)dm=d2LSllzk5ip)fb5F*T~m>kokR*&DKJ@-(}6KEiaXeDSe6C;XUzR(Nt8 z0F{^bAr6Zs_;GBDj_T#0JoYVGa=KLZf^{8^h_ zg2<|(8pV0fy@Pc7bvY}y)6Dv}Hy3n?$e*g@QWpCif+?@z>{l%!P{fXnq-_dD2s-8B zD54%C%wVsCcB$9XKa)E#-LsRoS6IYC`$@7*NiIm)HyhL=Nsd_}MpX|{oU;S0{_o6Z zM2z}Wrj_nmgT;c195S#@?O#i!|DR8;idEc+DR`2H7#DS7i|<93cZBh<yH=fqpM z-j^8ynD2e1uU`oN`%UOHtn93($excfJVaDcLX1PVv5OCpWD zNFzWT*&$Ygf@mAc?#?MgCqFL1M=8BEIXZ~MLYnDk6|FLO6ulp`Nbr(sU=1=U^XYG1 zsd3-RAJ2!V7~6!Q5@_&P%==eN+*?d0SuP*05cK~e?K2~@4(oC-yvRo3}g z^SwF&W(wcS^q{Ikm(Uflv_lGFKnF&*FH(~ZwuR?$`^z5#wvC07@fWE9PIOA2pF|&!*B|2`yB1Ta~2fdIDhPq#sRmb3_49l zgRs<#xO{01vF^syy*fueDDP0d;akIl8z(fy)9(#~g5ZV0JWs|})Ob}$#1IMnOjs)R zdYG1=o!PUV64q)RCgj81r8N-pjUD-va)fm{v;Z`Yh_cjy8sJ~rC2r_|-Rv<8l|+K< z$S!-5^IY^hgfvQy;j(E`P)Jq43`U&8;WAEzD zTcP{?G0PZppJfqP5P}(Is%JJ!yh@5y*dyNRJ3Q{qZ0TsWT1d#lj3{#qqXnSc8U7Lz zU?aV*nZw@NVmh-x$NmPL2moWt3)Z)PE{7oRr%dFt9T?|TOX}skCODhgu5?-1W!m4v zi+i6a5dmm9`MD>KgEY4sDC#aRT_beqkDjW0 z@B;U!sWf$a3nm7D*nA5XhM`bc5K=1#q{YZ5$t^}dLgx+><=p^XW713cyR>ADu31stKo|vq6AJ-{|nWegi9vIYco%;ff zYSQ6rzrkUC)4#X}+Y)BUnIFDcua+$){%wJpCW~EHq^SU`nu0}13ikp+QI z$!LDy#rI$TkQfX}Yl@y08QJJ~j=C(<%&f+W0X)56Nj@#?gH+ArxGl}ax5R0uVB^L^ zz6+sAkxL!^nzys-NgHUkQv&A>w&|co6++yF0-k0#L*5_7Q8wRc9JKr@QQ8vFW7QE% zba=m(TQy8z?Bwk#B!=Ar?{<2y>eWnB3pauih!mNoNH>vAvxHqO7&%Pe07{tM@S|Bi z3e~5Qvd_rhxD@41tQE<8y?3~%NT0~R{v1dS1Tcx>6*@gz{bz(yC6&+exV>YL?S<6! zOvgkqPl9I?NMp=F?ky^CG1e&JA7H?@RU5|Pm=x>%bafo$2hX5qs3AuYvqIie>kkY} zP|@_rJmf>QTsN~zSuPhf=M8&RcI9wgU@x=wf4_*~evuKk3q~5XBAQl=<1RH&0CEMb zfkr;a@7}&gL$4W@eI?kMxm;smEp}>Bg`5CV4$Vyy@9x*W>K9j$r$#?Mkk}fqYQSfr z=)&CiqTZ@Q)F(yngdKHI8rmu@*wbER^hNr1v$9gX$=dUA86o3n6;X51?+w7A!W#Af z%gqhEdv{OyebR$Dg?XTfFy!zmHDB5G4N&;^7?FdxFvR~QY^Ps>w z{wZ3lljo6VK*T5tF1Uk=+UW~KwKY{V1YbB)=T3H6;5S16u|Z!f-%TEg8RhPnXkX{< zUkT-tx;kt)2!N zbI*!)(h7^KMze<&b-SyRpY+(1Uzh4}xORw;Y(|#dKJ}lR9GA?(4dQ=CpdIX%)|`OF zz(a=-I7B;HN11#=8hNhx1(!wG=q2JF;~{T1nf>HqBXpC}>OVVM-WvBSSCC-x#b;kQV(^2a*a&tj7X&x)_GhVtmrP; z`g3cQz@iM?9W%oIh~()V*LWZV-2?&$>Jb@OXx7WZD^1 zdR7A+?D7_&Wm4HVNTq4jDNpsYo6S) zG>2APL_bpC&xq(TU9^i+@7usE-*mgpcZs@NCLJXr+^sw)g4{1$9k(SP}a;Uwk{`}Tsrc`vbiCNH*^HTJ`D@@a6pk?E& zfaCz1Bkc+m8N}H5N6~|3DN2Z`9zKhD(X!MxsKTfIsru%r%T0;HA>dY$2hY|KR^^R7 zWKC;2re|qO^NWEiaT#wvHJHL}OOP5p%8BASAK{@!y|6(S2XvL)B!1e^>?ZdnH&Og< zjhkVa=C?Zpg=)}8bO+E(e4@)Uaq;9D3BHDZXq2vz8EH$*#XUOj(?mLZ{L4|gzw8L( zjs_TrVJOylevg&Otpsiq^*v=QSRtl+dfaV9`Xa*Xho8*Xnu_2!Z@ZsNh9@@VN4$F? z>!2E-v1XdJ2FpdrB?2i(UR~?ztSVd-u9;6zX6nK*^|mi1P^zbqUxlDCr0fL;q~X)2 z9)(&g<7ng@ylaWJVDO|KMP@~X{)SfP{RK4m?C6Q~q)<})#kI%f zE5el9cy4Al%xnFq`6;<5fDfm>@}^8Bo3qdel@a9{Qd=edG*!n%IDrpY3jw~eXEi+= z3U5pztiKdq4?E77mg2!a(%Qa9!O#UJ3{dEHqfDgy@Esp*ciZ)W`{4#tSH9Gm03~s7 z)2KNggyA9;xMeI~C<%}ycuZFtWve$2rt~1pGfaE@KD|oj^;48chIp|FVIaZT$dyff zzwJ*cix$->o-K)LUuI89LCk}bn!Pv`h0}BSiY#czD{24fqo5|v?F7YqZbTR0>JQ6V zU7M3|6^m>inK6K*zg765fz8W8S#IYWTJ3pYD>8OtGN`~-zdfThH6elcN0cwgf@40; z$C8Fqg>r~$<}2?Z$FIC%zoC8xbI5 z2g|g(RzFdS(wT`GM+9^?Oy+3>sO7` zzgaLyvuAZPtEL&#_G6daVP}3W345nDQ3i(>4@(!vy^?2r*f_eM7Ur+P`4`B{UU43Y z5M-M5IanZ2)&*@3KdpRTv;xJVIa-_hFk<~&do41(fK-jNbE2}wb}FMKK@+p`Y7)9d|Xfruh!omeNz@gzLzprVyjte1v6 zpi?Wr>By{22B4H`J+Pm2UIJwy^bq4Yol#yv)CvtBCGZXiAVeXZyF-!jk215xxFMX^ zT@gZS_iBf@vIzWIS+4J8RY@uKg_%btj+@`OJczZt;$lv37$NiG(RIMqP_c+bgwKoM z%sc_2#+TIM7Iz#h4)tCzaPc$~mZrD{_xhr zP!N&h3PfR9t`x0!i~B7Q{4%=E`te6T$79FKtnzgKzz~3hcHVR>f5+ zLjXL0n32?HdH&0}*4GJ;Go23f*LEz78V|JQA zg&Q$2o|UWrQQZRS7G;j}S;GLtW6(rxkkk2lAM*ZvrfL{uNrM}*Qe%?NBJJ=l7qZ^> z9yS9|`#NaJK$xMrGgVLEy5~L<{nW?Dpx_4k15P(;d~TfdoA%Jlr@6)tfJg`)vaaGL zHK1BVoPf&Rv>Nka_8Jd?sWcFR2pBXeJK)*blo)cdz68B$!KuZ-mZBdUZ1=mP_lzDb z8_jTnKQo!LYq1IodZY`=Ko_O?{$=5VaL|XZoSL=b?@tcINxZ-UHO!3nW%(G3i&5WV zq!ozea!3mh`hk@UVYiFgebNwZr+dx?jx94?4XKK0eYwESsYHFiHWwU5gBzsG>QLwI z18<2$q4d~z@;7ox;|SYqx@J{Tgoo$-azN{%!FXrm_br=#>qS8g24U{`GrCVFLKzXg zQK>8GgJx1~TTNi7WF+@K%>DXaIqLJ~*MHY~HIvx%?h&A7R8ZA3E+0y=s#{m{4fUeV zLk)y>{KB8`zl{oX@3c3;cJ=L|z}X^18(#m>mGZON_E<^H1PHS|*Fxy(`Mc_9$K&kP zs{`8>8uQ#2-qP`*>jtW zQ6f{ef|JbBpR;d$<&48N|IB)u-2plj{i2AQ4Y-i7^>7_1aZTy~}&< zAw~tAvR?>PgEuB2Jd?lFE0G*f5-J^U9|KN`+v|pw4Sp|qC2LANtB+CYrDmpG{xLAH zGh@+q`#}%%3h?l>n93VqcT8ih6_$?$F3)!qGCK}BQqQ<~v{t0^@lR346XQ6(I%jq( zmW>m|;PU&uV{kxk&nON=(bV7$?TW0nJ)xne#cqndxLo=cHE(UUCs}{;8Nk~~qfBPI zir28iPC{Uy1Qqofc^f}VFoA`SPAA;$xjY?X6SCeM^L7u7wT6$sqW*wae5Oy;aeIIm z2#h;Ca{@yCp4``z;Z3|LBg z0MAqVK+$GC&6sbqh^F$(VkSTYdC&P-qI;w-Q;kv5GRM!ogKEvI zzzJv4Dm9$0&FnC6sZFfMp@H(}z(2=9b||jBoNt;}?Md7oo~o`7>xl51w3S2X0@r(@ zBt=g7BBCjgTiC~(%cNHl^dz!Vt}lCysd9e1MiMnuSL&5VR^)yF(AxioD)QFiwpdG_ zoi;`a?LL0#D}e%5mH{mBN0JXP+=NKz!CcU#tCGA1HjQi0W107II5~nilD7q_KIj-} zy_53wBE7F@fb8sd+vqzvzo+I^05X!gGTOK#@u&zIe`jFK`Px0cYJT_92HM~2$DKWWpHzXxZ)sRlE`;vG@>I4>DtmMc(m}q&wPX>A*aFf^;p`9;Iny;;=<@kGS@(0D{K<5Ri1DclCh9G<^}(_+D}mPT`ne zJ}w?kD&{kFX)>z|?33Z19esTQ&PJ2;l04qMMZYR`an-^|1)3_-gnrrO1hlw)pVqB^ zH48%He0tE*yCCf;^$?Hge)?dFvPAXp$!kf*Lln3^+5?ogE5T+NWv-y*PqS%IPViqp$7AJdGHFk92d0)nc)0Di!lc&9&jDFM| zvX-ok;!U5Y2h@D*;^IlA0qTDR&crimf`VEJ0;7p%+gJ|?4u}5atXoe+N^RWd3H29S zpSKhM1e=Rm5|)9i6QuSVtZeJQZq4~8aH<_}C#lett^mb-9Jf*HO=Fg*0v@lsdwhs! zZw(5Rv14p*-_4g2iC~5_Z&J>oSM#mTMR%%bH(GanX`^3NYpWKZJI5sm0(hG#+6L8E z9`*cM4iZ66@h_Z7{{-zMI6RX!pq7%q;fK<%I2=??6=u$(&g1ViNKu_JYP_DitO_&=E*O|c%js2oujJ3i?lHz6T|nY^*CPUMd+PJY z%9a<@_7HFbXQV|woX(@<+8@5EX9Y=qGhVeH;E0Tr_cI^bXfeLSDPPLn7Tze55p)Kx z3~W{Dm*0UH;x?W(wvS+Hr z(hyN3+-b-Pda0hQ#sjk=oD!pG7^9DX{whWjc0L}4fCW4uVHy^^U@^4mhK`j~_HJLE z#F=W`jZmCbEMT1{wfG(}VnnM(PwmzvOpv}7tUZJEDY6|pmDDmW9fkXYG5rEQQj}{L zt}@s$00q%@I1VOm`z7bo5g5TpWtK-}VHDlr>XKBLqLiSz+*mceuDB;rF47 zS-Mc2JTXKa?)Ogzu+sId8Ti&-(K_O*?$fvcP&tinN^p`3F>F%=w#1Y{d_D_RWWKH= z$q1o;kbbG)Z)ujmC9!3uQoo#2bv&;(agd2#lgc{o!U%pS&WW{pigC!w^;Rl4Nc@@X z=#m+J-3O9A)^$mR8PUow9(0u4y6EIA8|~rYoHnDO&L8SxzQ=@h!UV#S5&TzAsF9J5 zm_T_HQTiN}8M#G5O{S^xNI|WOyqUPIz^dtCpwhr!y(6?Zlh6E(mjsE(0z7|{M3f+C zmjJg}w*QCW`QjkCC}>6$eVR7D_9Tc_Lch6IE5*NMrWFO;;XhpLUmj}1vx(#d&Hdbm z;40enNqI`d2}gwHmM*TL!aD}%3H~w>$;WIg=9L4G zG-BLH*Bm*7WZj8vNRc4`ecwgnbq|7uej(SJqD+KpkBUquov1g^RlA@0XH#}o;E_8v zUR{X21YJDWO=*bsf>KM|U9|t~h7~+=@UwAK5N-4s((E+PjkuhD6Hlu14>KXnLrXXx z8-*phIVmM>J5~24XN25m? zhk%AkqeFn*)Q-PxPA{YfBu)|3FSfXWR;Br=a1=I-iWZA#9E7KY?<^0yre-)DYWSk$ zECh*LUelL$knKRDw{;fgJhCz$!9rl8sSAg%juGoS;gW?{Me9SdnQ#~+YfI;l7dz9j*jX4l9GCNR)gJb#>m-y5NBC_#P>7*$18)_XI18jBO0?^>}PGMGeox%m;)A)?w8frFh=U_PQ z)Hhe2jO%yDzp*wjZ^w5tQHmgtg5|=M&mc@1HpzQ8w^q_k_TV8riEb~XrP8^WI`Dk$ zi05I2J=k+&3MlgjK|P%BWI<^Q2jw1KF05t(Rl*Wl>!<;ia> z#|gE9xShNu^>21|Fxt0Nw0wHu4>snoq!9FtszU-Xr0IMbn*f9Ad#3zTU zTuw+Xda#3&=(XX>z*n2**Sn}K&#fBzMXclRD-Fw&pQo3<7KR)qkcD)%El1ya=h+mK)P`$d(@#bCg@3jFL9eh%Y8h zhv&26D=?D@&IGM+Q1y#Tuk~qJm_1ui@LP{^kr`Dc4zd%!8ICF4iV=JyTh0sydRu8Y z#)NMZ*br1&N-MGio{xPRlmJn^$KQ5YN{QBMPz0qqGD+#`Q68U)6CKblDib0&;hXZJ zC{er-X5!hQr>=lPrfHhhHbOfg1r3NO`$L_*fT%vSxDqghj=S;+vP0Gy3EN`Aij>&; zSypq{yku@r46>VD@kgo*BKR0%WQHd<7T{zYRTJ-rHw-J_aE4cl z|Gc~wm)6RECmH$`xezr`Na`+o;ZVzKES#CEA$mq^4)LN`?{l9W{!5GSis?xMspXvHq^oT5GAH|0@^6N#&LlHM-zKU< zO`Ifo`r*T8illW3vXU}G>2Nlb_G)`i{HfJaOJ<0+9t%2U}qdR89n zcL0|SCeH@r$#2cp_={Oahb5WhOr~8k$pX(JJtZ-5wa;0G(8Q932P;wo7JpNdViEuC zbLc)zDGvtqz}3SQng#BJAWIkp^!H+{ZTQQjj56snba;tjl^Rc7_6VOvz7>?g=W2Co z4HrdI(;1N}q0z2!Tgx#Ai&9`V@Bb?<=5DV)Bmb(d&|QLXYYt1$pUaT_tJ~)JEXZ;2 zhA)RAll0*6gr!iI2l0afPzHSRuM+e}T1J#=)%k?sLwj>1rX)CjL#8->UfZ5Xgu;Iu z;v~iD3^DRvLPN`9#+)MMZv&+-3(Uby#p;J=Lp>2w?S1&&R^}JCLjDo%NWOs_G3^@} zHA*OO;bf^+nodx$3u<%VSdxg{bOd1?{jUqA@XxKLouj*yjO8>q{GcORvH$s*!Zbq< z&#;sW=&CuPyWVEg1&++OrSZn+fCp+IvU5>$4@}I;k z*D8b9ZP~ENh{INAe#hS!Y*O!OLF%(l359uCC7v#Dxz7RruCD<34t+=r3}!8&6k?PN z9z#~n(XO8@Kyt!L%yhD>{Ts+_AY!N>PXlkm=oud|c5iF97sxbPx#6;1H`r$UA!8|O zVb86-aB@Rx=S2&@L{5L{Y|Z7ldlbi#?4v>?Z5g(jk<*z~iS5nu&?lOy-K8&MZA8I( zTFUt!P^9p#GzzAuWr*+*pLCR9atIyq9~I7|=!?H;q|ye~dyGQo0+3Cc)RG{(XS_vz z#)Aa}0S9`f&^$gJ@#&WzA-hDTas6w1y|#C>dojP3%#p^}=xcD$=1e0aI^#byF-7N> zajrnq6F4HL6{ z`#X|f1WsVOfyGY6&kBq|*dn7|j<1%8k>~}_x=YJX{ts5Pm16MxSYtF6C0*`uZ|&`T z6_@UA7?QWr1Y?hZ6Wr=Do0hiN$lrRHvqJfH=%ed{nOH8HoM1$kqq6A;qxT2IUhVo2 zKTfGh9!GZd%HmeCdUa~C<4j3!%1Lj5uaR4XbOv3}3~cSST#_;RiAFxs8qR z!?i9Fda%c4Ah{NW6H_k}e?aaU%oGCfse{q?T*(ev1%LN}59H@xcNN&}a2UBRY_4`V zU^#Z$tDTOaSUs*~AlsP_@dM(SvoaOjBthY0-WCSyVYde2E@F>mx4ZWP0es^74=`&~ zvufEw9H_Dc1ifW@834XCj~K*F{d*rLpB=<&7K)_koKxD41F7s=Uh$X?QN?(zt{D?3 z16}N6u()p82F2id)8eq+P)FYUL52MinSuU$ZoNC@;_P*7pWx5(v=LHl#kL(WBrU#y zK(e#oZYqX@7j7B)|F+N&eVC8muqr{4cVPOZg_hlIPFfyD!ZRb6GRwY9#`Q$w&Gaz| z+FFo49nf@@)Z#zx!}r^oshhtrk`7V>K~7Pba3deUBE)P#4g#oLQD4G60iU=W{QJ5N z$^j49oL8>O*I`K6_$xIC6m!(IVZF49UfpbZ9}p*bYkejHWrIp(*p1d+P!CVYD+))i zroB6F0+MFLty7P;C*)la8K6qiX|=| z7a!p{;E!CC@hRQa(Ey$W)OrtzK{zBsFt^#CWlBfuJFj=6MC_bTrYgCR-U7x-!Xroc z0;rTfl}5NNcZsAfXl z>yt4lbxOH~6D-?AFimzvM+DK)4pvbTb&1TRNtv+IXTxR;k1xUiwwe?uH=+bYq->!nuzu%k~1+^c*%c;1Ew!2{@a4;n{PypllnJmnYQF<)&GdW(WLp(E|pp{z> z0Q73=x}9$v<73A!QIY*amo|IrsA%3Ka*`vG>rA|6)xhbti9iChclF7_Eyv2w7JvvP z%cA9#q2~*eF7q-nB#-}|JKfYgsf_n_62HXJY50_QK>A=UpA=+;cjZXyr4eybuPtqLhp`_`37-RIXpaz%f2R&`q!Yjtr3{z^k=H4rhRdM6ugDR7xUSsj9aJ8*_hl?@;gbvQ-+eHVU_T-_)^S&3&jztNKTOK z##FJ=9X0?Fz0Hb@Ff~NV2a5G$D5k*o%eNngd15ScjcB&o-EJ9bd|}wm{Fdu})KuNA<`CKMGcveEnEfYxO)!D23d3V^rMG zbkOV;FQlM;9JVtADMZmkdaC_x`kPiIQOE(FJy)y2;9>TqdO?c)=SP@DuPK!h9`wKe zkMGD^Cv62%*Dx&W}^@ zdg2#KCWT^pL6($nd1ZKAMIR=R$4HMh${Yt-gfgL$%l2+hSuLF*z8k?6W3R5>4mn!a zJ`t!_Ck%IvYjY-R11{_iL1g-V!kNc?-D^fPb*6?)J7T5?%%)=)XE>y?QNyV_Gx#5g z(z=Ky1AyCB%7%gFe{4`2rY-ar5EM)QZ%CKR_cn>DF?q1#6IS8^Er)`%kgR5y9ei(k zA5>n&%P;B1ag?r*Q5{GxC){8kA%_Fv$SUv5s@X9gNBy!x*FTb}U4`UJKga1!3CF&6 zU~ijA>+Yd&;Fd?*K?@fi6zoa`I#urc82N{vAsIp^3cGYjrPXn_5m2;bm;wX7Py;wS>5=TA2PpPRjxA4n^IK$I=f)YFAEP2yKBd}7sI%Ut}9 z{aeww#-;HUDDb+l83ft%waqIdImggbvCnvV2vvuz5e@n=XrOHNPq#H>7cZGF{3NA3 z#N}6E{*%z~GJi^f(wwPaN)wPgfBA6lZ#8VZv1s^xGdD7Gn$Jh2pAcKDtVSP93n9rchD7oFUd{a-Al<4g2p6QOoX% zs24^%dPiKx`WXY?d>a-x4>K=7ODV?AlHAlSK!i}(`|Kj}&2j%0C%|$oCt`G?(!+f5 zCJz{>&E8g^6d8tT>a%vYA>B!`hGb|>ZaVh9^UCj5V9lwDlr(X))KG)#$=bFkvpn7qZ*7y5ABaBr(@f5*!duX#qylU@>%bWWze12Ck307L1^S*# z$;nq-DaCS9)nbmX1eRKvYM6+}t5OROfq8PF=Mr;2ceFr=8TaBRs}|)&IB|whiO@?1w$6 zUt+2;S3eS9W$441cQ+9#6nwJR_?Y*J=^L7qV{X6mIA_78&0s(ptZ`Qa$S7l1@|lqQ z>j9PH*WX;#Ro@)31KFS%2o^e{Jq%kzYHtlF8E}>O_cpA&jhyVXE518hk;STVR_D1w z{TH{e0?)7J4rg;^omy zRQGmWUO&59&wlmG^AH?rzpYQ?<2=j?RX;Vl0;5rME$#7&D9{7Tz2I}6Yax^NtZ6~2 zS12qo$N`ysLfcf+Y6K|f;~cyzbkPW+1#c{T8Zwjp*_&4em3VGAAr;MTiMW7~3f+gr z_r?FfYJRGvj9pDtN%PE*D;u>HjjXynmEX0_4cmS z!6Cz%+HSNsQy2pS@Q0|qq$VWl z6iHu(ZOHzApbTGa5*I?2cw=5nd8YdV+!mI2>9-cI@3Cgunl8##Ow8>?7~p{*$_k$9 z440ilTd>9zI!HRqvW?;k$lKux8_gA_6%aaI*?-Mlp=C;rD-k+&A8V?0981AP(?>Wl z4$D-nbwGpKLEke;`3>4Xk3%)EIp&0&6acUx$t~P01SHxxX$CF4EUO@&kXSgR2rtmL z7>SxjC}|6hz#>SbZnMl^Z$|a;!8Y z;(i<%n(}uvHOLX4(2b=h`F9c$WUHGw6M~)1S700jMiVPjFzrAser^XtY#IM{*~k<( z{W6>0qFKIP)?G_bRED-IWBtPlR5qF`CMBH6J4~)b@}O?gM|&yX;P^jsz=a{m!(9pvTAh!mUL3Ffz$Z=spTjl;T(W}HyDL_? zgXXMz7O2+t#>wRZE(-4?t{rKV(B5eamVC)^9W}2*!aHEHqpa5P#k2#kxUWbp-gjgo zl7iV=N%C}lqNgX9D>RhJ>lLd3C44)sxDSRnp|L~q|Q?*)lc^GgF@L3IoROjqf=-rvIDy9B>H zX_+BUeCHd^t8vm6jligm{1oaPWTnix8i))ey4Vc`{=S*_a`guNM@cOrvK<=5a9Gko z6GbuO?TQV1E+zES)q;p|pi8Nyw;wp}M>)M9irw=pyj{Qm?>$PE%U6+NeJ4K#JuPV6 zb*mr$I9yY0(1NY3^p9qsPI$)dNyp?IlNtDDFLLOtoBx5L$~7MWP6AE7CWH3lFZZGA z&9og)+>LGASga7iVbNbIqt{9utz1dnlnFCUL6A8TznLYMQ1vOLs~2(#Iuf1Lvt zNcI~i3TY*qwFFSzU`P4&0$!%u_8~M9kW2t zy6VjV@1lM7wCIkwR4(p{*~dZ>jFLFXq=#^oBHpxQ&-(1(SHa~@aQktwE-dH@&Yo3_ z%+X5v$gD-9Tx9i=0+wP0Y|sI?U5$F|bZL<`;L#DlTls^HU412;P`ejahV4D;yM;ZI zpAJ5E|C%M1aE;IbgLC9qekN~(2P-5fb6!xd)28URN7!}#;;AI_9^qsp>g@TfIDPUk z_!4w9CfD}?_sP^iVZmjJn*gyA$#Nf}0yKs}f3s_frHpGdVRJ}Z6yP?ctNdm)GD#7T z{){}Bl3%E1L8cHohT#1ksw!vX2X*AE3sT}YmwCdzcNpP{w+*Zw(l-hgVC2M#gAQHjQS#%vMK2r7#%l>P=NmnNuC%$ zFuMd34IRVDn;6t(4ImIPi9Po#>tuv|+#=z+>oUz%3Sr ziOO$#*S!q#3spKsO^mdo7(e44cc@n5!H`M-q>BbNq%T*w>_UUu;&Vh-5`o(f)hmX7 zjaQ?LL6pGW+M6<^;5u}dIurb=juyIV%6AdTJ?Y$sg)lgbhyjH3gf1xCf1|Z|%8}z( z)rzfn-@E^0&&oxnC>UjB^lsRX>v3tBjptd+OFE)1BIQ3fB#889GB>bW3E>(~ObB;% z3S7ZN_7Qdmk--KX^+>E7p^g~}48H?wX2f=tT1*iT575Z~nCG=xibwjIfEA6xt0=s# z>a;-S@s@tCr57u3)ZTTj)CJnQeTC$?$Ws}a{8Sj1E;TdEDwDroJwB&MMpNG6fx+Fj zJHMRi6?lT*ye{&j)5?K=%4M$a5+ddyw5F)-;c$)s=gXIoTy1;zQ}#USz8sqr;_pyZ z-#D@gwpwH(%SJia$Nl^LPxne4o^fJzDGZb79$)$Al`?>5MXJ|g^{#OsiFWyB9CW}} zrskUGi&4PUwDcXp0PSJ`z5d;bN!JWmtN8corv`J2*1(Tu?wfo?*BK5lB1th5)^Qzs z`T+*oFX;~3ef#o^PSA}scq^yfmYrR|wXr6X^^I*c8SrPI6~*T;K>C}n7$ZDO(ek1h%Zuv^Ujzk zczy%uJoC$!)G@)E&nTN{#pOlJrAsGAjcd)1jrC}z!k)kA4Kxxw6m5Dp>+pE@She1f z4Io5ES7z+hJKbZYiY+_cTXtWr>@r=|9A!>!>i;#>{#b=(B`9ctfSro}sEy{~+I}o0 zQD3ZcDK_P>p8_0v8PE&xaHpo! zyCYPvrl&t6UjfU#xpFqvNu~9s|9W5W^#?ey)lN)i#&U~K7-a3-AFUs(uKfd+yZ2B^ ztTbiM#Wx#*cXdWgI(6_@d9F;|b2HDx9e4JQ-ZS@)gO0!-O)IJp`t28)O|0tV9hsKM zsLSYNTU?NzWMRULAutnXUS{J!dj$P)MB|e*v(I75FGJvQ*3H~N#E`bvYy6MyVM3rw zin)331)jp%)TwK(yfXd-OXBE0JPZt^ktoF9IF%u{;A7mk&Bn3RZghQkj39BV!S<8)T~tJX^r&-0 zN%g(Sj=B2XdpH`PXF4nd?C1Cwf{{WNGU?^ebt?(`nK9u}Z{T2#W$4`L*^)Wr#z9Tu zPN)W1NnRsx)ulKCUdFa6O&CsHMP=|1SeJK!x$R+gCE9f_@o=lr$KUu8<*s0JC<2x< za=WDeplgSXU{aFTsTOasM{-HFEy!TU0BFp$prQ2b<)6x!N_-WGPi|9QRP8E*EoYlp zP?R4CnIW!m?ia&#(kCgGRz>~20eS>|M5oG(s!ptG{4lQL#$k+3^Ol{Q-TnbqTnBgE96_l7Wyz_tcqdcfCq*w*f>O*^Qcq3+ z%$o?FcuF0{yWZjvu|LsFnd8=Jw7c8N*D23_9bJv$plL~9@(Ed}=zs-{=k0A>%D=PE zZVSVCdDiD&QTnF8Rk5zhLprVG3m|X@5}_MW!z-TRUC*EzSBP$LwE=^7*oav46qGq* z`&@IY3^TQQ%b}+~Dyi+Z2xmHZD(;MU-a`20|J`BW*n%(2Y0#ZsptBRLc2cBR+I&6g zOO_24&NS@n`7K@$@2Izw40h`Jy>nikS6d#!03Be%K_o+l%%ECGCGNB=p~s}txzt}bq2t0t3wl>}L|gF&wGVL`Zw1OoA#Vt@ z`)2U3YFT5UP-iwVvw`!mK4t|K+JBA7yUZFZzAI8s;Aax!g?_~6bvn8YYju@))D^IZ z(*o7r*#czLwiU&I8Wbn(-{}qIm@l!%N}rY`^&laXjxkN&a?>X9`R@^e5F9ddUCd&s z#!=>NE~~Hmrsck`XItPD0cGHnL*sl&XPrkQcd>w zr@G#nVUl)GBav=(+hUn>kR{bjMf)MJLd{tz7zC&4Jt) z&HH!ktg*W8^4r&K%lgRdP1el9j0Xaq3Xgat)Bb1>jBK6Xx%zp=x>1)EnUz>=C#X3a zr4BQ~?=atR4J^%cvI?zrGsylb$epsEb;W#O<#DZM6W%nA_UrQ8-#wrPZ9 zTY<(zURaQy6ZhSGK;)!g=eA!;G|Y$_%4=SSH?D=2^*h3?Nd~!$YZVdF7L=LC#Zg4d z67MZhH}D3M`duSvOYVuBwS+vl7Ulp24WEf9LI0}RDc+wP$p5cuGHKqcne4d6o{=Z% zL;!%N6R{M>&uMZu0NIz;148QBb{J=_X%>2pg2JK&$v=o10Lh*H|Y2F(s@%5 z*e3j}$to?ho_8j^5#n%ZZ}&gl>aRe&F-l6tVh+zSnS^*r^=g#is=^=j`emEA$gf1a z0jEuSmgw30(ZVV9z`^~05~kSji0Z3fTP0hv2$(pmCa6Bk7&9k+#%BUa4Blv0J;QWravoimk+~IMfP109NP(!K1 znRY+c=k9bMICByLT_69g35WJCt{LTduuUu`*5okP^C}Cg;1m=&6H>j4Hawi_k1RM*crFFL&f+B!NbyQf z(nwiHk8qNwR-pMKae?=mb)bugsOxVo4{RWF?qm-C=4QysBRA>CMb|HQ8+uR-DkFE! zD>>Musqxwju&`+XN`b59)vikuIQaVtR$RcRGYR|OzRpf%Qkls~QctzBn z{q@$Ak0OSB-YK$z?eFTR|C$ z6wHQN2^ltS>JCghw*mDe1!PSt2~t$ow6|Ks_8>|6EHxzX-ze1VU803EE09J4x;$&c zs2RFXJ@e6N7BU`OJ%^-aeKF#e0$3l`eIZTAy8c0;xv-SR>q0Z*W(O4D0E_--?X=S9 z)Jp=5vxfcC4#r4~@#rC(bjz-Bx{ns_6WVZ&rIn|{81;4^ktS*E~@Da`sOea%IO zXYG08$z}i$O*?yCv{){zTf>FSDd_(+>mUILNUFu&e3zm{VJE@PTnYwHKNn7}66YDV zltD03($P)QHMPRqrIqO3EH`e)+CkT8QpA-MN1$cQW@G|VbY>JGz<;0XGk!fQ5-P~` z%=kf*MFdB-u&IanvE6iyFeq%McqLZ)fLzpe*m(mn+hspDCj`6l$fb;crkMaCb!P*4-CK=HS5MW-JB^CF?`Q$uTFqkt^960u zz@d1A%3$xg9Ty4nWGv(ihYEYZrD^D2&R#oQp7pbpe*gZQ0U2t8#8TGjZNZA^@=~F| zn6yWJdAQq1^uNH8`O2eZu??8@dk(q2y-~k@6kyc}PEto*-3rf-8$y9?b5A&PzQHAeW7_`b16Vqrs4GE?|KzfClFVt3n%OlvY=>o;wfQw? zlD7|d>!ebcG^?1hw6%avBeK4tset;Rle_Xp1U2Z4s*}9>OzV#Gjldcr=Cek>_=x%o zm*RXRx5#w~>Ms{SAX)a*g!=xS8KiiQI5NfQ&t{|8Uh_+0U)zl5I7JMxmh?3}$NFU0 z^N>5Od_=y7zn%pUMN8fee?U>>zo`fmS9VW(mInsi3qe{Rewei=e}Q7fO*fjTuyFLM z1Z`1QSMT98k&|p3>BuO=t&)Rk%<@sbQ}xLx;c=@82`g*`Hj($jPu%DEeaUyRM90pTT@PP1lciZqHfwsNlt)1RZrx2==bcZ z1Lc7pDn~eH6nfH|dXm9Vc{@X=AEMj_(OD)&MLQxthk`xz79_hN2ojr0zY zL?uC*@R$_<(yV_Rekx@I=%>#IuXVoIY{*6W<%}W#KRZx+Ps4(o+&@3d9)jmSPQF_> zYLCcN=rvUyC&03=^9Y)`2s zUA78V@D@#ocwta;H=d}&y6!}V)Gy$6d!)A54Qx^iRWwmKTIajk#3keEZT}}}E)szy z!0jMjxuk0hWE0z@VEvtwX!Mt5OY>y!%mn*t%eoyTVza=py2^9T2R>TYse$YGpay#} z2o!-qWrZP}w*Y~VZ!+@Gq`g)qTkcd;u7-10a&57^dYtIY1CpP$5ksk#Py;inUlVh# zr1)TY+`!dE#sqb-fMh}ZZAn*BA2D;lGalwyNB!p;=C;|8?){{%VJ3KybK*Y>pOYSV z3OsP<+`dA~Yib;(!A!7Y)DRLp$hC-$c}0`iprU5ZxB+;8>iUSb-DpCDM`l;5Nx4+V zBkMn~=@2PZ8*ek{d#xNIheB@~+KG2wrEbHagA9l!w2E*;0>b4U z6ION2f++P4=YNyk69U?Q&6Aw08l%-&q8%is_DKJq0x|R*bjbZ)%j2r99!g7D531qb zec{!=sD1NU%t#{9N~>PRS>SH@%?Q(05$u%e6!@Id()<>LYbqHY?9s zKQXm~UJrVacRTim1HCAkOKO)o>gM`Qq8b{6K-lr<{L!nj=;J5?7@O#ND2u_Y=ayB| zoY#MPt0`8b!K7O@8s}z$QF1%(N&57BWBWpg1)E z;8SXi1hLew`4iHT003$N5B{^K|J5G0!7}@x*tD)%h*!;{?-w>?_4>F0Q%uL7hiE_~ z6U36eD877sB_h1w1NLxo0JD)lgt!3f`0pgFz>?JJ zd_=%dlD&o_Fb#vRXB6>vE`fp}#B7DU)H?mr%ZU*|{TXC1vKgbzd4`YT3+ZEf48b*L zwN+|?fBvDHQc~)A*t`W1J->V*SDiwRC&x67 z{qrJ~@8$|B{tf^Jvh?8CK=rYtysMoYooe_@N1+)!>c{;}gJ9xC>KLv!_}lr`+~nBFZ)}kVNWpl! z8$cLDTF^kmV7KY+L>F7?y@J66oZA_@XJ=3ttuoCK70XJZ$_C9|iQXZnlw%4?5om9N z$GGj~$%1fPmAmDv-PV`jhL|EM*<@xgcl17<&{x8&yF!^Kz9o7%_9*kA!F|cgI@Iee z-Go%>Jyw1-(oKB_*#7gTCa@lwK%@-e4We|G*QBT1s9I|{2=BT>ja8sObuK#B1~Z;f z^usP>&A>KPl$I{y3Qsbu3YDw16DR{8#d}eFdZbvX7!F^ri?oHIRp zdp?r9MvmRlTW{Ok^=kz>&z&F5we;V?j&aSe?ddJKnLA1?Y8ReVv~ev(H-f|m`;+@m z%H8&%yVNjHzfDT}Z7lQ!;MI~jt2GTsm*g)h_O`SLEIpT=aL|D*%+C$=bIMymWi0=_ zELJC|*$^$x9Btix9r|g8%?o$71AJb|Z`^f#3e$F6Z)?u3Vbq^hb(uTZU-RX(d5V4>rCzPa%Y>zP@vK8ZjDqp3js&X{_&7`)g8P zCa@}I{p}utbMhd13^IpHsg0-}s%qw?HD`!FizZ-dQoZ^!TW-}lPKlNPYHO+{hO6Ii zaXM+^0(*CxW`k^zYz3d=igL=L%GPR069)5U*ZQ4E#Hw{^l>nK(rxJWBhE*2q?ldcp zq^;L015Eu3r!XB*NX=~8z>QARtkD<-Xbs)LNO$%m{?!|#k%hVI->MK5L6$6 zmT`g4t5Y}TI4`U0YEC|1NGVE2#sVVI-N1_TqA4?~&QTfbH9+6bkP?7b5xzo6uZ_A1 z+Qds$dpKYYa8rx~ej}>GB!7mRjVd-@N{G6fR`ovME>{;;WhMF<*uI_2;!lx(CMKB+ z5~hmrk8p3H({3YtF1l3Cd|k_72bM9S<FIB+#Kb=|wc*T*~3Beh5kN>(d%( z+F%jGCKs%kAhUPoE!W^?75CNZzbH{Hbea;7;RltkwI775Sot3TEh#B-TkiL%5A5{A zU;i2oLnq6=oM4{4uOQtQBblLZ$gwHygYD&#rKohteR{+#*N?ne{BjM;Qyf-o^Y7Dm zASh72O?e8|fEX$T+1*6KD0?Zic8k@aSoE0mgPY#bvZ8&Sn@(Y$C;exV?dH1GWV{+3I|CTKX%k5BOt zb;RMAx8W*L5oU0@0hmUkNXt{Q5bM@EdWZR}P??Nj?Ln-lAY1;@Z7L2|F|3gLXem3w zpxdHX+=IB?IVYiCeXr#qXNNN*QB5}I(4E%jPu#z9@P1!kpk1#elQmsn1cc_(7(Dv} zZ-zP8n*Hw?uu*kr8-X3S5G;4;YI4MRRVME0nx4IU#OcJQnY186LM5>T70S6fI)duH z&{;JRk+r_ zRJRl7Um-?;F`<#xlUS=P*SMze;b>*|T}ns%Wl6t6DOn}Zh#Q{c)}WjWQ@}aK_T;!z z`|y`7WLG2UWvSp?=7mJN6V21* zg2o$*pziW;ri6rp$6+-U0@6HLg;LBjJ!kO8*KZiA(*$!JCr7c|Z%)$f+rS72moc>U*?Xs?inOz9RC z^4V;v1-?#)>2_|;3-+S(VdSshW7OCJ+!&eqoPb79CW$~5hdr4cxxHB03cTjg zmZU(N6DOB2e*7!8peBSvWtm=Lu77PJEHD)@a){^W{?8&$AK~<2g;oQl zqz-hDXv<0!&0D&Xwu9>T=&eOL6kPkV91j+j zlpu)PlzVLxRa%+?IzRb=Gg~<^77WGjlLQgf;4mxb1Xoh4n9LgODW|4AxZ^^~7?SH( zJcaXIM`T{6Z<7gB9%xRZnUDQpiiEOXdfEHvYnc2avSj_p2<||m?4{NZgRQGv)I`bo z+@sI6_c9$d=NM%x*(A=`@di20T4MpI$mG>MG`4xFvsdncTVTrG3(5kr+-}luvN)*r zM&PVOMF(TJML!0i6nnaPoEp~(hkrgy_%A&jKj|_&Wm;X`%AHR#$Ybio{C85%0I|lP z>z?W*ciMd|##qu*L51SZb{n@PQ4kkRq9~OpSh0bk4=0W+k3dQw(xhondyaaAY|kJO z4RE7SSo^AMnut$*2X>R>#n=z!CC#_}>P(TAFv?>|Iv?PJfU9dQ>)B zPUuY9;)+AjazNFpbkFiV)E8>J{Ou2bj2N_DHi3p#ZJTjgMfMyPGj7$fsqFQU11{kV z0r;*A^?F<5AXy`LgtHQjt?iii_no`0U`Z8y*}kJ(^JWhu>Ra5K85A+OaZTMnxOt*;11_ z|FMS7fJFm|O{4n*<(KF4Yv9vbp|IXY{IhwMs>DYIpL5%3O*PG;5Q!Po4EKZp^;O3$ z{HLxjujX(;j8h%0{Kt2MWGkFtsb2x7bB^X`AKzE6cp*|=PjVDFcy&{T+tFc-ws^v? zOHU#?Z7K-G=|-o4cf4;`=Qv7*tJIq)c8yIHQI_wge{Qa%wEsLh*KZKh=4s9mrh=fA z1sg$!TR^^@IjIJyMB@y51r;DdJ^8H*?S;j10REuNeXCN4fPR79JVWgEYmDZm$s=-^ z@&3h6%f`vQf8ak6kPD_*41Mul&X=E)(vXlFB;KH-mt0QXeU{&hql++xtiQ zok7|3C20WU0v>7Tvt@`%7er9PA`nZes)vqvzQ>T;mLQ_cqDdROtY)uf>0`2%9!mbq&vH(Gn_jX6G)+%1vxfr~&DbT%LIz}+!o3c%V6dx&7Puel5^fuWcI zG4OE#jE2bf+=j@fkeJ6Aeh-VD!nwph^_4joc2Ai(B$jz#{1D#~`xhQQYs%x~kY-MK z7O{^#d!}iCcnz)m1w0%Cq6*6~*hhWLPYC3hH|Mi1;(x(K;qsR8-2#M@);=kjm`O>t2HF?uI28=e#|3i*Ul zQt8mCt1Q`X4cn0p>|K8nsa%%qd)RAlP{yeFNdRsmhb;1($+*fFFb*>8`c)ROA_h6==+oFOgTme$P*}n3q@Jt+UD~j zn~A7>h=lMpT2b=Zu!nYiG`TJkg5tAAcac%0e-lykxmKRt*i%At!R$CvNfnIc{7O$u z^$N}a=p$B+766Oy^YdXi??t%1(x;j|cDKSSh+EW}^G&vE_4-jCzRj{1 z{Mfr44Z4j1y6J6AnT&$iYKvJtu!wSN$fapOvFqmZN!kHh+V#G)$0WKG$jSQnJ>&#M zbHX;|CLTVW+aKzqog$V%a=V&KhU{vuHv*+p7w%U?cMXZkvI{*zZ?@$ zC0{J=3xQn^qeB!les8f*H%IRNuoxo#$YBc^K72&k&!@dt-oA7bDG6T362vEs8E51p z()&TixhNx45eQe+$Eqwprz0dH%Ba-DG!8lufpspZ{AT)g8ae0zUsJCf&VQMdpqf1i zZwQm2O7k9s#iea9I}V%0`Lkv?s|g4YnWn8I%=kOD^(+6cFm^=k98<0c0IO z5)+gBS%OoNTr}Q#v%`Zeh1r_yFw2R;l>I=&dGK=Hq+U}#gS|vND7HHe;19Uie@yOK z9v=7}+euq>XSUHAa>&rfzs$|deJFk&3d)uS{~FCV$MF4o)jq0|YJB04iI3GYrKA{8 z!>26YnN~7dSx(_>nie6J6DV4NH*NOzHpe9KX(-8SeAEJ=yru@I49~=0L>C^)3l-s5 zY+`+rv$oWEm+VS7f3*Q8!BmypbIMjMaA1)fY$eu+f%HzwU4pGMe%+1?n{+sb07^nd zC|wj+p#$+V$Fk%*<(a>c_yP#tz2p8B+b zk7qR@k`;*S1HBPNKcRa6$)SsXv^K>1{~b8T$qR^hRXa_&8$em+PFMOM7Gb+DP86gR zG`{f8{#md4{}}w`82)wR8fnX&=wjfk)zU9fz%%TG47%GT?+v+%JWpn^>l`QyAT8~PR1SXHA^nz@11cAM*-5% z`d~MB508KkiziMEUs^4=urp4v>@``ORC&XJogC@+wKu=YLfzN!ni>dsq<$Yw5E!hz zLLM}S%Sb4Cw4K&C*ZYl27GHp>FOUtX?isB>XW8(oLkrT$=5`P-IIcQZfi*w*qiXI` z)sB+82z#z;lRvm{&G=|>GB!A>ii4WcnB8u_^;c|`<{A1lZPYIj0mA*6 z4(|=a`C%l0#8PurHPLT%7by4pi+eLGn;5feaV0Kp4iJ_etuI#hRgYRxFtqZ zR?CvnTCleMWYdnmD^#f8{0k)a!`@n%Ym;VWP1D*kbneAH)Juo$BIdATqky ztX~w$vl-1 zYQFw$VxGNiL7D%1FS$x6xHs; zg;uRIS=hmr4v5ZJNg&Q?X@C=ek%UE~aDN3kj+69uG-7MlwH13&Xuf|$9fKCF&X_0_ zcsycO^^1f`ce71T{}<5`T;pzL+Q!o)NiYN1H4Z6I&|(pt(}v&#kzFy#fk#Zwgd9tp zT*^ju3CQ1ky_DyOP?&Z13YpMX{F~$5GF_LS;laE$G>!Z>M)N!{f zQ(Nt7pnRkn(N$;4TFq#lb^;;5iNS-BD?C!pRDy|ZBXWW$db5iNY(Hx*1=bKBTBQ!8 z^(V?zwB^K+=W8=GCqX$pCdDc3 zUw*4iyggDk9eSI%N@$E#)EK|p58xpYSvw|j-YBHD%$Se?SLtPxy9rl*Rpv0!52%BD z1Bd4-cv!R6nxPsj^4Sa;W|VVuIWY8h-?HvsJ<~HcAPs80r+Op|*_$5OK)%Q;>i38= zOoqONP*mb)y4uto)qS$sa3{{o{%V*4>5Br>IEq$4pE<(b+0O$(vhXaTKTzMm7qf|@ z&jTmdf+;X-oo9ej%)HLXR->6$yzPUGM~6}2LQn}meYowdOAT2?W!YTtnY>8KoXGY> z+6j^bz~DR+nMO@0Uvj?VTA0C(dq7b`i-i=v${7Rjt$~mEPGsLgHHP(@LQ@DA^K`&H z+uH8m<-ZOd4aN7^t(=>r@JnK>UcVKLQeh)r-kM155*bnf7^MY1tKa6lPW+Ta#! zu(8?`&hW66i`|%eDgW)cnz9U~Gsc}Ti2j7WPM?3fekpY+S#0qD4@yhvR{`&v5VUPz zqM!{v$0JgL!oQ-d_WpSVi9F5wj7}V1xlafY5(T53=VV4#@@pj-espATL*E0(5$Rt;T)4MU3p`^^W934hug*X^ z0nK1XCs5w~NT0YUKT(RL(T&a3eNlks2ID|QVLDD)o8UT~qcT}CB8V(_NS!|k)kDXO z;*ITg$py=C;$;5WR)%|eK*o5h;WVk#UEgLbVt&lyrk3{(alD><&dPKxHVm?*??X?e zk)-N3VUfW;xvUW(K9P>B6N1w1yc>Ki5kqB*JggmR_Ubt;>g-3CC?->qkdUy!iwa|6eMGn3)D0JdOJmu^MxhojtV3L`cd` zYFVH4q&IjT>FVzFQ;OqMUf!|aXr~HkOA~c%d#Ow`R?vt&p!hadbTTWE*_?wcDwrFc zzaMUazIitll_rJ?AA0Gz>;?f6(@9P?bRqoTxtj@C9+}qG;zyF`KF^JdtCy%o%9OCg z9iapc6k+GOr?xD_OEiY?==}fgI-j4#e>N}8f+esF2I*N$Ep81o*NiXEW(_d&m$pHh zeK5{-E|F0ef6PI*aR5I+z`wkR`H0JRwu|=g5X}W}pM{|rbGL*eC5B&+-=h7;8dt!k z9_OTODay1yu;oDSnpTn3o7HBYz-!l`L9P%&GE4#-vs@c2j@mHh=SNmnDYTNndOCCz zQ98|%d~Ed2%{AuM6Q2mtWNmlX>bNNmEQ_$Bo`)lA%W`4|heBHyaw+00Ykkc|WNnB; zh$^(*{dDk@Y9b0}^LkC|(-kzuG%&g;-EtWh*znphk{L?wTsrk^V5*9iym_X1YILg| zXtkgtF-vs#l(0YH-VubwjQ}i3fb+%BHxsQCsO%SD;gj6PW80 z#SHXVb;B8$y9Tc*Tea@K^x}36 z4kU{M`(BG!)*~z5H0c8cs%1AvPjIS}8Pd^cOyTf1g_lWu-Tm4M?L3|rtsN&$@nKnH zmH`YQxmak|`U+mD)zB&r4u#K9Xru?R%^f|`zg47ONF==GJ+X#H9tvIMx;7^37 z|64vZ1CU_Ak^CnWW^f5FeU{L9BqYz7;iOhKCkjBFWK$*~M z!wYyqrzaj7Oy(f@cYDXt3Xy`lfm$u08S8#;_*|hGP~`VW1YR?7{Cqhft2ei^&woFW zw8_L<`dnuQyeH>As8Yi2$tARlMLS2%m;8{Sghp~yv=5aE8~b7Wsf*}sqOHl=i$5uS z%76-NK(kKtoDCEil*f)tk&uYEKv7JC1G*$(P z!c-YA7R;YuH|RK2@gx!6N~x*TQA~`61%24i_-TyAAKnZKS~w&>&h-3bB_xJh2H&b< z{6&N6&@p{LS21Gd>p}zF!q|f6#k7f`#hjXuFf>zMH{NC}q#Ku8PNr__%s!(x!Z($W znTfTIFmEric|EJ{^Vyk4MqHCh>LNsA4rgnLy(`R#JEdQ26@cTj^v6OikYA4%g}wny z+&n{5p&|}Leo z{q#Mxt#h&&mIb=uhBuQBihM}?(`{_}>K3OYgf7-E^2?byG{_MEfOK?;Ohm_2AnL~n z9b~9d0;p`2e3ejHB>_q^rOH&bx4^(YPelex;R>64d}Ngpx2#E{E8*Xm)$L58S}xs4 zX82Q{j83&l0dNEVvt|}0k}#=lPGq&YR1)ydE~vQOsq5aSU{ZXwNy)Dje?Fr?EA!$^-jL|}UEIJn z`vS!i-Ysn*951N47~%r!tss&G{174(w(U+iFCbULP9^RF>lc+&i8t6*ySQd-9rgds zmk-}x%*$rGfGn_20cu+%>Ub1m2S8Z?WUc3Xf_4lkcT`V5CmB4!D0sk+m`VnA ztbn6;Oe6Vwm^rzadB5tn-TE?c*5Z&Ok3GOt%L;*=7wee0&=4Qg> zs4+Y7-7*RaNZ2k_0OggjLvEI1$CwRjH2>=eh!{x{nN{N2#P&I|QWXy80;m@i5#~6n zv{MkuuVuP4cL`0cAkIk+{-l*f1Qf4lTyi!b>l~*bJ=USXgXx`d1@5Y=$@FT<`u~2sdY+6UUs*2Fic&>ZUcrhs-T)(4Y7%%-q|PnrITeun$LvG=7!-(b zzOu)|!vvb7h#whJX>fRoHzQZvNA+WlW0z=3|E*7Bix+GOI9~m?aMbJYH_?59$J$U6 zD6?S^ze?o;0@Hh60JmIEPiuVEe!ftt3Z817rl zE>Et%S4j2L>1_2@P~NAWCWX^!4q|P%n}u4@4{MiU|E#872L9x09-uUhA5T4xTkn3) z0j`HL#XCKV^Ohn^eS4zeV_|VE+WGq{MpD+gH?#H~wRE`wAGwTQ9}M>k&om_tr&Fi@pTiDk<+Serg*o{BGYN68Cjm%g8hW7Z>tOPPFNj zQX2mRvU6b?ao_n4D9ba$hz&F`hgv0;K{)SA8VlM~*24I}{=CvTWUpZ8%n3*TJsgfh z!^2K(SWpn>GzFBREojN~;G<|_--Wp}v+BH%q(C!}u&nm1$>R@YYRgt{+6{wK^Mr^Q zFZO<%E1CDL$lSU~lAKlT4ZJFYVFM=ED24DnAcZ$haV+cxAqe}icSVmy;`n<4!dE^1CVHIc!cCasg7beO-Jj^ zeLi|3&jJ4jqvSmjGBepKPxvBYmRpa>g2Z|``C`W0$VL15>*mnqgFPnwoEBAOT`lfR$vM z;0abqJnvCzbTL&9uCHaWonF5oC}D?ACn#>F23BOPM;N!MuG35&NoB^bvX6;;eY3&Gf3HSf_w`w6lgoj%^*0@GuD6I1;r@!~ ziVG93a=8uOx#R}0Sfm3evFtpqDsyGR^37TURT+y`^sC3fgIb;i5uCSFm@B*G8 zt|X<_XICARP1q7Uvo3q4N7w*mrmR~J<1KJWo6>>@lnoChrICg9A5sg_=8<%^TLWOSR8hwb`{{vk`LGvL$dRv zjE<}T?7lDTREA@PEUX%{j?6!T6^f_fMxaNW#0v(Q4akO&eJn$wfGL#@Z?PKm@sy-l z+QcMIe1|gUYP|u&INxapi2^Tge+$O@c4~Rawx!p0Wr{8lORRIA`>D=*L5?9wHpK3{8)z&NReIQb?#4UzsG-ZPlR!q498Q(X6xeoYE-k z`@C77SD;d}a$nI~o-p)~W0%4;~Jd0Ijfo#(kJ^S^a!h6Xhg|N-# zy%c8+ocdHY)c%{Wvow2UqYlB$jp!jl^%b`VEe=KscS@H3T7r!Lk(0;M0)5~rLVe&h z6mAc0=F&oJe?vuZi4@MNMUahNV|x-<7{jGxg(nhVw!T@F2q4fxsGY8P&jx zh{c+^B$fg=!c`m6dJ3<1iT}f4y@^_oQ1S7wmI`qd0%;nsdkm;4d%_2i+gg_wtOu6F zeKUC4sO}4EABY6L@m~%L!Wo&mTf%O18^jYTS}9xiM)v=oOodsT>AgoWq5V1|C9j7f zkbvAEjKS)YsQK#}e0xrSQ|MBh3x+nl+4=s$HrmR!n|*TQ%Ny zU;&6iBf&o0Ym+;Ym$w{!Hj6zvuvC}j&>tS`t! zYF|<6l#n#E`!iNzw?JCv7F#Lt@tOobMZZQK$8La?&X~0QAzHKUODw$JE2ywlP7A&< zw~?jgcRQSZ3leC62o6+vIS>=Z(;A*o$#>&?1i4}V3NX4Fh(mtZwH5Pgegq;xP${8f z<6TIhJdAgGCSknkeYstxPqHHe4?CCZN6k-}H+n`2E4HE$*`T+1_~SxviSnFMKE!tU z$YLQKDM>hTyW3>cb(+WlQ06QmD*3$uu)pm9Eolo?c;y9UEoyT7M+(|=!%zS)ZpcNN z!#+lZ*xc`y?~{_#;KXH^Vr&MKq5JNeLJtn!>9TOJ7EOs}YyCBpFA}e0l$a)?@F+yV zEzWB<5#}3Yk&$7v+q%W1d_}3FU>~~T7b(8rPc>FbmY{@qC zKe@h@Gf)7QOdx@`OJ-oN8Dxk{qLJ6qO|1ov*q{gMo%cj*tk-hJ>BjUFUSTkJCc_i?XVW}gici$E|9rr~M}uyhK(Yc5h& zO_Su(<3qJz%ZB-K9+Fo?#AeNx=g_&0-Ram9pzwRJWnQ zFDCZ>zPQ-%Gn-72ks`>(Vi1f+L$|5t7x}JAf776=fW7TueL&cx6WwqKCRan=&rti2 zRs@oaa#ij8^528`Z+Ysk{u8b`HYw!0uk`?BuW3g8M*w&+w4j*{V93gb`9pl=ml{@( zS%^5c(+?}vrk?y?psP$rIH7_mMXqy}ypsC`Xv^lB%gyN|g|819Ld5f_y&4?L zKcB-*0-sBBig0`exh@AaD;crAIvI{$V9v3q=Lk0#&F6=%oB=^g&omt#oE<^8o2}j_ zok!T*KFIj@A4`iMsVxN`@nh0*ku58SC?maVpFfg;nykf#%%}LFFuMH~KF-4>aIQ4_ zN+?5B=>C&G_@?N$<1fKar;&-ORl(a`wBLWPN*}_B@2{Fm@KMkT7o@T{VV*{z@&I3$ zop~RiNoX42$77|msCW@Xf8)!`mDn9Z!v0SEw>@DneMygQ1+-|9YHLk{wO11|Hb!x_ znQDEh?V|`W%|XZ-0pD!&SzeZs!8eLA=rfyTsA}~fkIznzCrPOeK$X%Z@QCxzfK3Xs zTt(!le`LF<@~9;`wvz9Yd)AImkjSl8s~pbuf)o@Wx#n~l4<=o)Ckyy4x(nC!yoqvl z({}bP)v%b1mm?Nq_Q&loqqvP1o^~>~f=vAq^RRn8{8bES2~9s2Hp-8Ry)7&B7h*X7 zUUSdRUO*;q`Ih`FKgVn5_N55aem7-8e8?A3Zx?`E_|W=@r(B~rW%5K_t}|%vr`=Xn zpP#}2*iUqBlR0-bM~ui>4+x@qb3WjiI7)u1<$~g`=xemb?eyto4KD5Iud=c;X@90r zHz2+f!i?$lk|2LZV`!bTDr~dvpbg{!o~ExtqZBialIi@Cha0_#Gt5B^=J9S7)pVvp zBHq5HvfIadM1Z1(|H!cA_r7I#3wH}-W>9=?V6sRitFW;T59Qj616F1F&(FLY29foH zCkWc!Jgt?oD?+{mCr$Juq&WAc`c+eR>GNz?79C)oU)$WbPOJ@HX z@WBp(@o5?$*;i-O2dqTAw@NbVz}D8}J=LGF_-AuvV~+$d!8W~!}IcNC zf_vA6xPh-gM^C3W)mIZaKQh}xi20gW{uz-q8)P(-Q2q(DdvZ>JmD#*K_L=e~d>y5` zLns`73crlWjFYWkojU`vEm%`9e7J85p}e09J4K_(xk_t`Fw~z8FNFU7rH~<%Wx?z( z52dd$y5)p=>*{`kTAlh>4&5+x4un}G5KDE>D3n`f<%ptk7V|(am)iyr2nBZ(1MPF!3UKS8dW5uSr4JRR%f=!S=X@*y;rsk+ z_8!gc7d0$0N0}{auvifAVrM}9SQ6`^R~N{xn3BucJ92*zklVjINXokEqHDh5VYG3| zTgf`W&ar3dy#$GB@FI;mhEfvHpK^wo5C8)U&$<$CD#C(0c1})?HM}2fru2-T-=!?~ zX7`W4{}Cu*RRohAax;+2mE~t4AV4)hT+uY;?&XmUBFFaSjml7$oz(xXb3XZD1CnR| z{&NFUxPV+J<|wI0v2aRFb3d9yLc?z~QX>$(Fqa>7l{CwbdW=8*|e zrq;{|D7P=cJI}Aq>PKhW_$_eSoxS$)eqB{EC@bLh=|k>B8WaF zfWB_`RDV+4Qp2_I*mNvouC0C#N+K;*CkJv|GPedZAfa-G z@_?Z4O2iO%AFb1@*b-;p%H;wO-1LQt9CEbERH>$r8bC~UVFl8@`kebzASo{S);mMy zrKeFwhkEV2Fc1bUTu;ZsEXC zWnxH?W`1Ne}VmR+gH+=qE4AjR#&^CT`NGH-~ z2YjwmlZ(Q<V*s$+RkwknZT7fa=8NRz{EUtg{DIoT2eXgV4o{Ee^ArHz$LT9mds( zEJnU=(A_n%3y05Z@b1*%TAvfCit7PxTFyLSngiW{Xv9bM-=ANh5Dnzcru+Z^7?S6} z8&yIUUOtm7+;aP>%GeufxT|DRV>zP*7OEKfapW$1umGsI{6FF52*T7682DC~~7L)FN`B zYtTdP^OUu%lC;R8%3?#(&0`X6Va+MS^@7J>4=$6|(byD`)db+l(w6m$UY*$W9@7r) zoo~DV0S%YGM#Z_ilO40-pF+Nl2D!E2anJ2Vx_DtP

K;BNhX7Is_pl5zBNj8KL6r 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 literal 0 HcmV?d00001 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

+ + + +
+
+ +