diff --git a/.github/workflows/sahil-server.yml b/.github/workflows/sahil-server.yml new file mode 100644 index 00000000..bb2df291 --- /dev/null +++ b/.github/workflows/sahil-server.yml @@ -0,0 +1,166 @@ +name: Docker image build and publish for Server +on: + workflow_dispatch: + inputs: + path_to_dockerfile: + description: Path to the dockerfile (default = 'Dockerfile') + default: "infra/docker/Dockerfile.server" + type: string + docker_build_dir: + description: Docker build directory (default = '.') + default: "." + type: string + image_tag: + description: Tag to apply to images. + type: string + default: sahil-server + lifecycle_policy_file: + description: Path to the lifecycle policy JSON file (default = 'policy.json') + default: "policy.json" + type: string + backend_s3_bucket: + description: Name of the S3bucket for Terraform backend + default: "sahil-terraform-state-bucket" + type: string + backend_iam_role: + description: Name of the Terraform backend assumable IAM Role + default: "workload-assumable-role" + type: string + github_iam_role: + description: Name of the IAM Role for adding access to ECR repo + default: "github-actions-role" + type: string + aws_account_id: + description: AWS Account ID + default: "060795911441" + type: string + aws_region: + description: Target AWS Region + default: "eu-west-1" + type: string + backend_dynamodb_table: + description: DynamoDB table for State lock + default: "sahil-terraform-table-locks" + type: string + +# concurrency required to avoid terraform lock contention during ECR provisioning +concurrency: ci-${{ github.repository }}-server-docker-pipeline + +jobs: + docker: + runs-on: ubuntu-latest + + permissions: + id-token: write + contents: read + + outputs: + image_tag: ${{ steps.build-publish.outputs.image_tag }} + version_tag: ${{ steps.build-publish.outputs.version_tag }} + full_image: ${{ steps.build-publish.outputs.full_image }} + + steps: + - uses: actions/checkout@v3 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/sahil-deployment-role + aws-region: ${{ inputs.aws_region }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_wrapper: false + + - name: prepare ECR repo name based on the Github repository + shell: bash + run: | + set -eux + # lowercase the name + repo="${GITHUB_REPOSITORY,,}" + + # replace / with _ + echo "ECR_REPO_NAME=${repo//\//_}" >> $GITHUB_ENV + + - name: TF init + shell: bash + run: | + set -eux + terraform init -upgrade -reconfigure \ + -backend-config='skip_metadata_api_check=true' \ + -backend-config='skip_region_validation=true' \ + -backend-config='skip_credentials_validation=true' \ + -backend-config='region=${{ inputs.aws_region }}' \ + -backend-config='bucket=${{ inputs.backend_s3_bucket }}' \ + -backend-config='key=docker-ecr/terraform-${{ env.ECR_REPO_NAME }}.tfstate' \ + -backend-config='dynamodb_table=${{ inputs.backend_dynamodb_table }}' \ + -backend-config='assume_role={ role_arn = "arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ inputs.backend_iam_role }}" }' + working-directory: infra/terraform + + - name: Create ECR repo [TF apply] + shell: bash + run: | + set -eux + terraform apply \ + -var 'repository_name=${{ env.ECR_REPO_NAME }}' \ + -var 'lifecycle_policy=${{ inputs.lifecycle_policy_file }}' \ + -var 'iam_role=arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ inputs.github_iam_role }}' \ + -var 'aws_account_id=${{ inputs.aws_account_id }}' \ + -auto-approve + working-directory: infra/terraform + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + with: + registries: ${{ inputs.aws_account_id }} + + - name: Build, tag, and push image to Amazon ECR + id: build-publish + shell: bash + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: ${{ env.ECR_REPO_NAME }} + IMAGE_TAG: ${{ inputs.image_tag }} + run: | + # Get the current latest image digest (if it exists) + PREVIOUS_IMAGE_MANIFEST=$(aws ecr batch-get-image \ + --repository-name $ECR_REPOSITORY \ + --image-ids imageTag=$IMAGE_TAG \ + --output text \ + --query 'images[].imageManifest' || echo "") + + # Build the new image + docker build "${{ inputs.docker_build_dir }}" -f "${{ inputs.path_to_dockerfile }}" -t "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" + + # Push the new image as latest + docker push "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" + + # If there was a previous image, tag it as v1, v2, etc. + if [ ! -z "$PREVIOUS_IMAGE_MANIFEST" ]; then + # Get the current highest version number + CURRENT_VERSION=$(aws ecr describe-images \ + --repository-name $ECR_REPOSITORY \ + --query 'imageDetails[].imageTags[?starts_with(@, `v`)]' \ + --output text | grep -o 'v[0-9]*' | sed 's/v//' | sort -n | tail -1) + + # Calculate next version number + if [ -z "$CURRENT_VERSION" ]; then + NEW_VERSION="v1" + else + NEW_VERSION="v$((CURRENT_VERSION + 1))" + fi + + # Tag the previous image with the new version + aws ecr put-image \ + --repository-name $ECR_REPOSITORY \ + --image-tag $NEW_VERSION \ + --image-manifest "$PREVIOUS_IMAGE_MANIFEST" + + echo "Previous image tagged as $NEW_VERSION" + echo "version_tag=$NEW_VERSION" >> $GITHUB_OUTPUT + fi + + echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + echo "full_image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT diff --git a/apps/agent/src/components/PromptOrderSuggestion.tsx b/apps/agent/src/components/PromptOrderSuggestion.tsx new file mode 100644 index 00000000..7822e336 --- /dev/null +++ b/apps/agent/src/components/PromptOrderSuggestion.tsx @@ -0,0 +1,259 @@ +import { useState } from 'react' +import { Button, Modal, Drawer } from 'ui' +import { HiOutlineShoppingCart, HiOutlineSparkles } from 'react-icons/hi2' + +interface Item { + id: string + name: string + quantity: number + unit: string + price: number +} + +interface Supplier { + name: string + rating: number + totalOrders: number + onTimeDelivery: number +} + +interface PromptOrderSuggestionProps { + suggestion: { + items: Item[] + supplier: Supplier + deliveryDate: string + totalCost: number + } + onCompare: () => void +} + +// Mock suppliers data +const mockSuppliers = [ + { + id: 1, + name: "Global Foods Ltd", + rating: 4.8, + totalOrders: 1500, + onTimeDelivery: 98, + price: 42.50, + deliveryDate: "2025-01-10", + minOrderQuantity: 25 + }, + { + id: 2, + name: "Fresh Direct", + rating: 4.6, + totalOrders: 1200, + onTimeDelivery: 95, + price: 40.00, + deliveryDate: "2025-01-12", + minOrderQuantity: 20 + }, + { + id: 3, + name: "Premium Suppliers", + rating: 4.9, + totalOrders: 2000, + onTimeDelivery: 99, + price: 45.00, + deliveryDate: "2025-01-09", + minOrderQuantity: 30 + } +] + +interface SupplierCompareDrawerProps { + currentSupplier: Supplier +} + +function SupplierCompareDrawer({ currentSupplier }: SupplierCompareDrawerProps) { + return ( + +
+

