Skip to content

Commit 7697895

Browse files
author
Herve Tribouilloy
committed
feat(intent-discovery): add evaluation state and UI blocking during AI recommendations
Introduce an evaluation phase while AI recommendations are being computed. - Add `EvaluationOverlay` to display feedback while recommendations are evaluated - Lift `isEvaluating` state to `IntentDiscovery` parent component - Disable `AttributeLayer` interactions during evaluation - Propagate `searchLoading` from `useAnalyseSearch` to control evaluation state - Refactor `useAnalyseSearch` to orchestrate product + AI queries safely - Add `enabled` guard to `useAiRecommendations` to prevent unnecessary calls - Simplify hook structure and remove legacy mock implementation - Minor styling improvement with `.step-finder--disabled` to visually block interaction This improves UX during the AI recommendation phase and stabilizes the recommendation pipeline for the demo.
1 parent 8eeb520 commit 7697895

9 files changed

Lines changed: 42 additions & 83 deletions

File tree

vite_project/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vite_project/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "widget-intent-discovery",
33
"private": true,
4-
"version": "0.1.1",
4+
"version": "0.1.3",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

vite_project/src/components/AttributeLayer.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {useState} from "react";
2-
import type {MagentoCategory} from "../types/infra/magento/category.types.ts";
32
import {useActiveAttributeState} from "../state/ActiveAttribute/useActiveAttributeState.ts";
43
import {useSelectedPreferences} from "./selectedPreferencesUtils";
54
import type {IntentDiscoveryDataConfig} from "../domain/intent-discovery.types.ts";
@@ -8,11 +7,11 @@ import type {MagentoAggregation, MagentoProducts} from "../hooks/infra/useProduc
87

98
type Props = {
109
config: IntentDiscoveryDataConfig;
11-
categoryData: MagentoCategory;
1210
attributeLayerData: MagentoProducts
11+
disabled: boolean
1312
};
1413

