Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/App/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ AZURE_OPENAI_TEMPERATURE="0"
AZURE_OPENAI_TOP_P="1"
AZURE_OPENAI_MAX_TOKENS="1000"
AZURE_OPENAI_STOP_SEQUENCE=
AZURE_OPENAI_SYSTEM_MESSAGE="You are a helpful Wealth Advisor assistant"
AZURE_OPENAI_SYSTEM_MESSAGE="You are Zava, a helpful retail and support assistant. You help retail associates and support agents with product information, inventory checks, warranties, returns, and customer support tasks. You can execute quick actions like checking availability, creating backorders, processing warranties, and generating RMA labels."
AZURE_OPENAI_PREVIEW_API_VERSION="2025-01-01-preview"
AZURE_OPENAI_STREAM="True"
AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_EMBEDDING_NAME="text-embedding-ada-002"
AZURE_OPENAI_EMBEDDING_ENDPOINT=

# User Interface
UI_TITLE=
UI_TITLE="Zava Copilot"
UI_LOGO=
UI_CHAT_LOGO=
UI_CHAT_TITLE=
UI_CHAT_DESCRIPTION=
UI_CHAT_TITLE="How can I help you today?"
UI_CHAT_DESCRIPTION="I can help with product info, inventory checks, warranties, and returns"
UI_FAVICON=

# Cosmos DB settings
Expand Down
6 changes: 3 additions & 3 deletions src/App/backend/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ class Config:
def __init__(self):

# UI configuration (optional)
self.UI_TITLE = os.environ.get("UI_TITLE") or "Woodgrove Bank"
self.UI_TITLE = os.environ.get("UI_TITLE") or "Zava Copilot"
self.UI_LOGO = os.environ.get("UI_LOGO")
self.UI_CHAT_LOGO = os.environ.get("UI_CHAT_LOGO")
self.UI_CHAT_TITLE = os.environ.get("UI_CHAT_TITLE") or "Start chatting"
self.UI_CHAT_TITLE = os.environ.get("UI_CHAT_TITLE") or "How can I help you today?"
self.UI_CHAT_DESCRIPTION = (
os.environ.get("UI_CHAT_DESCRIPTION")
or "This chatbot is configured to answer your questions"
or "I can help with product info, inventory checks, warranties, and returns"
)
self.UI_FAVICON = os.environ.get("UI_FAVICON") or "/favicon.ico"
self.UI_SHOW_SHARE_BUTTON = (
Expand Down
8 changes: 8 additions & 0 deletions src/App/frontend/src/assets/ZavaLogo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
156 changes: 156 additions & 0 deletions src/App/frontend/src/components/ActionDrawer/ActionDrawer.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
.actionPanel {
font-family: 'Segoe UI', sans-serif;
}

.panelContent {
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
height: 100%;
}

.description {
color: var(--zava-dark-gray);
line-height: 1.5;
}

.progressContainer {
background-color: var(--zava-light-gray);
padding: 16px;
border-radius: 8px;
border-left: 4px solid var(--zava-accent);
}

.progressTitle {
font-weight: 600;
margin-bottom: 12px;
color: var(--zava-primary);
}

.progressSteps {
display: flex;
gap: 20px;
overflow-x: auto;
padding-bottom: 4px;
}

.progressStep {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
min-width: 80px;
text-align: center;
}

.progressDot {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
background-color: var(--zava-medium-gray);
color: var(--zava-dark-gray);
transition: all 0.3s ease;
}

.progressDot.completed {
background-color: var(--zava-accent);
color: var(--zava-white);
}

.progressLabel {
color: var(--zava-dark-gray);
max-width: 80px;
word-wrap: break-word;
}

.messageBar {
margin-bottom: 16px;
}

.fieldsContainer {
flex: 1;
overflow-y: auto;
}

.selectField {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--zava-medium-gray);
border-radius: 4px;
font-size: 14px;
font-family: 'Segoe UI', sans-serif;
background-color: var(--zava-white);
}

.selectField:focus {
outline: none;
border-color: var(--zava-accent);
box-shadow: 0 0 0 1px var(--zava-accent);
}

.fieldDescription {
color: var(--zava-dark-gray);
font-style: italic;
}

.actions {
display: flex;
gap: 12px;
align-items: center;
padding-top: 16px;
border-top: 1px solid var(--zava-medium-gray);
margin-top: auto;
}

.submitButton {
background-color: var(--zava-accent) !important;
border-color: var(--zava-accent) !important;
color: var(--zava-white) !important;
}

.submitButton:hover {
background-color: var(--zava-accent-dark) !important;
border-color: var(--zava-accent-dark) !important;
}