Compare Suppliers

+

+ Compare different suppliers and choose the best option for your order. +

+
+ +
+ {mockSuppliers.map((supplier) => ( +
+
+
+

{supplier.name}

+
+

+ + {supplier.rating} ({supplier.totalOrders} orders) +

+

+ + {supplier.onTimeDelivery}% On-time delivery +

+
+
+
+

${supplier.price}

+

Min. order: {supplier.minOrderQuantity} units

+
+
+
+

Delivery by: {supplier.deliveryDate}

+
+ {supplier.name === currentSupplier.name && ( +
+ + Current Supplier + +
+ )} +
+ +
+
+ ))} +
+
+ ) +} + +export function PromptOrderSuggestion({ suggestion, onCompare }: PromptOrderSuggestionProps) { + const [isEditModalOpen, setIsEditModalOpen] = useState(false) + const [editedItem, setEditedItem] = useState(null) + + const handleEditItem = (item: Item) => { + setEditedItem(item) + setIsEditModalOpen(true) + } + + const handleSaveEdit = () => { + console.log('Saving edited item:', editedItem) + setIsEditModalOpen(false) + } + + return ( +
+

Suggested Order

+

Based on your request, here's what we suggest:

+ +
+

Supplier Information

+
+
+

Supplier

+

{suggestion.supplier.name}

+
+
+

Rating

+

{suggestion.supplier.rating}

+
+
+

Total Orders

+

{suggestion.supplier.totalOrders}

+
+
+

On-Time Delivery

+

{suggestion.supplier.onTimeDelivery}%

+
+
+
+ +
+

Delivery Date

+

{suggestion.deliveryDate}

+
+ + + + + + + + + + + + {suggestion.items.map((item) => ( + + + + + + + ))} + + + + + + +
ItemQuantityPriceActions
{item.name}{item.quantity} {item.unit}${item.price.toFixed(2)} + +
Total:${suggestion.totalCost.toFixed(2)}
+ +
+ + +
+ + setIsEditModalOpen(false)} + title="Edit Item" + > + {editedItem && ( +
+
+ + +
+
+ + setEditedItem({ ...editedItem, quantity: parseInt(e.target.value) })} + className="w-full p-2 border rounded-md" + /> +
+
+ + setEditedItem({ ...editedItem, price: parseFloat(e.target.value) })} + className="w-full p-2 border rounded-md" + /> +
+
+ + +
+
+ )} +
+
+ ) +} diff --git a/apps/agent/src/pages/orders/smart.tsx b/apps/agent/src/pages/orders/smart.tsx new file mode 100644 index 00000000..204b7dd6 --- /dev/null +++ b/apps/agent/src/pages/orders/smart.tsx @@ -0,0 +1,137 @@ +'use client' + +import { useState } from 'react' +import { Button, Card, Input } from "ui" +import { HiOutlineServer, HiOutlinePaperAirplane } from "react-icons/hi2" +import { useForm, SubmitHandler } from "react-hook-form" +import { z } from "zod" +import { zodResolver } from "@hookform/resolvers/zod" +import { PromptOrderSuggestion } from '@/components/PromptOrderSuggestion' + +const orderPromptSchema = z.object({ + prompt: z.string().min(1, "Please enter your order requirements"), + notes: z.string().optional() +}) + +type FormData = z.infer + +interface OrderSuggestion { + items: Array<{ id: string; name: string; quantity: number; unit: string; price: number }> + supplier: { + name: string + rating: number + totalOrders: number + onTimeDelivery: number + } + deliveryDate: string + totalCost: number +} + +export default function SmartOrdersPage() { + const [suggestion, setSuggestion] = useState(null) + const [loading, setLoading] = useState(false) + + const { + register, + handleSubmit, + formState: { errors } + } = useForm({ + resolver: zodResolver(orderPromptSchema) + }) + + const handleCompare = () => { + console.log('Comparing suppliers...') + } + + const onSubmit: SubmitHandler = async (data) => { + setLoading(true) + + try { + // Simulated API response + const mockResponse: OrderSuggestion = { + items: [ + { + id: "item_1", + name: "Premium Basmati Rice", + quantity: 50, + unit: "kgs", + price: 2.5 + } + ], + supplier: { + name: "Global Rice Suppliers", + rating: 4.8, + totalOrders: 1234, + onTimeDelivery: 98 + }, + deliveryDate: "2025-01-10", + totalCost: 125.00 + } + + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 1000)) + setSuggestion(mockResponse) + } catch (error) { + console.error('Error:', error) + } finally { + setLoading(false) + } + } + + return ( +
+ +
+

Quick Order

+

Tell us in plain language and we'll suggest an order for you.

+ +
+
+ + + {errors.prompt && ( +

{errors.prompt.message}

+ )} +
+ +
+ +