15-
export const AttributeLayer = ({ config, attributeLayerData }: Props) => {
14+
export const AttributeLayer = ({ config, attributeLayerData, disabled }: Props) => {
1615
const { setActiveAttributeCode } = useActiveAttributeState();
1716
const {intentState} = useSystemState()
1817
const { valueFor: prefValue } =
@@ -42,10 +41,9 @@ export const AttributeLayer = ({ config, attributeLayerData }: Props) => {
4241

4342
return (
4443
<>
45-
{/*<SelectedPreferences categoryData={categoryData} intent={intent} />*/}
4644
<div className="finder">
4745
<h2 className="finder__title">Need help choosing?</h2>
48-
<div className="step-finder">
46+
<div className={`step-finder ${disabled ? 'step-finder--disabled' : ''}`}>
4947
{visibleAttributes.map((attr: MagentoAggregation) => (
5048
<div
5149
key={attr.attribute_code}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {Spinner} from "./global/Spinner.tsx";
2+
3+
export const EvaluationOverlay = () => {
4+
return (
5+
<div className="intent-evaluation-overlay">
6+
<Spinner />
7+
<p>Evaluating your preferences…</p>
8+
</div>
9+
)
10+
}

vite_project/src/components/IntentDiscovery.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {IntentDiscoveryOptions} from "./IntentDiscoveryOptions.tsx";
1010
import {ProductRecommendations} from "./ProductRecommendations.tsx";
1111
import {activity} from "../activity";
1212
import {useIntentDecision} from "../hooks/domain/useIntentDecision.tsx";
13+
import {EvaluationOverlay} from "./EvaluationOverlay.tsx";
1314

1415
export interface Props {
1516
config: IntentDiscoveryDataConfig
@@ -23,6 +24,7 @@ export const IntentDiscovery = ({ config, categoryData }: Props) => {
2324

2425
const [showRightColumn, setShowRightColumn] = useState(false)
2526
const { shouldSearch } = useIntentDecision(attributeLayerData, config)
27+
const [isEvaluating, setIsEvaluating] = useState(false)
2628

2729
useEffect(() => {
2830
if (!attributeLayerData?.aggregations) return
@@ -37,18 +39,13 @@ export const IntentDiscovery = ({ config, categoryData }: Props) => {
3739

3840
return (
3941
<>
40-
{shouldSearch && (
41-
<div className="intent-evaluation-overlay">
42-
<Spinner />
43-
<p>Evaluating your preferences…</p>
44-
</div>
45-
)}
42+
{isEvaluating && <EvaluationOverlay />}
4643
<div className={showRightColumn ? "re-intent-layout re-intent-layout--two" : "re-intent-layout"}>
4744
<div className="re-intent-col re-intent-col--left">
4845
<AttributeLayer
4946
config={config}
50-
categoryData={categoryData}
5147
attributeLayerData={attributeLayerData}
48+
disabled={isEvaluating}
5249
/>
5350
<IntentDiscoveryOptions
5451
config={config}
@@ -63,6 +60,7 @@ export const IntentDiscovery = ({ config, categoryData }: Props) => {
6360
attributeLayerData={attributeLayerData}
6461
onVisibilityChange={setShowRightColumn}
6562
shouldSearch={shouldSearch}
63+
setIsEvaluating={setIsEvaluating}
6664
/>
6765
</div>
6866
</div>

vite_project/src/components/ProductRecommendations.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ export interface Props {
1010
attributeLayerData: MagentoProducts
1111
onVisibilityChange?: (visible: boolean) => void
1212
shouldSearch: boolean
13+
setIsEvaluating: (status: boolean) => void
1314
}
1415

1516
export const ProductRecommendations = ({
1617
categoryData,
1718
attributeLayerData,
1819
onVisibilityChange,
19-
shouldSearch
20+
shouldSearch,
21+
setIsEvaluating
2022
}: Props) => {
2123
const { aiRecommendation, searchLoading, error } =
2224
useAnalyseSearch(
@@ -31,6 +33,10 @@ export const ProductRecommendations = ({
3133
onVisibilityChange?.(hasSuggestions)
3234
}, [hasSuggestions, onVisibilityChange])
3335

36+
useEffect(() => {
37+
setIsEvaluating(searchLoading);
38+
}, [searchLoading]);
39+
3440
if (!shouldSearch) return null;
3541
if (searchLoading) return <Spinner />;
3642
if (!aiRecommendation?.suggestions?.length) return null;
Lines changed: 6 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import {useAiRecommendations} from "../infra/useAiRecommendations.tsx";
22
import type {MagentoAggregation} from "../infra/useProductAttributeLayer.tsx";
3-
import {useEffect, useState} from "react";
43
import type {MagentoCategory} from "../../types/infra/magento/category.types.ts";
54
import {useFindProducts} from "./useFindProducts.tsx";
65

76
export function useAnalyseSearch(
87
aggregations: MagentoAggregation[] | undefined,
98
categoryData: MagentoCategory,
10-
enabled: boolean
9+
enabled: boolean,
1110
) {
11+
1212
const {
1313
productData,
1414
productLoading,
@@ -19,72 +19,13 @@ export function useAnalyseSearch(
1919
data: aiRecommendation,
2020
loading: aiLoading,
2121
error: aiError,
22-
} = useAiRecommendations(aggregations, productData);
22+
} = useAiRecommendations(aggregations, productData, enabled);
23+
24+
const searchLoading = productLoading || aiLoading;
2325

2426
return {
2527
aiRecommendation,
26-
searchLoading: productLoading || aiLoading,
28+
searchLoading,
2729
error: productError ?? aiError,
2830
};
29-
}
30-
31-
export interface MockRecommendation {
32-
sku: string
33-
name: string
34-
score: number
35-
}
36-
37-
export interface MockAnalyseResult {
38-
productData: MockRecommendation[]
39-
message?: string
40-
}
41-
42-
export function useAnalyseSearchMock(attributeData: MagentoAggregation[]) {
43-
const [data, setData] = useState<MockAnalyseResult | null>(null)
44-
45-
useEffect(() => {
46-
if (!attributeData?.length) {
47-
setData(null)
48-
return
49-
}
50-
51-
// simple scoring heuristic
52-
const sizeAgg = attributeData.find(a => a.attribute_code === "size")
53-
const colourAgg = attributeData.find(a => a.attribute_code === "color")
54-
55-
const sizeSignals = sizeAgg?.options?.filter(o => o.count > 0).length ?? 0
56-
const colourSignals = colourAgg?.options?.filter(o => o.count > 0).length ?? 0
57-
58-
const mockProducts: MockRecommendation[] = [
59-
{
60-
sku: "MOCK-1",
61-
name: "Slim Fit Cotton Shirt",
62-
score: sizeSignals * 2 + colourSignals
63-
},
64-
{
65-
sku: "MOCK-2",
66-
name: "Relaxed Fit Linen Shirt",
67-
score: sizeSignals + colourSignals * 2
68-
}
69-
]
70-
71-
const sorted = mockProducts.sort((a, b) => b.score - a.score)
72-
73-
setData({
74-
productData: sorted,
75-
message:
76-
sorted[0].score > 2
77-
? "Based on your filters, these may match your intent."
78-
: "You might want to refine your selection."
79-
})
80-
81-
}, [attributeData])
82-
83-
return {
84-
productData: data?.productData ?? [],
85-
message: data?.message,
86-
productLoading: false,
87-
productError: null,
88-
refetch: () => {}
89-
}
9031
}

vite_project/src/hooks/infra/useAiRecommendations.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export interface AiRecommendationResponse {
2929

3030
export function useAiRecommendations(
3131
attributeData: MagentoAggregation[] | undefined,
32-
productData: GraphqlProduct[] | undefined
32+
productData: GraphqlProduct[] | undefined,
33+
enabled: boolean
3334
) {
3435
const { intentState } = useSystemState()
3536

@@ -40,7 +41,7 @@ export function useAiRecommendations(
4041
const { intentApiClient } = useSystemState();
4142

4243
const load = useCallback(async () => {
43-
if (!intentState || !attributeData?.length || !productData?.length) return
44+
if (!intentState || !attributeData?.length || !productData?.length || !enabled) return
4445

4546
setLoading(true)
4647
setError(null)

vite_project/src/styles/intent-discovery.suggestions.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,9 @@
176176
justify-content: center;
177177
z-index: 10;
178178
font-size: 16px;
179+
}
180+
181+
.step-finder--disabled {
182+
opacity: 0.5;
183+
pointer-events: none;
179184
}

0 commit comments

Comments
 (0)