.submitButton:disabled {
background-color: var(--zava-medium-gray) !important;
border-color: var(--zava-medium-gray) !important;
color: var(--zava-dark-gray) !important;
}

.spinner {
margin-left: 8px;
}

/* Mobile responsive */
@media (max-width: 768px) {
.panelContent {
padding: 16px;
}

.progressSteps {
gap: 12px;
}

.progressStep {
min-width: 60px;
}

.progressDot {
width: 28px;
height: 28px;
font-size: 11px;
}

.actions {
flex-direction: column;
align-items: stretch;
}
}
150 changes: 150 additions & 0 deletions src/App/frontend/src/components/ActionDrawer/ActionDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { useState } from 'react'
import { Panel, PanelType, PrimaryButton, DefaultButton, TextField, Stack, Spinner, Text, MessageBar, MessageBarType } from '@fluentui/react'
import styles from './ActionDrawer.module.css'

export interface ActionField {
key: string
label: string
type: 'text' | 'number' | 'email' | 'select'
value: string
required?: boolean
options?: { key: string; text: string }[]
description?: string
}

export interface ActionDrawerProps {
isOpen: boolean
onDismiss: () => void
title: string
description?: string
fields: ActionField[]
onFieldChange: (key: string, value: string) => void
onSubmit: () => void
isSubmitting?: boolean
error?: string
success?: string
submitLabel?: string
progressSteps?: string[]
currentStep?: number
}

export const ActionDrawer: React.FC<ActionDrawerProps> = ({
isOpen,
onDismiss,
title,
description,
fields,
onFieldChange,
onSubmit,
isSubmitting = false,
error,
success,
submitLabel = 'Submit',
progressSteps,
currentStep = 0
}) => {
const renderProgressIndicator = () => {
if (!progressSteps) return null

return (
<div className={styles.progressContainer}>
<Text variant="small" className={styles.progressTitle}>Progress</Text>
<div className={styles.progressSteps}>
{progressSteps.map((step, index) => (
<div key={index} className={styles.progressStep}>
<div className={`${styles.progressDot} ${index <= currentStep ? styles.completed : ''}`}>
{index < currentStep ? 'βœ“' : index + 1}
</div>
<Text variant="small" className={styles.progressLabel}>{step}</Text>
</div>
))}
</div>
</div>
)
}

const renderField = (field: ActionField) => {
if (field.type === 'select') {
return (
<Stack key={field.key} tokens={{ childrenGap: 4 }}>
<Text variant="medium">{field.label} {field.required && '*'}</Text>
<select
value={field.value}
onChange={(e) => onFieldChange(field.key, e.target.value)}
className={styles.selectField}
required={field.required}
>
<option value="">Select...</option>
{field.options?.map(option => (
<option key={option.key} value={option.key}>{option.text}</option>
))}
</select>
{field.description && <Text variant="small" className={styles.fieldDescription}>{field.description}</Text>}
</Stack>
)
}

return (
<Stack key={field.key} tokens={{ childrenGap: 4 }}>
<TextField
label={field.label}
value={field.value}
onChange={(_, newValue) => onFieldChange(field.key, newValue || '')}
type={field.type}
required={field.required}
description={field.description}
/>
</Stack>
)
}

const canSubmit = fields.filter(f => f.required).every(f => f.value.trim()) && !isSubmitting

return (
<Panel
isOpen={isOpen}
onDismiss={onDismiss}
type={PanelType.medium}
headerText={title}
className={styles.actionPanel}
closeButtonAriaLabel="Close"
>
<div className={styles.panelContent}>
{description && (
<Text variant="medium" className={styles.description}>
{description}
</Text>
)}

{renderProgressIndicator()}

{error && (
<MessageBar messageBarType={MessageBarType.error} className={styles.messageBar}>
{error}
</MessageBar>
)}

{success && (
<MessageBar messageBarType={MessageBarType.success} className={styles.messageBar}>
{success}
</MessageBar>
)}

<Stack tokens={{ childrenGap: 16 }} className={styles.fieldsContainer}>
{fields.map(renderField)}
</Stack>

<div className={styles.actions}>
<PrimaryButton
text={isSubmitting ? 'Processing...' : submitLabel}
onClick={onSubmit}
disabled={!canSubmit}
className={styles.submitButton}
/>
<DefaultButton text="Cancel" onClick={onDismiss} />
{isSubmitting && <Spinner className={styles.spinner} />}
</div>
</div>
</Panel>
)
}
2 changes: 2 additions & 0 deletions src/App/frontend/src/components/ActionDrawer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ActionDrawer } from './ActionDrawer'
export type { ActionDrawerProps, ActionField } from './ActionDrawer'
Loading