From c9bbeb34902e0b35cc20de0b8b87dc45e7345e2a Mon Sep 17 00:00:00 2001 From: Soham Dutta <19648293+NP-compete@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:58:41 +0530 Subject: [PATCH 001/114] FEAT: added deployment artifact (#4) --- deployment/mpp/README.md | 272 ++++++++++++++++++++++++++++++ deployment/mpp/buildconfig.yaml | 30 ++++ deployment/mpp/configmap.yaml | 14 ++ deployment/mpp/deployment.yaml | 86 ++++++++++ deployment/mpp/imagestream.yaml | 11 ++ deployment/mpp/kustomization.yaml | 15 ++ deployment/mpp/route.yaml | 19 +++ deployment/mpp/secret.yaml | 12 ++ deployment/mpp/service.yaml | 18 ++ deployment/mpp/tenant.yaml | 11 ++ 10 files changed, 488 insertions(+) create mode 100644 deployment/mpp/README.md create mode 100644 deployment/mpp/buildconfig.yaml create mode 100644 deployment/mpp/configmap.yaml create mode 100644 deployment/mpp/deployment.yaml create mode 100644 deployment/mpp/imagestream.yaml create mode 100644 deployment/mpp/kustomization.yaml create mode 100644 deployment/mpp/route.yaml create mode 100644 deployment/mpp/secret.yaml create mode 100644 deployment/mpp/service.yaml create mode 100644 deployment/mpp/tenant.yaml diff --git a/deployment/mpp/README.md b/deployment/mpp/README.md new file mode 100644 index 00000000..ef2299d0 --- /dev/null +++ b/deployment/mpp/README.md @@ -0,0 +1,272 @@ +# MPP (Managed Platform) Deployment + +This directory contains Kubernetes manifests for deploying the Template UI to Red Hat Managed Platform. + +## Prerequisites + +- Access to Red Hat Managed Platform cluster +- `kubectl` configured with cluster access +- Tenant name for your deployment +- Agent service deployed and accessible + +## Quick Start + +Deploy using the Makefile from the project root: + +```bash +# Deploy to MPP +make deploy mpp TENANT=ask-data + +# Remove deployment +make undeploy mpp TENANT=ask-data +``` + +## Configuration + +### Required Secrets + +Update `secret.yaml` before deploying: + +```yaml +stringData: + COOKIE_SIGN: "" # Generate a secure random key (min 32 chars) + SSO_CLIENT_ID: "" # Optional: SSO client ID + SSO_CLIENT_SECRET: "" # Optional: SSO client secret +``` + +### ConfigMap Settings + +Configure `configmap.yaml` for your environment: + +| Setting | Default | Description | +|---------|---------|-------------| +| `ENVIRONMENT` | `production` | Runtime environment (development, staging, production) | +| `AUTH_ENABLED` | `true` | Enable/disable SSO authentication | +| `SSO_ISSUER_HOST` | Red Hat SSO | SSO provider URL | +| `SSO_CALLBACK_URL` | - | OAuth callback URL | +| `AGENT_HOST` | - | Backend agent service URL | + +### Tenant Configuration + +Update `tenant.yaml` with your tenant information: + +```yaml +spec: + tenantId: "ask-data" # Your tenant ID + appCode: "ASKD-001" # Your application code + costCenter: "12345" # Your cost center +``` + +## Architecture + +### Resources Deployed + +| Resource | Name | Purpose | +|----------|------|---------| +| **BuildConfig** | template-ui | Build Node.js container from source | +| **ImageStream** | template-ui | Store built container images | +| **Deployment** | template-ui | Run UI application | +| **Service** | template-ui | Internal cluster service (port 8080) | +| **Route** | template-ui | External HTTPS access with TLS | +| **ConfigMap** | template-ui-config | Environment configuration | +| **Secret** | template-ui-secrets | Sensitive credentials | +| **Tenant** | ask-data | Multi-tenant configuration | + +### Network Configuration + +- **Service Port**: 8080 (HTTP) +- **Route**: HTTPS with edge termination +- **TLS**: Automatic certificate from platform + +## Deployment Process + +### Manual Deployment + +```bash +# Navigate to deployment directory +cd deployment/mpp + +# Update configuration files +# - secret.yaml: Add your secrets +# - configmap.yaml: Update AGENT_HOST and SSO settings +# - tenant.yaml: Set your tenant ID + +# Apply manifests +kubectl apply -k . + +# Verify deployment +kubectl get pods -l app=template-ui +kubectl logs -l app=template-ui --tail=50 +``` + +### Build Process + +The BuildConfig will: +1. Accept binary source upload +2. Build container using Containerfile (Node.js 24 Alpine) +3. Push to internal ImageStream +4. Trigger deployment rollout + +### Verify Deployment + +```bash +# Check pod status +kubectl get pods -l app=template-ui + +# Check pod logs +kubectl logs -l app=template-ui -f + +# Check route +kubectl get route template-ui + +# Test health endpoint +ROUTE_URL=$(kubectl get route template-ui -o jsonpath='{.spec.host}') +curl https://${ROUTE_URL}/health +``` + +Expected health response: +```json +{ + "status": "ok" +} +``` + +## Troubleshooting + +### Pod Fails to Start + +Check logs for errors: +```bash +kubectl logs -l app=template-ui --tail=100 +``` + +Common issues: +- Missing required secrets (COOKIE_SIGN) +- Invalid configuration values +- Agent backend unreachable +- SSO configuration errors (if AUTH_ENABLED=true) + +### Build Failures + +Check build logs: +```bash +kubectl logs -f bc/template-ui +``` + +Common issues: +- Containerfile syntax errors +- Missing dependencies in package.json +- Build timeout +- npm ci failures + +### Route Not Accessible + +Check route configuration: +```bash +kubectl describe route template-ui +``` + +Verify: +- TLS certificate is valid +- Route is admitted +- Service endpoints are available + +### Agent Connection Issues + +Check agent connectivity: +```bash +# From within the pod +kubectl exec -it deployment/template-ui -- sh +wget -O- $AGENT_HOST/health +``` + +Verify: +- AGENT_HOST URL is correct +- Agent service is running +- Network policies allow communication + +### Authentication Issues + +If SSO authentication fails: +```bash +# Check SSO configuration +kubectl exec -it deployment/template-ui -- sh +env | grep SSO +``` + +Verify: +- SSO_CLIENT_ID and SSO_CLIENT_SECRET are set +- SSO_CALLBACK_URL matches route hostname +- SSO_ISSUER_HOST is accessible + +## Cleanup + +Remove all resources: + +```bash +# Using Makefile +make undeploy mpp TENANT=ask-data + +# Or manually +kubectl delete -k deployment/mpp/ +``` + +## Security Considerations + +### Production Deployment Best Practices + +1. **Secrets Management**: + - Use external secrets operator + - Rotate credentials regularly + - Never commit secrets to git + - Use strong random keys for COOKIE_SIGN (minimum 32 characters) + +2. **Authentication**: + - Keep AUTH_ENABLED=true in production + - Use corporate SSO provider + - Verify SSO token validation + - Set secure cookie attributes + +3. **Network Policies**: + - Restrict ingress to authorized sources + - Use network policies for pod isolation + - Limit egress to agent service and SSO provider + +4. **Resource Limits**: + - Set appropriate CPU/memory limits + - Configure horizontal pod autoscaling for production + - Monitor resource usage + +5. **Monitoring & Logging**: + - Set up log aggregation + - Configure health check monitoring + - Enable metrics collection + - Monitor agent backend latency + +6. **TLS Configuration**: + - Use edge termination for frontend + - Verify backend agent uses HTTPS + - Monitor certificate expiration + +## Environment Variables + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `PORT` | Yes | `8080` | Server port | +| `ENVIRONMENT` | Yes | `production` | Runtime environment | +| `AUTH_ENABLED` | Yes | `true` | Enable authentication | +| `COOKIE_SIGN` | Yes | - | Cookie signing key (min 32 chars) | +| `SSO_CLIENT_ID` | If AUTH_ENABLED | - | SSO OAuth client ID | +| `SSO_CLIENT_SECRET` | If AUTH_ENABLED | - | SSO OAuth client secret | +| `SSO_ISSUER_HOST` | If AUTH_ENABLED | - | SSO provider base URL | +| `SSO_CALLBACK_URL` | If AUTH_ENABLED | - | OAuth callback URL | +| `AGENT_HOST` | Yes | - | Backend agent service URL | + +## Support + +For issues or questions: +- Check application logs: `kubectl logs -l app=template-ui` +- Review pod events: `kubectl describe pod -l app=template-ui` +- Test agent connectivity from pod +- Contact platform support team + diff --git a/deployment/mpp/buildconfig.yaml b/deployment/mpp/buildconfig.yaml new file mode 100644 index 00000000..93edb6bc --- /dev/null +++ b/deployment/mpp/buildconfig.yaml @@ -0,0 +1,30 @@ +apiVersion: build.openshift.io/v1 +kind: BuildConfig +metadata: + name: template-ui + labels: + app: template-ui + component: ui +spec: + successfulBuildsHistoryLimit: 1 + failedBuildsHistoryLimit: 1 + output: + to: + kind: ImageStreamTag + name: template-ui:latest + source: + type: Binary + binary: {} + strategy: + type: Docker + dockerStrategy: + dockerfilePath: Containerfile + resources: + requests: + memory: "4Gi" + cpu: "2000m" + limits: + memory: "8Gi" + cpu: "8" + triggers: [] + diff --git a/deployment/mpp/configmap.yaml b/deployment/mpp/configmap.yaml new file mode 100644 index 00000000..73294f0b --- /dev/null +++ b/deployment/mpp/configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: template-ui-config + labels: + app: template-ui + component: ui +data: + ENVIRONMENT: "production" + AUTH_ENABLED: "true" + SSO_ISSUER_HOST: "https://auth.redhat.com/auth/realms/EmployeeIDP" + SSO_CALLBACK_URL: "https://template-ui.apps.int.spoke.preprod.us-west-2.aws.paas.redhat.com/auth/callback/oidc" + AGENT_HOST: "https://template-agent.apps.int.spoke.preprod.us-west-2.aws.paas.redhat.com" + diff --git a/deployment/mpp/deployment.yaml b/deployment/mpp/deployment.yaml new file mode 100644 index 00000000..a47387e0 --- /dev/null +++ b/deployment/mpp/deployment.yaml @@ -0,0 +1,86 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: template-ui + labels: + app: template-ui + component: ui +spec: + replicas: 1 + selector: + matchLabels: + app: template-ui + component: ui + template: + metadata: + labels: + app: template-ui + component: ui + spec: + containers: + - name: template-ui + image: template-ui:latest + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http + protocol: TCP + env: + - name: PORT + value: "8080" + - name: ENVIRONMENT + valueFrom: + configMapKeyRef: + name: template-ui-config + key: ENVIRONMENT + - name: AUTH_ENABLED + valueFrom: + configMapKeyRef: + name: template-ui-config + key: AUTH_ENABLED + - name: SSO_ISSUER_HOST + valueFrom: + configMapKeyRef: + name: template-ui-config + key: SSO_ISSUER_HOST + optional: true + - name: SSO_CALLBACK_URL + valueFrom: + configMapKeyRef: + name: template-ui-config + key: SSO_CALLBACK_URL + optional: true + - name: AGENT_HOST + valueFrom: + configMapKeyRef: + name: template-ui-config + key: AGENT_HOST + - name: COOKIE_SIGN + valueFrom: + secretKeyRef: + name: template-ui-secrets + key: COOKIE_SIGN + - name: SSO_CLIENT_ID + valueFrom: + secretKeyRef: + name: template-ui-secrets + key: SSO_CLIENT_ID + optional: true + - name: SSO_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: template-ui-secrets + key: SSO_CLIENT_SECRET + optional: true + envFrom: + - secretRef: + name: template-ui-secrets + optional: true + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + restartPolicy: Always diff --git a/deployment/mpp/imagestream.yaml b/deployment/mpp/imagestream.yaml new file mode 100644 index 00000000..5f7d6a7a --- /dev/null +++ b/deployment/mpp/imagestream.yaml @@ -0,0 +1,11 @@ +apiVersion: image.openshift.io/v1 +kind: ImageStream +metadata: + name: template-ui + labels: + app: template-ui + component: ui +spec: + lookupPolicy: + local: true + diff --git a/deployment/mpp/kustomization.yaml b/deployment/mpp/kustomization.yaml new file mode 100644 index 00000000..7422cd57 --- /dev/null +++ b/deployment/mpp/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - buildconfig.yaml + - imagestream.yaml + - configmap.yaml + # - secret.yaml + - deployment.yaml + - service.yaml + - route.yaml +labels: + - pairs: + app: template-ui + component: ui + includeSelectors: true diff --git a/deployment/mpp/route.yaml b/deployment/mpp/route.yaml new file mode 100644 index 00000000..13bcbfda --- /dev/null +++ b/deployment/mpp/route.yaml @@ -0,0 +1,19 @@ +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: template-ui + labels: + app: template-ui + component: ui + shard: internal +spec: + host: template-ui.apps.int.spoke.preprod.us-west-2.aws.paas.redhat.com + to: + kind: Service + name: template-ui + port: + targetPort: http + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect + diff --git a/deployment/mpp/secret.yaml b/deployment/mpp/secret.yaml new file mode 100644 index 00000000..d0f35c72 --- /dev/null +++ b/deployment/mpp/secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: template-ui-secrets + labels: + app: template-ui + component: ui +type: Opaque +stringData: + COOKIE_SIGN: "" + SSO_CLIENT_ID: "" + SSO_CLIENT_SECRET: "" diff --git a/deployment/mpp/service.yaml b/deployment/mpp/service.yaml new file mode 100644 index 00000000..50820af4 --- /dev/null +++ b/deployment/mpp/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: template-ui + labels: + app: template-ui + component: ui +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: template-ui + component: ui + diff --git a/deployment/mpp/tenant.yaml b/deployment/mpp/tenant.yaml new file mode 100644 index 00000000..e479967b --- /dev/null +++ b/deployment/mpp/tenant.yaml @@ -0,0 +1,11 @@ +apiVersion: tenant.paas.redhat.com/v1alpha1 +kind: TenantNamespace +metadata: + name: template + namespace: ask-data--config +spec: + network: + security-zone: internal + rbac: all + type: runtime + From a54dc5d0cfb702d51573380fa1e1b547526beda8 Mon Sep 17 00:00:00 2001 From: Tuhin Sharma Date: Tue, 7 Apr 2026 11:49:57 +0530 Subject: [PATCH 002/114] ENH: UI improvements and deployment cleanup - Remove mpp deployment configuration - Rename email-dispatcher to publisher - Add tool icons and improve card UI with vertical scrolling - Rename "Tool execution" to "Tool" for clearer labeling - Add TodoListRenderer component for in-place todo list handling --- deployment/mpp/README.md | 272 ------------------ deployment/mpp/buildconfig.yaml | 30 -- deployment/mpp/configmap.yaml | 14 - deployment/mpp/deployment.yaml | 86 ------ deployment/mpp/imagestream.yaml | 11 - deployment/mpp/kustomization.yaml | 15 - deployment/mpp/route.yaml | 19 -- deployment/mpp/secret.yaml | 12 - deployment/mpp/service.yaml | 18 -- deployment/mpp/tenant.yaml | 11 - src/frontend/components/ChatMessagesView.tsx | 176 +++++++----- .../components/StreamEventRenderer.tsx | 25 +- src/frontend/components/TodoListRenderer.tsx | 76 +++++ src/frontend/lib/toolIcons.ts | 36 +++ 14 files changed, 231 insertions(+), 570 deletions(-) delete mode 100644 deployment/mpp/README.md delete mode 100644 deployment/mpp/buildconfig.yaml delete mode 100644 deployment/mpp/configmap.yaml delete mode 100644 deployment/mpp/deployment.yaml delete mode 100644 deployment/mpp/imagestream.yaml delete mode 100644 deployment/mpp/kustomization.yaml delete mode 100644 deployment/mpp/route.yaml delete mode 100644 deployment/mpp/secret.yaml delete mode 100644 deployment/mpp/service.yaml delete mode 100644 deployment/mpp/tenant.yaml create mode 100644 src/frontend/components/TodoListRenderer.tsx create mode 100644 src/frontend/lib/toolIcons.ts diff --git a/deployment/mpp/README.md b/deployment/mpp/README.md deleted file mode 100644 index ef2299d0..00000000 --- a/deployment/mpp/README.md +++ /dev/null @@ -1,272 +0,0 @@ -# MPP (Managed Platform) Deployment - -This directory contains Kubernetes manifests for deploying the Template UI to Red Hat Managed Platform. - -## Prerequisites - -- Access to Red Hat Managed Platform cluster -- `kubectl` configured with cluster access -- Tenant name for your deployment -- Agent service deployed and accessible - -## Quick Start - -Deploy using the Makefile from the project root: - -```bash -# Deploy to MPP -make deploy mpp TENANT=ask-data - -# Remove deployment -make undeploy mpp TENANT=ask-data -``` - -## Configuration - -### Required Secrets - -Update `secret.yaml` before deploying: - -```yaml -stringData: - COOKIE_SIGN: "" # Generate a secure random key (min 32 chars) - SSO_CLIENT_ID: "" # Optional: SSO client ID - SSO_CLIENT_SECRET: "" # Optional: SSO client secret -``` - -### ConfigMap Settings - -Configure `configmap.yaml` for your environment: - -| Setting | Default | Description | -|---------|---------|-------------| -| `ENVIRONMENT` | `production` | Runtime environment (development, staging, production) | -| `AUTH_ENABLED` | `true` | Enable/disable SSO authentication | -| `SSO_ISSUER_HOST` | Red Hat SSO | SSO provider URL | -| `SSO_CALLBACK_URL` | - | OAuth callback URL | -| `AGENT_HOST` | - | Backend agent service URL | - -### Tenant Configuration - -Update `tenant.yaml` with your tenant information: - -```yaml -spec: - tenantId: "ask-data" # Your tenant ID - appCode: "ASKD-001" # Your application code - costCenter: "12345" # Your cost center -``` - -## Architecture - -### Resources Deployed - -| Resource | Name | Purpose | -|----------|------|---------| -| **BuildConfig** | template-ui | Build Node.js container from source | -| **ImageStream** | template-ui | Store built container images | -| **Deployment** | template-ui | Run UI application | -| **Service** | template-ui | Internal cluster service (port 8080) | -| **Route** | template-ui | External HTTPS access with TLS | -| **ConfigMap** | template-ui-config | Environment configuration | -| **Secret** | template-ui-secrets | Sensitive credentials | -| **Tenant** | ask-data | Multi-tenant configuration | - -### Network Configuration - -- **Service Port**: 8080 (HTTP) -- **Route**: HTTPS with edge termination -- **TLS**: Automatic certificate from platform - -## Deployment Process - -### Manual Deployment - -```bash -# Navigate to deployment directory -cd deployment/mpp - -# Update configuration files -# - secret.yaml: Add your secrets -# - configmap.yaml: Update AGENT_HOST and SSO settings -# - tenant.yaml: Set your tenant ID - -# Apply manifests -kubectl apply -k . - -# Verify deployment -kubectl get pods -l app=template-ui -kubectl logs -l app=template-ui --tail=50 -``` - -### Build Process - -The BuildConfig will: -1. Accept binary source upload -2. Build container using Containerfile (Node.js 24 Alpine) -3. Push to internal ImageStream -4. Trigger deployment rollout - -### Verify Deployment - -```bash -# Check pod status -kubectl get pods -l app=template-ui - -# Check pod logs -kubectl logs -l app=template-ui -f - -# Check route -kubectl get route template-ui - -# Test health endpoint -ROUTE_URL=$(kubectl get route template-ui -o jsonpath='{.spec.host}') -curl https://${ROUTE_URL}/health -``` - -Expected health response: -```json -{ - "status": "ok" -} -``` - -## Troubleshooting - -### Pod Fails to Start - -Check logs for errors: -```bash -kubectl logs -l app=template-ui --tail=100 -``` - -Common issues: -- Missing required secrets (COOKIE_SIGN) -- Invalid configuration values -- Agent backend unreachable -- SSO configuration errors (if AUTH_ENABLED=true) - -### Build Failures - -Check build logs: -```bash -kubectl logs -f bc/template-ui -``` - -Common issues: -- Containerfile syntax errors -- Missing dependencies in package.json -- Build timeout -- npm ci failures - -### Route Not Accessible - -Check route configuration: -```bash -kubectl describe route template-ui -``` - -Verify: -- TLS certificate is valid -- Route is admitted -- Service endpoints are available - -### Agent Connection Issues - -Check agent connectivity: -```bash -# From within the pod -kubectl exec -it deployment/template-ui -- sh -wget -O- $AGENT_HOST/health -``` - -Verify: -- AGENT_HOST URL is correct -- Agent service is running -- Network policies allow communication - -### Authentication Issues - -If SSO authentication fails: -```bash -# Check SSO configuration -kubectl exec -it deployment/template-ui -- sh -env | grep SSO -``` - -Verify: -- SSO_CLIENT_ID and SSO_CLIENT_SECRET are set -- SSO_CALLBACK_URL matches route hostname -- SSO_ISSUER_HOST is accessible - -## Cleanup - -Remove all resources: - -```bash -# Using Makefile -make undeploy mpp TENANT=ask-data - -# Or manually -kubectl delete -k deployment/mpp/ -``` - -## Security Considerations - -### Production Deployment Best Practices - -1. **Secrets Management**: - - Use external secrets operator - - Rotate credentials regularly - - Never commit secrets to git - - Use strong random keys for COOKIE_SIGN (minimum 32 characters) - -2. **Authentication**: - - Keep AUTH_ENABLED=true in production - - Use corporate SSO provider - - Verify SSO token validation - - Set secure cookie attributes - -3. **Network Policies**: - - Restrict ingress to authorized sources - - Use network policies for pod isolation - - Limit egress to agent service and SSO provider - -4. **Resource Limits**: - - Set appropriate CPU/memory limits - - Configure horizontal pod autoscaling for production - - Monitor resource usage - -5. **Monitoring & Logging**: - - Set up log aggregation - - Configure health check monitoring - - Enable metrics collection - - Monitor agent backend latency - -6. **TLS Configuration**: - - Use edge termination for frontend - - Verify backend agent uses HTTPS - - Monitor certificate expiration - -## Environment Variables - -| Variable | Required | Default | Description | -|----------|----------|---------|-------------| -| `PORT` | Yes | `8080` | Server port | -| `ENVIRONMENT` | Yes | `production` | Runtime environment | -| `AUTH_ENABLED` | Yes | `true` | Enable authentication | -| `COOKIE_SIGN` | Yes | - | Cookie signing key (min 32 chars) | -| `SSO_CLIENT_ID` | If AUTH_ENABLED | - | SSO OAuth client ID | -| `SSO_CLIENT_SECRET` | If AUTH_ENABLED | - | SSO OAuth client secret | -| `SSO_ISSUER_HOST` | If AUTH_ENABLED | - | SSO provider base URL | -| `SSO_CALLBACK_URL` | If AUTH_ENABLED | - | OAuth callback URL | -| `AGENT_HOST` | Yes | - | Backend agent service URL | - -## Support - -For issues or questions: -- Check application logs: `kubectl logs -l app=template-ui` -- Review pod events: `kubectl describe pod -l app=template-ui` -- Test agent connectivity from pod -- Contact platform support team - diff --git a/deployment/mpp/buildconfig.yaml b/deployment/mpp/buildconfig.yaml deleted file mode 100644 index 93edb6bc..00000000 --- a/deployment/mpp/buildconfig.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: build.openshift.io/v1 -kind: BuildConfig -metadata: - name: template-ui - labels: - app: template-ui - component: ui -spec: - successfulBuildsHistoryLimit: 1 - failedBuildsHistoryLimit: 1 - output: - to: - kind: ImageStreamTag - name: template-ui:latest - source: - type: Binary - binary: {} - strategy: - type: Docker - dockerStrategy: - dockerfilePath: Containerfile - resources: - requests: - memory: "4Gi" - cpu: "2000m" - limits: - memory: "8Gi" - cpu: "8" - triggers: [] - diff --git a/deployment/mpp/configmap.yaml b/deployment/mpp/configmap.yaml deleted file mode 100644 index 73294f0b..00000000 --- a/deployment/mpp/configmap.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: template-ui-config - labels: - app: template-ui - component: ui -data: - ENVIRONMENT: "production" - AUTH_ENABLED: "true" - SSO_ISSUER_HOST: "https://auth.redhat.com/auth/realms/EmployeeIDP" - SSO_CALLBACK_URL: "https://template-ui.apps.int.spoke.preprod.us-west-2.aws.paas.redhat.com/auth/callback/oidc" - AGENT_HOST: "https://template-agent.apps.int.spoke.preprod.us-west-2.aws.paas.redhat.com" - diff --git a/deployment/mpp/deployment.yaml b/deployment/mpp/deployment.yaml deleted file mode 100644 index a47387e0..00000000 --- a/deployment/mpp/deployment.yaml +++ /dev/null @@ -1,86 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: template-ui - labels: - app: template-ui - component: ui -spec: - replicas: 1 - selector: - matchLabels: - app: template-ui - component: ui - template: - metadata: - labels: - app: template-ui - component: ui - spec: - containers: - - name: template-ui - image: template-ui:latest - imagePullPolicy: Always - ports: - - containerPort: 8080 - name: http - protocol: TCP - env: - - name: PORT - value: "8080" - - name: ENVIRONMENT - valueFrom: - configMapKeyRef: - name: template-ui-config - key: ENVIRONMENT - - name: AUTH_ENABLED - valueFrom: - configMapKeyRef: - name: template-ui-config - key: AUTH_ENABLED - - name: SSO_ISSUER_HOST - valueFrom: - configMapKeyRef: - name: template-ui-config - key: SSO_ISSUER_HOST - optional: true - - name: SSO_CALLBACK_URL - valueFrom: - configMapKeyRef: - name: template-ui-config - key: SSO_CALLBACK_URL - optional: true - - name: AGENT_HOST - valueFrom: - configMapKeyRef: - name: template-ui-config - key: AGENT_HOST - - name: COOKIE_SIGN - valueFrom: - secretKeyRef: - name: template-ui-secrets - key: COOKIE_SIGN - - name: SSO_CLIENT_ID - valueFrom: - secretKeyRef: - name: template-ui-secrets - key: SSO_CLIENT_ID - optional: true - - name: SSO_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: template-ui-secrets - key: SSO_CLIENT_SECRET - optional: true - envFrom: - - secretRef: - name: template-ui-secrets - optional: true - resources: - requests: - memory: "256Mi" - cpu: "100m" - limits: - memory: "512Mi" - cpu: "500m" - restartPolicy: Always diff --git a/deployment/mpp/imagestream.yaml b/deployment/mpp/imagestream.yaml deleted file mode 100644 index 5f7d6a7a..00000000 --- a/deployment/mpp/imagestream.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: image.openshift.io/v1 -kind: ImageStream -metadata: - name: template-ui - labels: - app: template-ui - component: ui -spec: - lookupPolicy: - local: true - diff --git a/deployment/mpp/kustomization.yaml b/deployment/mpp/kustomization.yaml deleted file mode 100644 index 7422cd57..00000000 --- a/deployment/mpp/kustomization.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - buildconfig.yaml - - imagestream.yaml - - configmap.yaml - # - secret.yaml - - deployment.yaml - - service.yaml - - route.yaml -labels: - - pairs: - app: template-ui - component: ui - includeSelectors: true diff --git a/deployment/mpp/route.yaml b/deployment/mpp/route.yaml deleted file mode 100644 index 13bcbfda..00000000 --- a/deployment/mpp/route.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: route.openshift.io/v1 -kind: Route -metadata: - name: template-ui - labels: - app: template-ui - component: ui - shard: internal -spec: - host: template-ui.apps.int.spoke.preprod.us-west-2.aws.paas.redhat.com - to: - kind: Service - name: template-ui - port: - targetPort: http - tls: - termination: edge - insecureEdgeTerminationPolicy: Redirect - diff --git a/deployment/mpp/secret.yaml b/deployment/mpp/secret.yaml deleted file mode 100644 index d0f35c72..00000000 --- a/deployment/mpp/secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: template-ui-secrets - labels: - app: template-ui - component: ui -type: Opaque -stringData: - COOKIE_SIGN: "" - SSO_CLIENT_ID: "" - SSO_CLIENT_SECRET: "" diff --git a/deployment/mpp/service.yaml b/deployment/mpp/service.yaml deleted file mode 100644 index 50820af4..00000000 --- a/deployment/mpp/service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: template-ui - labels: - app: template-ui - component: ui -spec: - type: ClusterIP - ports: - - port: 8080 - targetPort: 8080 - protocol: TCP - name: http - selector: - app: template-ui - component: ui - diff --git a/deployment/mpp/tenant.yaml b/deployment/mpp/tenant.yaml deleted file mode 100644 index e479967b..00000000 --- a/deployment/mpp/tenant.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: tenant.paas.redhat.com/v1alpha1 -kind: TenantNamespace -metadata: - name: template - namespace: ask-data--config -spec: - network: - security-zone: internal - rbac: all - type: runtime - diff --git a/src/frontend/components/ChatMessagesView.tsx b/src/frontend/components/ChatMessagesView.tsx index 24994437..cb3c3fb3 100644 --- a/src/frontend/components/ChatMessagesView.tsx +++ b/src/frontend/components/ChatMessagesView.tsx @@ -1,7 +1,8 @@ import type React from "react"; import type { Message } from "@langchain/langgraph-sdk"; import { ScrollArea } from "./ui/scroll-area"; -import { CheckCircle, ChevronDown, ChevronRight, Copy, CopyCheck, Loader2, Settings } from "lucide-react"; +import { CheckCircle, ChevronDown, ChevronRight, Copy, CopyCheck, Loader2 } from "lucide-react"; +import { getToolIcon, getToolLabel } from "../lib/toolIcons"; import { InputForm } from "./InputForm"; import { useState, ReactNode, useMemo } from "react"; import { cn } from "../lib/utils"; @@ -11,6 +12,8 @@ import { } from "./ActivityTimeline"; import { StreamEvent } from "../hooks/useDataStream"; import ReactMarkdown from "react-markdown"; +import { TodoListRenderer, isWriteTodosCall, extractTodos } from "./TodoListRenderer"; +import type { TodoItem } from "./TodoListRenderer"; // Markdown component props type from former ReportView type MdComponentProps = { @@ -218,11 +221,16 @@ interface ChatMessagesViewProps { historicalActivities: Record; } -export function AIMessageRenderer({ message }: { message: Message }) { +interface AIMessageRendererProps { + message: Message; + latestTodos?: TodoItem[]; + skipWriteTodos?: boolean; +} + +export function AIMessageRenderer({ message, latestTodos, skipWriteTodos }: AIMessageRendererProps) { const [expandedItems, setExpandedItems] = useState>(new Set()); const toggleExpand = (itemId: string) => { - console.log('toggleExpand : ', itemId); setExpandedItems(prev => { const newSet = new Set(prev); if (newSet.has(itemId)) { @@ -241,63 +249,70 @@ export function AIMessageRenderer({ message }: { message: Message }) { const isNormalMessage = message.type === 'ai' && (!Array.isArray(message?.tool_calls) || message?.tool_calls?.length === 0); if (isToolCallStart) { - // const color = (toolCall as any).content ? 'green' : 'blue'; - return ( - <> - { - message.tool_calls?.map((toolCall, idx) => ( -
- - - {expandedItems.has(`${message.id}-${idx}`) && ( -
-
Arguments:
-
-                      {JSON.stringify(toolCall.args, null, 2)}
-                    
-
- { - (toolCall as any).content ? 'Result:' : 'Running...:' - } -
-
-                      {JSON.stringify((toolCall as any).content, null, 2)}
-                    
-
- )} + const toolCalls = message.tool_calls ?? []; + const nonTodoToolCalls = toolCalls.filter(tc => !isWriteTodosCall(tc)); + const hasTodoCall = toolCalls.some(tc => isWriteTodosCall(tc)); + + const elements: React.ReactNode[] = []; + + if (hasTodoCall && latestTodos && !skipWriteTodos) { + elements.push( + + ); + } + + for (let idx = 0; idx < nonTodoToolCalls.length; idx++) { + const toolCall = nonTodoToolCalls[idx]; + const ToolIcon = getToolIcon(toolCall.name); + elements.push( +
+ + + {expandedItems.has(`${message.id}-${idx}`) && ( +
+
Arguments:
+
+                  {JSON.stringify(toolCall.args, null, 2)}
+                
+
+ { + (toolCall as any).content ? 'Result:' : 'Running...:' + } +
+
+                  {JSON.stringify((toolCall as any).content, null, 2)}
+                
+
+ )} +
+ ); + } + + if (elements.length === 0) return null; + return <>{elements}; } @@ -330,7 +345,7 @@ export function AIMessageRenderer({ message }: { message: Message }) { {expandedItems.has(message.id || '') && (
Result:
-
+                
                   {typeof message.content === 'string' ? message.content : JSON.stringify(message.content, null, 2)}
                 
@@ -346,19 +361,12 @@ export function AIMessageRenderer({ message }: { message: Message }) { ); } - }, [JSON.stringify(message), expandedItems]); + }, [JSON.stringify(message), expandedItems, latestTodos, skipWriteTodos]); return (
{renderMessage} - - {/* {isLoading && ( -
- - Processing... -
- )} */}
); } @@ -378,16 +386,44 @@ export function ChatMessagesView({ try { await navigator.clipboard.writeText(text); setCopiedMessageId(messageId); - setTimeout(() => setCopiedMessageId(null), 2000); // Reset after 2 seconds + setTimeout(() => setCopiedMessageId(null), 2000); } catch (err) { console.error("Failed to copy text: ", err); } }; + + const todoMeta = useMemo(() => { + let firstTodoIndex = -1; + let latestTodos: TodoItem[] = []; + const writeTodosMsgIndices = new Set(); + + messages.forEach((msg, idx) => { + if (msg.type === 'ai' && Array.isArray(msg.tool_calls)) { + for (const tc of msg.tool_calls) { + if (isWriteTodosCall(tc)) { + if (firstTodoIndex === -1) firstTodoIndex = idx; + writeTodosMsgIndices.add(idx); + latestTodos = extractTodos(tc); + } + } + } + }); + + return { firstTodoIndex, latestTodos, writeTodosMsgIndices }; + }, [messages]); + return (
{messages.map((message, index) => { + if (message.type === 'tool' && message.name === 'write_todos') { + return null; + } + + const isFirstTodo = index === todoMeta.firstTodoIndex; + const isLaterTodo = todoMeta.writeTodosMsgIndices.has(index) && !isFirstTodo; + return (
)} diff --git a/src/frontend/components/StreamEventRenderer.tsx b/src/frontend/components/StreamEventRenderer.tsx index 37d09a39..44a9dc9b 100644 --- a/src/frontend/components/StreamEventRenderer.tsx +++ b/src/frontend/components/StreamEventRenderer.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { StreamEvent, ToolCall } from "../hooks/useDataStream"; import { Brain, - Settings, CheckCircle, ChevronDown, ChevronRight, @@ -10,6 +9,7 @@ import { Play, Zap, } from "lucide-react"; +import { getToolIcon, getToolLabel } from "../lib/toolIcons"; import ReactMarkdown from "react-markdown"; interface StreamEventRendererProps { @@ -114,21 +114,19 @@ export function StreamEventRenderer({ events, isLoading }: StreamEventRendererPr case 'tool_call': const isExpanded = expandedItems.has(event.id); return ( - event.tool_calls?.map((toolCall: ToolCall) => ( + event.tool_calls?.map((toolCall: ToolCall) => { + const ToolIcon = getToolIcon(toolCall.name); + return (
- {expandedItems.has(`${message.id}-${idx}`) ? ( + {expandedItems.has(`${stableId}-${idx}`) ? ( ) : ( )} - {expandedItems.has(`${message.id}-${idx}`) && ( + {expandedItems.has(`${stableId}-${idx}`) && (
Arguments:

From 8fbf12d246044cd75e0fb0834289cf0816d89ab3 Mon Sep 17 00:00:00 2001
From: Tuhin Sharma 
Date: Sat, 11 Apr 2026 01:18:57 +0530
Subject: [PATCH 004/114] REFACTOR: Use centralized helper for write_todos
 result check

Replace hardcoded string check with isWriteTodosResult() helper that uses
the TOOL_NAME constant. Ensures consistency and prevents missed updates if
the tool name changes.
---
 src/frontend/components/ChatMessagesView.tsx | 4 ++--
 src/frontend/components/TodoListRenderer.tsx | 4 ++++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/frontend/components/ChatMessagesView.tsx b/src/frontend/components/ChatMessagesView.tsx
index de0ec548..b8a021e4 100644
--- a/src/frontend/components/ChatMessagesView.tsx
+++ b/src/frontend/components/ChatMessagesView.tsx
@@ -12,7 +12,7 @@ import {
 } from "./ActivityTimeline";
 import { StreamEvent } from "../hooks/useDataStream";
 import ReactMarkdown from "react-markdown";
-import { TodoListRenderer, isWriteTodosCall, extractTodos } from "./TodoListRenderer";
+import { TodoListRenderer, isWriteTodosCall, isWriteTodosResult, extractTodos } from "./TodoListRenderer";
 import type { TodoItem } from "./TodoListRenderer";
 
 // Markdown component props type from former ReportView
@@ -418,7 +418,7 @@ export function ChatMessagesView({
       
         
{messages.map((message, index) => { - if (message.type === 'tool' && message.name === 'write_todos') { + if (isWriteTodosResult(message)) { return null; } diff --git a/src/frontend/components/TodoListRenderer.tsx b/src/frontend/components/TodoListRenderer.tsx index 33dca0fd..d3f03fae 100644 --- a/src/frontend/components/TodoListRenderer.tsx +++ b/src/frontend/components/TodoListRenderer.tsx @@ -12,6 +12,10 @@ export function isWriteTodosCall(toolCall: { name: string }): boolean { return toolCall.name === TOOL_NAME; } +export function isWriteTodosResult(message: { type: string; name?: string }): boolean { + return message.type === 'tool' && message.name === TOOL_NAME; +} + export function extractTodos(toolCall: { args: Record }): TodoItem[] { const args = toolCall.args as Record; if (Array.isArray(args?.todos)) { From 938acd7b2cba07a75c4eeb70d76db22f27e7e8f5 Mon Sep 17 00:00:00 2001 From: Tuhin Sharma Date: Sat, 11 Apr 2026 01:20:03 +0530 Subject: [PATCH 005/114] FIX: Validate todo items before rendering Add type guard to filter out malformed todo items from backend. Ensures each item has required content and status fields before rendering to prevent blank list items or unexpected behavior. --- src/frontend/components/TodoListRenderer.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/frontend/components/TodoListRenderer.tsx b/src/frontend/components/TodoListRenderer.tsx index d3f03fae..dca857b8 100644 --- a/src/frontend/components/TodoListRenderer.tsx +++ b/src/frontend/components/TodoListRenderer.tsx @@ -16,10 +16,19 @@ export function isWriteTodosResult(message: { type: string; name?: string }): bo return message.type === 'tool' && message.name === TOOL_NAME; } +function isValidTodoItem(item: unknown): item is TodoItem { + return ( + typeof item === 'object' && + item !== null && + typeof (item as any).content === 'string' && + typeof (item as any).status === 'string' + ); +} + export function extractTodos(toolCall: { args: Record }): TodoItem[] { const args = toolCall.args as Record; if (Array.isArray(args?.todos)) { - return args.todos as TodoItem[]; + return args.todos.filter(isValidTodoItem); } return []; } From d63224932fb6793c049c14c6db87460172794d57 Mon Sep 17 00:00:00 2001 From: Tuhin Sharma Date: Sat, 11 Apr 2026 01:22:16 +0530 Subject: [PATCH 006/114] DOCS: Document todo aggregation strategy Add comment explaining that the latest todo state is displayed at the first write_todos position while hiding intermediate updates. This provides a single live-updating todo list rather than scattered snapshots throughout the conversation. --- src/frontend/components/ChatMessagesView.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/frontend/components/ChatMessagesView.tsx b/src/frontend/components/ChatMessagesView.tsx index b8a021e4..c0f45f87 100644 --- a/src/frontend/components/ChatMessagesView.tsx +++ b/src/frontend/components/ChatMessagesView.tsx @@ -393,6 +393,10 @@ export function ChatMessagesView({ } }; + // Aggregation strategy for write_todos calls: + // - Show the LATEST todo state at the FIRST write_todos position + // - Hide all intermediate updates to avoid multiple todo lists in conversation + // - UX: User sees a single, live-updating todo list rather than scattered snapshots const todoMeta = useMemo(() => { let firstTodoIndex = -1; let latestTodos: TodoItem[] = []; From c273d882b30fc59a01fb2e009473a8fa22c3ab49 Mon Sep 17 00:00:00 2001 From: Tuhin Sharma Date: Sat, 11 Apr 2026 01:30:02 +0530 Subject: [PATCH 007/114] REFACTOR: Remove domain-specific tool icons Remove hardcoded analyst, trainer, publisher, and dietician entries to make template more generic. Keep only common tools (ls, read_file, execute) and add comment for customization. --- src/frontend/lib/toolIcons.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/frontend/lib/toolIcons.ts b/src/frontend/lib/toolIcons.ts index 1536a925..dbcbe426 100644 --- a/src/frontend/lib/toolIcons.ts +++ b/src/frontend/lib/toolIcons.ts @@ -2,10 +2,6 @@ import { FolderOpen, FileText, Terminal, - HeartPulse, - Dumbbell, - Mail, - UtensilsCrossed, Settings, type LucideIcon, } from "lucide-react"; @@ -14,21 +10,14 @@ const toolIconMap: Record = { ls: FolderOpen, read_file: FileText, execute: Terminal, - analyst: HeartPulse, - trainer: Dumbbell, - publisher: Mail, - dietician: UtensilsCrossed, }; export function getToolIcon(toolName: string): LucideIcon { return toolIconMap[toolName] ?? Settings; } -const subagentNames = new Set([ - "analyst", - "trainer", - "publisher", - "dietician", +const subagentNames = new Set([ + // Add your subagent names here ]); export function getToolLabel(toolName: string): string { From b9b56793b1a17aaf803da5ea9bc62af97afd13c1 Mon Sep 17 00:00:00 2001 From: Tuhin Sharma Date: Sat, 11 Apr 2026 02:33:13 +0530 Subject: [PATCH 008/114] Remove generic Tool label from tool call cards getToolLabel returns empty for standard tools; Subagent suffix remains when configured. Chat and stream views only show the extra label when non-empty. Made-with: Cursor --- src/frontend/components/ChatMessagesView.tsx | 5 ++++- src/frontend/components/StreamEventRenderer.tsx | 5 ++++- src/frontend/lib/toolIcons.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/frontend/components/ChatMessagesView.tsx b/src/frontend/components/ChatMessagesView.tsx index c0f45f87..f180d954 100644 --- a/src/frontend/components/ChatMessagesView.tsx +++ b/src/frontend/components/ChatMessagesView.tsx @@ -264,6 +264,7 @@ export function AIMessageRenderer({ message, latestTodos, skipWriteTodos }: AIMe for (let idx = 0; idx < nonTodoToolCalls.length; idx++) { const toolCall = nonTodoToolCalls[idx]; const ToolIcon = getToolIcon(toolCall.name); + const toolKindLabel = getToolLabel(toolCall.name); const stableId = message.id || 'tc'; elements.push(
@@ -275,7 +276,9 @@ export function AIMessageRenderer({ message, latestTodos, skipWriteTodos }: AIMe
{toolCall.name} - • {getToolLabel(toolCall.name)} + {toolKindLabel ? ( + • {toolKindLabel} + ) : null} { (toolCall as any).content ? ( diff --git a/src/frontend/components/StreamEventRenderer.tsx b/src/frontend/components/StreamEventRenderer.tsx index 44a9dc9b..fc130add 100644 --- a/src/frontend/components/StreamEventRenderer.tsx +++ b/src/frontend/components/StreamEventRenderer.tsx @@ -116,6 +116,7 @@ export function StreamEventRenderer({ events, isLoading }: StreamEventRendererPr return ( event.tool_calls?.map((toolCall: ToolCall) => { const ToolIcon = getToolIcon(toolCall.name); + const toolKindLabel = getToolLabel(toolCall.name); return (
{isExpanded ? ( diff --git a/src/frontend/lib/toolIcons.ts b/src/frontend/lib/toolIcons.ts index dbcbe426..097526d5 100644 --- a/src/frontend/lib/toolIcons.ts +++ b/src/frontend/lib/toolIcons.ts @@ -21,5 +21,5 @@ const subagentNames = new Set([ ]); export function getToolLabel(toolName: string): string { - return subagentNames.has(toolName) ? "Subagent" : "Tool"; + return subagentNames.has(toolName) ? "Subagent" : ""; } From aa2dd11ef656dcfaecabfba9d712c8a10199a33f Mon Sep 17 00:00:00 2001 From: Tuhin Sharma Date: Sat, 11 Apr 2026 03:03:39 +0530 Subject: [PATCH 009/114] FIX: Prevent rendering empty message containers ChatMessagesView: - Add explicit null return for message type fall-through - Return null before wrapper when renderMessage is null - Move width wrapper inside AIMessageRenderer - Check content before rendering parent wrapper divs StreamEventRenderer: - Guard against empty/undefined tool_calls array - Filter null values from rendered events - Skip wrapper div when all events render as null - Add braces to case blocks for ESLint Ensures no empty divs with spacing are rendered, eliminating blank lines. --- src/frontend/components/ChatMessagesView.tsx | 39 +++++++++++-------- .../components/StreamEventRenderer.tsx | 15 +++++-- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/frontend/components/ChatMessagesView.tsx b/src/frontend/components/ChatMessagesView.tsx index f180d954..842df742 100644 --- a/src/frontend/components/ChatMessagesView.tsx +++ b/src/frontend/components/ChatMessagesView.tsx @@ -365,12 +365,17 @@ export function AIMessageRenderer({ message, latestTodos, skipWriteTodos }: AIMe ); } + return null; + }, [JSON.stringify(message), expandedItems, latestTodos, skipWriteTodos]); + if (!renderMessage) return null; return ( -
- {renderMessage} +
+
+ {renderMessage} +
); } @@ -432,26 +437,28 @@ export function ChatMessagesView({ const isFirstTodo = index === todoMeta.firstTodoIndex; const isLaterTodo = todoMeta.writeTodosMsgIndices.has(index) && !isFirstTodo; + const content = message.type === "human" ? ( + + ) : ( + + ); + + if (!content) return null; + return (
- {message.type === "human" ? ( - - ) : ( -
- -
- )} + {content}
); diff --git a/src/frontend/components/StreamEventRenderer.tsx b/src/frontend/components/StreamEventRenderer.tsx index fc130add..47596e79 100644 --- a/src/frontend/components/StreamEventRenderer.tsx +++ b/src/frontend/components/StreamEventRenderer.tsx @@ -111,10 +111,11 @@ export function StreamEventRenderer({ events, isLoading }: StreamEventRendererPr
); - case 'tool_call': + case 'tool_call': { const isExpanded = expandedItems.has(event.id); + if (!event.tool_calls || event.tool_calls.length === 0) return null; return ( - event.tool_calls?.map((toolCall: ToolCall) => { + event.tool_calls.map((toolCall: ToolCall) => { const ToolIcon = getToolIcon(toolCall.name); const toolKindLabel = getToolLabel(toolCall.name); return ( @@ -151,8 +152,9 @@ export function StreamEventRenderer({ events, isLoading }: StreamEventRendererPr ); }) ); + } - case 'tool_result': + case 'tool_result': { const resultExpanded = expandedItems.has(event.id); return (
@@ -188,6 +190,7 @@ export function StreamEventRenderer({ events, isLoading }: StreamEventRendererPr )}
); + } case 'token_group': return ( @@ -270,9 +273,13 @@ export function StreamEventRenderer({ events, isLoading }: StreamEventRendererPr if (events.length === 0 && !isLoading) return null; + const renderedEvents = processedEvents.map(renderEvent).filter(Boolean); + + if (renderedEvents.length === 0 && !isLoading) return null; + return (
- {processedEvents.map(renderEvent)} + {renderedEvents} {isLoading && (
From 9970e186f12264b82436c05f0ecc3e09c107be42 Mon Sep 17 00:00:00 2001 From: Tuhin Sharma Date: Sat, 11 Apr 2026 03:14:00 +0530 Subject: [PATCH 010/114] REFACTOR: Remove unused getToolLabel function and subagent set Remove subagentNames set and getToolLabel function as they're no longer used after removing tool/subagent labels from the UI. Clean up all imports and usages across ChatMessagesView and StreamEventRenderer. --- src/frontend/components/ChatMessagesView.tsx | 6 +----- src/frontend/components/StreamEventRenderer.tsx | 6 +----- src/frontend/lib/toolIcons.ts | 8 -------- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/frontend/components/ChatMessagesView.tsx b/src/frontend/components/ChatMessagesView.tsx index 842df742..c1a8ab48 100644 --- a/src/frontend/components/ChatMessagesView.tsx +++ b/src/frontend/components/ChatMessagesView.tsx @@ -2,7 +2,7 @@ import type React from "react"; import type { Message } from "@langchain/langgraph-sdk"; import { ScrollArea } from "./ui/scroll-area"; import { CheckCircle, ChevronDown, ChevronRight, Copy, CopyCheck, Loader2 } from "lucide-react"; -import { getToolIcon, getToolLabel } from "../lib/toolIcons"; +import { getToolIcon } from "../lib/toolIcons"; import { InputForm } from "./InputForm"; import { useState, ReactNode, useMemo } from "react"; import { cn } from "../lib/utils"; @@ -264,7 +264,6 @@ export function AIMessageRenderer({ message, latestTodos, skipWriteTodos }: AIMe for (let idx = 0; idx < nonTodoToolCalls.length; idx++) { const toolCall = nonTodoToolCalls[idx]; const ToolIcon = getToolIcon(toolCall.name); - const toolKindLabel = getToolLabel(toolCall.name); const stableId = message.id || 'tc'; elements.push(
@@ -276,9 +275,6 @@ export function AIMessageRenderer({ message, latestTodos, skipWriteTodos }: AIMe
{toolCall.name} - {toolKindLabel ? ( - • {toolKindLabel} - ) : null} { (toolCall as any).content ? ( diff --git a/src/frontend/components/StreamEventRenderer.tsx b/src/frontend/components/StreamEventRenderer.tsx index 47596e79..979c012f 100644 --- a/src/frontend/components/StreamEventRenderer.tsx +++ b/src/frontend/components/StreamEventRenderer.tsx @@ -9,7 +9,7 @@ import { Play, Zap, } from "lucide-react"; -import { getToolIcon, getToolLabel } from "../lib/toolIcons"; +import { getToolIcon } from "../lib/toolIcons"; import ReactMarkdown from "react-markdown"; interface StreamEventRendererProps { @@ -117,7 +117,6 @@ export function StreamEventRenderer({ events, isLoading }: StreamEventRendererPr return ( event.tool_calls.map((toolCall: ToolCall) => { const ToolIcon = getToolIcon(toolCall.name); - const toolKindLabel = getToolLabel(toolCall.name); return (
{isExpanded ? ( diff --git a/src/frontend/lib/toolIcons.ts b/src/frontend/lib/toolIcons.ts index 097526d5..b8fdea05 100644 --- a/src/frontend/lib/toolIcons.ts +++ b/src/frontend/lib/toolIcons.ts @@ -15,11 +15,3 @@ const toolIconMap: Record = { export function getToolIcon(toolName: string): LucideIcon { return toolIconMap[toolName] ?? Settings; } - -const subagentNames = new Set([ - // Add your subagent names here -]); - -export function getToolLabel(toolName: string): string { - return subagentNames.has(toolName) ? "Subagent" : ""; -} From 0b645eb4c296136a459ad4d32ec1927275799da7 Mon Sep 17 00:00:00 2001 From: Tuhin Sharma Date: Sat, 11 Apr 2026 03:29:00 +0530 Subject: [PATCH 011/114] CONFIG: Update application port from 8080 to 5003 --- Containerfile | 2 -- compose.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Containerfile b/Containerfile index deb0def0..8978f100 100644 --- a/Containerfile +++ b/Containerfile @@ -18,6 +18,4 @@ USER 1001 RUN npm ci && npm run build -EXPOSE 8080 - CMD ["node", "dist/server/index.js"] diff --git a/compose.yml b/compose.yml index 7cd52d29..7ea57252 100644 --- a/compose.yml +++ b/compose.yml @@ -7,7 +7,7 @@ services: dockerfile: Containerfile container_name: template-ui-app ports: - - "8080:8080" + - "5003:5003" environment: - NODE_ENV=production env_file: From 0b539b3c9ee05bff0a0559c7e335718be4a29a16 Mon Sep 17 00:00:00 2001 From: Tuhin Sharma Date: Tue, 14 Apr 2026 17:18:07 +0530 Subject: [PATCH 012/114] FIX: Use unique keys for tool call map items Each tool call in an event now has a unique key and independent expand/collapse state. Co-authored-by: mimran-khan --- src/frontend/components/StreamEventRenderer.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/frontend/components/StreamEventRenderer.tsx b/src/frontend/components/StreamEventRenderer.tsx index 979c012f..7a34780c 100644 --- a/src/frontend/components/StreamEventRenderer.tsx +++ b/src/frontend/components/StreamEventRenderer.tsx @@ -112,15 +112,16 @@ export function StreamEventRenderer({ events, isLoading }: StreamEventRendererPr ); case 'tool_call': { - const isExpanded = expandedItems.has(event.id); if (!event.tool_calls || event.tool_calls.length === 0) return null; return ( - event.tool_calls.map((toolCall: ToolCall) => { + event.tool_calls.map((toolCall: ToolCall, index: number) => { + const toolCallId = `${event.id}-${index}`; + const isExpanded = expandedItems.has(toolCallId); const ToolIcon = getToolIcon(toolCall.name); return ( -
+
+ {traceId && ( + <> + + + + )} +
); }; @@ -224,9 +263,12 @@ interface ChatMessagesViewProps { interface AIMessageRendererProps { message: Message; latestTodos?: TodoItem[]; + handleCopy?: (text: string, messageId: string) => void; + copiedMessageId?: string | null; + onFeedback?: (messageId: string, traceId: string, feedbackType: "positive" | "negative") => void; } -export function AIMessageRenderer({ message, latestTodos }: AIMessageRendererProps) { +export function AIMessageRenderer({ message, latestTodos, handleCopy, copiedMessageId, onFeedback }: AIMessageRendererProps) { const [expandedItems, setExpandedItems] = useState>(new Set()); const toggleExpand = (itemId: string) => { @@ -356,7 +398,12 @@ export function AIMessageRenderer({ message, latestTodos }: AIMessageRendererPro if (isNormalMessage) { return ( - + ); } @@ -385,6 +432,17 @@ export function ChatMessagesView({ onCancel, }: ChatMessagesViewProps) { const [copiedMessageId, setCopiedMessageId] = useState(null); + const [feedbackModal, setFeedbackModal] = useState<{ + isOpen: boolean; + messageId: string; + traceId: string; + feedbackType: "positive" | "negative"; + }>({ + isOpen: false, + messageId: "", + traceId: "", + feedbackType: "positive", + }); const handleCopy = async (text: string, messageId: string) => { try { @@ -396,6 +454,44 @@ export function ChatMessagesView({ } }; + const handleFeedback = (messageId: string, traceId: string, feedbackType: "positive" | "negative") => { + if (!traceId) { + console.error("Cannot submit feedback: trace_id is missing from message"); + return; + } + + setFeedbackModal({ + isOpen: true, + messageId, + traceId, + feedbackType, + }); + }; + + const handleFeedbackSubmit = async (traceId: string, feedbackType: "positive" | "negative", comment: string) => { + if (!traceId) { + console.error("Cannot submit feedback: trace_id is missing"); + return; + } + + try { + const value = feedbackType === "positive" ? 1 : 0; + await submitFeedback(traceId, value, comment); + console.log("Feedback submitted successfully:", { traceId, feedbackType, comment }); + } catch (error) { + console.error("Failed to submit feedback:", error); + } + }; + + const handleFeedbackClose = () => { + setFeedbackModal({ + isOpen: false, + messageId: "", + traceId: "", + feedbackType: "positive", + }); + }; + // Strategy for write_todos calls per user turn: // - Group messages by conversation turn (based on human messages) // - For each turn, show only the LATEST todo state @@ -467,6 +563,9 @@ export function ChatMessagesView({ ); @@ -531,6 +630,14 @@ export function ChatMessagesView({ onCancel={onCancel} hasHistory={messages.length > 0} /> +
); } diff --git a/src/frontend/components/FeedbackModal.tsx b/src/frontend/components/FeedbackModal.tsx new file mode 100644 index 00000000..ffe61041 --- /dev/null +++ b/src/frontend/components/FeedbackModal.tsx @@ -0,0 +1,75 @@ +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "./ui/dialog"; +import { Button } from "./ui/button"; +import { Textarea } from "./ui/textarea"; + +interface FeedbackModalProps { + isOpen: boolean; + onClose: () => void; + feedbackType: "positive" | "negative"; + messageId: string; + traceId: string; + onSubmit: (traceId: string, feedbackType: "positive" | "negative", comment: string) => void; +} + +export function FeedbackModal({ + isOpen, + onClose, + feedbackType, + messageId, + traceId, + onSubmit, +}: FeedbackModalProps) { + const [comment, setComment] = useState(""); + + const handleSubmit = () => { + onSubmit(traceId, feedbackType, comment); + setComment(""); + onClose(); + }; + + const handleCancel = () => { + setComment(""); + onClose(); + }; + + return ( + + + + + {feedbackType === "positive" ? "Provide positive feedback" : "Provide feedback"} + + + {feedbackType === "positive" + ? "What did you like about this response?" + : "What could be improved about this response?"} + + +
+