@stokelp/ui 2.108.0 → 2.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- "use client";const e=require("../text/Text.cjs");let t=require("@stokelp/styled-system/jsx"),n=require("react/jsx-runtime");var r=[`origin`,`rawMaterialOrigin`,`temperature`,`cutting`,`cooking`,`treatment`,`label`,`packaging`];function i(e){return e>=70?`#3B6D11`:e>=40?`#854F0B`:`#A32D2D`}function a(e){return e>=70?`#639922`:e>=40?`#BA7517`:`#E24B4A`}function o(e){return e?.matched!=null&&e?.total!=null}var s=({percent:s,detail:c,labels:l})=>{let u=r.filter(e=>o(c[e])),d=r.filter(e=>!o(c[e]));return(0,n.jsxs)(t.Box,{bg:`white`,borderRadius:`radius-8`,p:`space-16`,minW:`220px`,maxW:`240px`,css:{"& *":{fontFamily:`inherit`}},children:[(0,n.jsxs)(t.HStack,{justify:`space-between`,alignItems:`baseline`,mb:`space-8`,children:[(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.md`,fontWeight:`bold`,children:l.title}),(0,n.jsxs)(e.Text,{as:`span`,fontSize:`body.lg`,fontWeight:`medium`,style:{color:i(s)},children:[Math.round(s),` %`]})]}),(0,n.jsx)(t.Box,{h:`1px`,bg:`grey.100`,mb:`space-8`}),u.length>0&&(0,n.jsxs)(t.VStack,{alignItems:`stretch`,gap:`space-4`,mb:`space-8`,children:[(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,fontWeight:`bold`,color:`grey.400`,children:l.compared}),u.map(r=>{let i=c[r].matched,o=c[r].total,s=o>0?i/o*100:0;return(0,n.jsxs)(t.HStack,{alignItems:`center`,gap:`space-8`,children:[(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,color:`grey.500`,minW:`80px`,children:l.attributes[r]}),(0,n.jsx)(t.Box,{flex:`1`,h:`3px`,bg:`grey.100`,borderRadius:`full`,overflow:`hidden`,children:(0,n.jsx)(t.Box,{h:`100%`,borderRadius:`full`,style:{width:`${s}%`,background:a(s)}})}),(0,n.jsxs)(e.Text,{as:`span`,fontSize:`body.sm`,fontWeight:`medium`,minW:`40px`,textAlign:`right`,style:{color:a(s)},children:[i,`/`,o]})]},r)})]}),u.length>0&&d.length>0&&(0,n.jsx)(t.Box,{h:`1px`,bg:`grey.100`,mb:`space-8`}),d.length>0&&(0,n.jsxs)(t.VStack,{alignItems:`stretch`,gap:`space-4`,children:[(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,fontWeight:`bold`,color:`grey.400`,children:l.notSpecified}),(0,n.jsx)(t.HStack,{flexWrap:`wrap`,gap:`space-4`,children:d.map(t=>(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,color:`grey.400`,border:`1px solid`,borderColor:`grey.100`,px:`space-8`,py:`2px`,borderRadius:`full`,children:l.attributes[t]},t))})]}),l.footer&&(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.Box,{h:`1px`,bg:`grey.100`,mt:`space-8`,mb:`space-8`}),(0,n.jsx)(e.Text,{as:`span`,fontSize:`9px`,color:`grey.300`,style:{lineHeight:1},children:l.footer})]})]})};exports.ChatScoreBreakdown=s;
1
+ "use client";const e=require("../text/Text.cjs");let t=require("@stokelp/styled-system/jsx"),n=require("react/jsx-runtime");var r=[`origin`,`rawMaterialOrigin`,`temperature`,`cutting`,`cooking`,`treatment`,`label`,`packaging`,`grammageGsm`,`recycledPct`,`thicknessMicron`],i=[`temperature`,`cutting`,`cooking`],a=[`grammageGsm`,`recycledPct`,`thicknessMicron`],o={egal:{bg:`#E6F3D9`,color:`#3B6D11`},proche:{bg:`#F0F6DC`,color:`#5E8516`},eloigne:{bg:`#FDF0E0`,color:`#C2740C`},non_correspondant:{bg:`#FCEBEB`,color:`#A32D2D`}};function s(e){return e>=70?`#3B6D11`:e>=40?`#854F0B`:`#A32D2D`}function c(e){return e>=70?`#639922`:e>=40?`#BA7517`:`#E24B4A`}function l(e){return e?.level!=null}function u(e){return e?.matched!=null&&e?.total!=null}function d(e){return l(e)||u(e)}var f=({percent:u,detail:f,labels:p})=>{let m=a.some(e=>f[e]?.level!=null),h=new Set(m?i:a),g=r.filter(e=>!h.has(e)),_=g.filter(e=>d(f[e])),v=g.filter(e=>!d(f[e])&&p.attributes[e]!=null);return(0,n.jsxs)(t.Box,{bg:`white`,borderRadius:`radius-8`,p:`space-16`,minW:`220px`,maxW:`240px`,css:{"& *":{fontFamily:`inherit`}},children:[(0,n.jsxs)(t.HStack,{justify:`space-between`,alignItems:`baseline`,mb:`space-8`,children:[(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.md`,fontWeight:`bold`,children:p.title}),(0,n.jsxs)(e.Text,{as:`span`,fontSize:`body.lg`,fontWeight:`medium`,style:{color:s(u)},children:[Math.round(u),` %`]})]}),(0,n.jsx)(t.Box,{h:`1px`,bg:`grey.100`,mb:`space-8`}),_.length>0&&(0,n.jsxs)(t.VStack,{alignItems:`stretch`,gap:`space-4`,mb:`space-8`,children:[(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,fontWeight:`bold`,color:`grey.400`,children:p.compared}),_.map(r=>{let i=f[r];if(l(i)){let a=i.level,s=o[a];return(0,n.jsxs)(t.HStack,{alignItems:`center`,gap:`space-8`,justify:`space-between`,children:[(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,color:`grey.500`,minW:`80px`,children:p.attributes[r]}),(0,n.jsx)(t.Box,{px:`space-8`,py:`2px`,borderRadius:`full`,flexShrink:`0`,style:{backgroundColor:s.bg},children:(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,fontWeight:`medium`,style:{color:s.color,whiteSpace:`nowrap`},children:p.levels?.[a]??a})})]},r)}let a=i.matched,s=i.total,u=s>0?a/s*100:0;return(0,n.jsxs)(t.HStack,{alignItems:`center`,gap:`space-8`,children:[(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,color:`grey.500`,minW:`80px`,children:p.attributes[r]}),(0,n.jsx)(t.Box,{flex:`1`,h:`3px`,bg:`grey.100`,borderRadius:`full`,overflow:`hidden`,children:(0,n.jsx)(t.Box,{h:`100%`,borderRadius:`full`,style:{width:`${u}%`,background:c(u)}})}),(0,n.jsxs)(e.Text,{as:`span`,fontSize:`body.sm`,fontWeight:`medium`,minW:`40px`,textAlign:`right`,style:{color:c(u)},children:[a,`/`,s]})]},r)})]}),_.length>0&&v.length>0&&(0,n.jsx)(t.Box,{h:`1px`,bg:`grey.100`,mb:`space-8`}),v.length>0&&(0,n.jsxs)(t.VStack,{alignItems:`stretch`,gap:`space-4`,children:[(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,fontWeight:`bold`,color:`grey.400`,children:p.notSpecified}),(0,n.jsx)(t.HStack,{flexWrap:`wrap`,gap:`space-4`,children:v.map(t=>(0,n.jsx)(e.Text,{as:`span`,fontSize:`body.sm`,color:`grey.400`,border:`1px solid`,borderColor:`grey.100`,px:`space-8`,py:`2px`,borderRadius:`full`,children:p.attributes[t]},t))})]}),p.footer&&(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.Box,{h:`1px`,bg:`grey.100`,mt:`space-8`,mb:`space-8`}),(0,n.jsx)(e.Text,{as:`span`,fontSize:`9px`,color:`grey.300`,style:{lineHeight:1},children:p.footer})]})]})};exports.ChatScoreBreakdown=f;
2
2
  //# sourceMappingURL=ChatScoreBreakdown.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatScoreBreakdown.cjs","names":[],"sources":["../../../src/components/chat/ChatScoreBreakdown.tsx"],"sourcesContent":["import { Box, HStack, VStack } from '@stokelp/styled-system/jsx'\nimport { Text } from '~/components'\n\nexport type ScoreAttributeKey =\n | 'origin'\n | 'rawMaterialOrigin'\n | 'temperature'\n | 'cutting'\n | 'cooking'\n | 'treatment'\n | 'label'\n | 'packaging'\n\nexport type ScoreAttributeDetail = {\n matched: number | null\n total: number | null\n}\n\nexport type ChatScoreBreakdownLabels = {\n title: string\n compared: string\n notSpecified: string\n attributes: Record<ScoreAttributeKey, string>\n footer?: string\n}\n\nexport type ChatScoreBreakdownProps = {\n percent: number\n detail: Record<ScoreAttributeKey, ScoreAttributeDetail>\n labels: ChatScoreBreakdownLabels\n}\n\nconst ATTRIBUTE_ORDER: ScoreAttributeKey[] = [\n 'origin',\n 'rawMaterialOrigin',\n 'temperature',\n 'cutting',\n 'cooking',\n 'treatment',\n 'label',\n 'packaging',\n]\n\nfunction getHeadlineColor(percent: number): string {\n if (percent >= 70) return '#3B6D11'\n if (percent >= 40) return '#854F0B'\n return '#A32D2D'\n}\n\nfunction getBarColor(ratioPercent: number): string {\n if (ratioPercent >= 70) return '#639922'\n if (ratioPercent >= 40) return '#BA7517'\n return '#E24B4A'\n}\n\nfunction isComparable(detail?: ScoreAttributeDetail): boolean {\n return detail?.matched != null && detail?.total != null\n}\n\nexport const ChatScoreBreakdown = ({ percent, detail, labels }: ChatScoreBreakdownProps) => {\n const compared = ATTRIBUTE_ORDER.filter(key => isComparable(detail[key]))\n const notSpecified = ATTRIBUTE_ORDER.filter(key => !isComparable(detail[key]))\n\n return (\n <Box\n bg=\"white\"\n borderRadius=\"radius-8\"\n p=\"space-16\"\n minW=\"220px\"\n maxW=\"240px\"\n css={{ '& *': { fontFamily: 'inherit' } }}\n >\n <HStack justify=\"space-between\" alignItems=\"baseline\" mb=\"space-8\">\n <Text as=\"span\" fontSize=\"body.md\" fontWeight=\"bold\">\n {labels.title}\n </Text>\n <Text as=\"span\" fontSize=\"body.lg\" fontWeight=\"medium\" style={{ color: getHeadlineColor(percent) }}>\n {Math.round(percent)} %\n </Text>\n </HStack>\n\n <Box h=\"1px\" bg=\"grey.100\" mb=\"space-8\" />\n\n {compared.length > 0 && (\n <VStack alignItems=\"stretch\" gap=\"space-4\" mb=\"space-8\">\n <Text as=\"span\" fontSize=\"body.sm\" fontWeight=\"bold\" color=\"grey.400\">\n {labels.compared}\n </Text>\n {compared.map(key => {\n const matched = detail[key].matched as number\n const total = detail[key].total as number\n const ratioPercent = total > 0 ? (matched / total) * 100 : 0\n return (\n <HStack key={key} alignItems=\"center\" gap=\"space-8\">\n <Text as=\"span\" fontSize=\"body.sm\" color=\"grey.500\" minW=\"80px\">\n {labels.attributes[key]}\n </Text>\n <Box flex=\"1\" h=\"3px\" bg=\"grey.100\" borderRadius=\"full\" overflow=\"hidden\">\n <Box\n h=\"100%\"\n borderRadius=\"full\"\n style={{ width: `${ratioPercent}%`, background: getBarColor(ratioPercent) }}\n />\n </Box>\n <Text\n as=\"span\"\n fontSize=\"body.sm\"\n fontWeight=\"medium\"\n minW=\"40px\"\n textAlign=\"right\"\n style={{ color: getBarColor(ratioPercent) }}\n >\n {matched}/{total}\n </Text>\n </HStack>\n )\n })}\n </VStack>\n )}\n\n {compared.length > 0 && notSpecified.length > 0 && <Box h=\"1px\" bg=\"grey.100\" mb=\"space-8\" />}\n\n {notSpecified.length > 0 && (\n <VStack alignItems=\"stretch\" gap=\"space-4\">\n <Text as=\"span\" fontSize=\"body.sm\" fontWeight=\"bold\" color=\"grey.400\">\n {labels.notSpecified}\n </Text>\n <HStack flexWrap=\"wrap\" gap=\"space-4\">\n {notSpecified.map(key => (\n <Text\n key={key}\n as=\"span\"\n fontSize=\"body.sm\"\n color=\"grey.400\"\n border=\"1px solid\"\n borderColor=\"grey.100\"\n px=\"space-8\"\n py=\"2px\"\n borderRadius=\"full\"\n >\n {labels.attributes[key]}\n </Text>\n ))}\n </HStack>\n </VStack>\n )}\n\n {labels.footer && (\n <>\n <Box h=\"1px\" bg=\"grey.100\" mt=\"space-8\" mb=\"space-8\" />\n <Text as=\"span\" fontSize=\"9px\" color=\"grey.300\" style={{ lineHeight: 1 }}>\n {labels.footer}\n </Text>\n </>\n )}\n </Box>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ChatScoreBreakdown.cjs","names":[],"sources":["../../../src/components/chat/ChatScoreBreakdown.tsx"],"sourcesContent":["import { Box, HStack, VStack } from '@stokelp/styled-system/jsx'\nimport { Text } from '~/components'\n\nexport type ScoreSetAttributeKey =\n | 'origin'\n | 'rawMaterialOrigin'\n | 'temperature'\n | 'cutting'\n | 'cooking'\n | 'treatment'\n | 'label'\n | 'packaging'\n\nexport type ScoreNumericAttributeKey = 'grammageGsm' | 'recycledPct' | 'thicknessMicron'\n\nexport type ScoreAttributeKey = ScoreSetAttributeKey | ScoreNumericAttributeKey\n\n/** Proximity bucket for free numeric packaging specs: egal (equal) → non_correspondant (no match). */\nexport type ScoreLevel = 'egal' | 'proche' | 'eloigne' | 'non_correspondant'\n\nexport type ScoreAttributeDetail = {\n matched: number | null\n total: number | null\n level?: ScoreLevel | null\n}\n\nexport type ChatScoreBreakdownLabels = {\n title: string\n compared: string\n notSpecified: string\n attributes: Record<ScoreSetAttributeKey, string> & Partial<Record<ScoreNumericAttributeKey, string>>\n levels?: Record<ScoreLevel, string>\n footer?: string\n}\n\nexport type ChatScoreBreakdownProps = {\n percent: number\n detail: Record<string, ScoreAttributeDetail>\n labels: ChatScoreBreakdownLabels\n}\n\nconst ATTRIBUTE_ORDER: ScoreAttributeKey[] = [\n 'origin',\n 'rawMaterialOrigin',\n 'temperature',\n 'cutting',\n 'cooking',\n 'treatment',\n 'label',\n 'packaging',\n 'grammageGsm',\n 'recycledPct',\n 'thicknessMicron',\n]\n\n// Attributes that only exist for food products; hidden for packaging products.\nconst FOOD_ONLY_KEYS: ScoreAttributeKey[] = ['temperature', 'cutting', 'cooking']\n// Free numeric specs that only exist for packaging products; hidden for food products.\nconst PACKAGING_ONLY_KEYS: ScoreNumericAttributeKey[] = ['grammageGsm', 'recycledPct', 'thicknessMicron']\n\nconst LEVEL_COLORS: Record<ScoreLevel, { bg: string; color: string }> = {\n egal: { bg: '#E6F3D9', color: '#3B6D11' },\n proche: { bg: '#F0F6DC', color: '#5E8516' },\n eloigne: { bg: '#FDF0E0', color: '#C2740C' },\n non_correspondant: { bg: '#FCEBEB', color: '#A32D2D' },\n}\n\nfunction getHeadlineColor(percent: number): string {\n if (percent >= 70) return '#3B6D11'\n if (percent >= 40) return '#854F0B'\n return '#A32D2D'\n}\n\nfunction getBarColor(ratioPercent: number): string {\n if (ratioPercent >= 70) return '#639922'\n if (ratioPercent >= 40) return '#BA7517'\n return '#E24B4A'\n}\n\nfunction hasLevel(detail?: ScoreAttributeDetail): boolean {\n return detail?.level != null\n}\n\nfunction isFraction(detail?: ScoreAttributeDetail): boolean {\n return detail?.matched != null && detail?.total != null\n}\n\nfunction isComparable(detail?: ScoreAttributeDetail): boolean {\n return hasLevel(detail) || isFraction(detail)\n}\n\nexport const ChatScoreBreakdown = ({ percent, detail, labels }: ChatScoreBreakdownProps) => {\n // A packaging product exposes the numeric specs (comparable via level); food attributes\n // then come back fully null and must be hidden entirely, and vice versa.\n const isPackaging = PACKAGING_ONLY_KEYS.some(key => detail[key]?.level != null)\n const hiddenKeys = new Set<ScoreAttributeKey>(isPackaging ? FOOD_ONLY_KEYS : PACKAGING_ONLY_KEYS)\n const visibleKeys = ATTRIBUTE_ORDER.filter(key => !hiddenKeys.has(key))\n\n const compared = visibleKeys.filter(key => isComparable(detail[key]))\n const notSpecified = visibleKeys.filter(key => !isComparable(detail[key]) && labels.attributes[key] != null)\n\n return (\n <Box\n bg=\"white\"\n borderRadius=\"radius-8\"\n p=\"space-16\"\n minW=\"220px\"\n maxW=\"240px\"\n css={{ '& *': { fontFamily: 'inherit' } }}\n >\n <HStack justify=\"space-between\" alignItems=\"baseline\" mb=\"space-8\">\n <Text as=\"span\" fontSize=\"body.md\" fontWeight=\"bold\">\n {labels.title}\n </Text>\n <Text as=\"span\" fontSize=\"body.lg\" fontWeight=\"medium\" style={{ color: getHeadlineColor(percent) }}>\n {Math.round(percent)} %\n </Text>\n </HStack>\n\n <Box h=\"1px\" bg=\"grey.100\" mb=\"space-8\" />\n\n {compared.length > 0 && (\n <VStack alignItems=\"stretch\" gap=\"space-4\" mb=\"space-8\">\n <Text as=\"span\" fontSize=\"body.sm\" fontWeight=\"bold\" color=\"grey.400\">\n {labels.compared}\n </Text>\n {compared.map(key => {\n const entry = detail[key]\n if (hasLevel(entry)) {\n const level = entry.level as ScoreLevel\n const colors = LEVEL_COLORS[level]\n return (\n <HStack key={key} alignItems=\"center\" gap=\"space-8\" justify=\"space-between\">\n <Text as=\"span\" fontSize=\"body.sm\" color=\"grey.500\" minW=\"80px\">\n {labels.attributes[key]}\n </Text>\n <Box px=\"space-8\" py=\"2px\" borderRadius=\"full\" flexShrink=\"0\" style={{ backgroundColor: colors.bg }}>\n <Text\n as=\"span\"\n fontSize=\"body.sm\"\n fontWeight=\"medium\"\n style={{ color: colors.color, whiteSpace: 'nowrap' }}\n >\n {labels.levels?.[level] ?? level}\n </Text>\n </Box>\n </HStack>\n )\n }\n\n const matched = entry.matched as number\n const total = entry.total as number\n const ratioPercent = total > 0 ? (matched / total) * 100 : 0\n return (\n <HStack key={key} alignItems=\"center\" gap=\"space-8\">\n <Text as=\"span\" fontSize=\"body.sm\" color=\"grey.500\" minW=\"80px\">\n {labels.attributes[key]}\n </Text>\n <Box flex=\"1\" h=\"3px\" bg=\"grey.100\" borderRadius=\"full\" overflow=\"hidden\">\n <Box\n h=\"100%\"\n borderRadius=\"full\"\n style={{ width: `${ratioPercent}%`, background: getBarColor(ratioPercent) }}\n />\n </Box>\n <Text\n as=\"span\"\n fontSize=\"body.sm\"\n fontWeight=\"medium\"\n minW=\"40px\"\n textAlign=\"right\"\n style={{ color: getBarColor(ratioPercent) }}\n >\n {matched}/{total}\n </Text>\n </HStack>\n )\n })}\n </VStack>\n )}\n\n {compared.length > 0 && notSpecified.length > 0 && <Box h=\"1px\" bg=\"grey.100\" mb=\"space-8\" />}\n\n {notSpecified.length > 0 && (\n <VStack alignItems=\"stretch\" gap=\"space-4\">\n <Text as=\"span\" fontSize=\"body.sm\" fontWeight=\"bold\" color=\"grey.400\">\n {labels.notSpecified}\n </Text>\n <HStack flexWrap=\"wrap\" gap=\"space-4\">\n {notSpecified.map(key => (\n <Text\n key={key}\n as=\"span\"\n fontSize=\"body.sm\"\n color=\"grey.400\"\n border=\"1px solid\"\n borderColor=\"grey.100\"\n px=\"space-8\"\n py=\"2px\"\n borderRadius=\"full\"\n >\n {labels.attributes[key]}\n </Text>\n ))}\n </HStack>\n </VStack>\n )}\n\n {labels.footer && (\n <>\n <Box h=\"1px\" bg=\"grey.100\" mt=\"space-8\" mb=\"space-8\" />\n <Text as=\"span\" fontSize=\"9px\" color=\"grey.300\" style={{ lineHeight: 1 }}>\n {labels.footer}\n </Text>\n </>\n )}\n </Box>\n )\n}\n"],"mappings":"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"}
@@ -1,18 +1,24 @@
1
- export type ScoreAttributeKey = 'origin' | 'rawMaterialOrigin' | 'temperature' | 'cutting' | 'cooking' | 'treatment' | 'label' | 'packaging';
1
+ export type ScoreSetAttributeKey = 'origin' | 'rawMaterialOrigin' | 'temperature' | 'cutting' | 'cooking' | 'treatment' | 'label' | 'packaging';
2
+ export type ScoreNumericAttributeKey = 'grammageGsm' | 'recycledPct' | 'thicknessMicron';
3
+ export type ScoreAttributeKey = ScoreSetAttributeKey | ScoreNumericAttributeKey;
4
+ /** Proximity bucket for free numeric packaging specs: egal (equal) → non_correspondant (no match). */
5
+ export type ScoreLevel = 'egal' | 'proche' | 'eloigne' | 'non_correspondant';
2
6
  export type ScoreAttributeDetail = {
3
7
  matched: number | null;
4
8
  total: number | null;
9
+ level?: ScoreLevel | null;
5
10
  };
6
11
  export type ChatScoreBreakdownLabels = {
7
12
  title: string;
8
13
  compared: string;
9
14
  notSpecified: string;
10
- attributes: Record<ScoreAttributeKey, string>;
15
+ attributes: Record<ScoreSetAttributeKey, string> & Partial<Record<ScoreNumericAttributeKey, string>>;
16
+ levels?: Record<ScoreLevel, string>;
11
17
  footer?: string;
12
18
  };
13
19
  export type ChatScoreBreakdownProps = {
14
20
  percent: number;
15
- detail: Record<ScoreAttributeKey, ScoreAttributeDetail>;
21
+ detail: Record<string, ScoreAttributeDetail>;
16
22
  labels: ChatScoreBreakdownLabels;
17
23
  };
18
24
  export declare const ChatScoreBreakdown: ({ percent, detail, labels }: ChatScoreBreakdownProps) => import("react/jsx-runtime").JSX.Element;
@@ -11,19 +11,53 @@ var s = [
11
11
  "cooking",
12
12
  "treatment",
13
13
  "label",
14
- "packaging"
15
- ];
16
- function c(e) {
14
+ "packaging",
15
+ "grammageGsm",
16
+ "recycledPct",
17
+ "thicknessMicron"
18
+ ], c = [
19
+ "temperature",
20
+ "cutting",
21
+ "cooking"
22
+ ], l = [
23
+ "grammageGsm",
24
+ "recycledPct",
25
+ "thicknessMicron"
26
+ ], u = {
27
+ egal: {
28
+ bg: "#E6F3D9",
29
+ color: "#3B6D11"
30
+ },
31
+ proche: {
32
+ bg: "#F0F6DC",
33
+ color: "#5E8516"
34
+ },
35
+ eloigne: {
36
+ bg: "#FDF0E0",
37
+ color: "#C2740C"
38
+ },
39
+ non_correspondant: {
40
+ bg: "#FCEBEB",
41
+ color: "#A32D2D"
42
+ }
43
+ };
44
+ function d(e) {
17
45
  return e >= 70 ? "#3B6D11" : e >= 40 ? "#854F0B" : "#A32D2D";
18
46
  }
19
- function l(e) {
47
+ function f(e) {
20
48
  return e >= 70 ? "#639922" : e >= 40 ? "#BA7517" : "#E24B4A";
21
49
  }
22
- function u(e) {
50
+ function p(e) {
51
+ return e?.level != null;
52
+ }
53
+ function m(e) {
23
54
  return e?.matched != null && e?.total != null;
24
55
  }
25
- var d = ({ percent: d, detail: f, labels: p }) => {
26
- let m = s.filter((e) => u(f[e])), h = s.filter((e) => !u(f[e]));
56
+ function h(e) {
57
+ return p(e) || m(e);
58
+ }
59
+ var g = ({ percent: m, detail: g, labels: _ }) => {
60
+ let v = l.some((e) => g[e]?.level != null), y = new Set(v ? c : l), b = s.filter((e) => !y.has(e)), x = b.filter((e) => h(g[e])), S = b.filter((e) => !h(g[e]) && _.attributes[e] != null);
27
61
  return /* @__PURE__ */ o(t, {
28
62
  bg: "white",
29
63
  borderRadius: "radius-8",
@@ -40,13 +74,13 @@ var d = ({ percent: d, detail: f, labels: p }) => {
40
74
  as: "span",
41
75
  fontSize: "body.md",
42
76
  fontWeight: "bold",
43
- children: p.title
77
+ children: _.title
44
78
  }), /* @__PURE__ */ o(e, {
45
79
  as: "span",
46
80
  fontSize: "body.lg",
47
81
  fontWeight: "medium",
48
- style: { color: c(d) },
49
- children: [Math.round(d), " %"]
82
+ style: { color: d(m) },
83
+ children: [Math.round(m), " %"]
50
84
  })]
51
85
  }),
52
86
  /* @__PURE__ */ a(t, {
@@ -54,7 +88,7 @@ var d = ({ percent: d, detail: f, labels: p }) => {
54
88
  bg: "grey.100",
55
89
  mb: "space-8"
56
90
  }),
57
- m.length > 0 && /* @__PURE__ */ o(r, {
91
+ x.length > 0 && /* @__PURE__ */ o(r, {
58
92
  alignItems: "stretch",
59
93
  gap: "space-4",
60
94
  mb: "space-8",
@@ -63,9 +97,41 @@ var d = ({ percent: d, detail: f, labels: p }) => {
63
97
  fontSize: "body.sm",
64
98
  fontWeight: "bold",
65
99
  color: "grey.400",
66
- children: p.compared
67
- }), m.map((r) => {
68
- let i = f[r].matched, s = f[r].total, c = s > 0 ? i / s * 100 : 0;
100
+ children: _.compared
101
+ }), x.map((r) => {
102
+ let i = g[r];
103
+ if (p(i)) {
104
+ let s = i.level, c = u[s];
105
+ return /* @__PURE__ */ o(n, {
106
+ alignItems: "center",
107
+ gap: "space-8",
108
+ justify: "space-between",
109
+ children: [/* @__PURE__ */ a(e, {
110
+ as: "span",
111
+ fontSize: "body.sm",
112
+ color: "grey.500",
113
+ minW: "80px",
114
+ children: _.attributes[r]
115
+ }), /* @__PURE__ */ a(t, {
116
+ px: "space-8",
117
+ py: "2px",
118
+ borderRadius: "full",
119
+ flexShrink: "0",
120
+ style: { backgroundColor: c.bg },
121
+ children: /* @__PURE__ */ a(e, {
122
+ as: "span",
123
+ fontSize: "body.sm",
124
+ fontWeight: "medium",
125
+ style: {
126
+ color: c.color,
127
+ whiteSpace: "nowrap"
128
+ },
129
+ children: _.levels?.[s] ?? s
130
+ })
131
+ })]
132
+ }, r);
133
+ }
134
+ let s = i.matched, c = i.total, l = c > 0 ? s / c * 100 : 0;
69
135
  return /* @__PURE__ */ o(n, {
70
136
  alignItems: "center",
71
137
  gap: "space-8",
@@ -75,7 +141,7 @@ var d = ({ percent: d, detail: f, labels: p }) => {
75
141
  fontSize: "body.sm",
76
142
  color: "grey.500",
77
143
  minW: "80px",
78
- children: p.attributes[r]
144
+ children: _.attributes[r]
79
145
  }),
80
146
  /* @__PURE__ */ a(t, {
81
147
  flex: "1",
@@ -87,8 +153,8 @@ var d = ({ percent: d, detail: f, labels: p }) => {
87
153
  h: "100%",
88
154
  borderRadius: "full",
89
155
  style: {
90
- width: `${c}%`,
91
- background: l(c)
156
+ width: `${l}%`,
157
+ background: f(l)
92
158
  }
93
159
  })
94
160
  }),
@@ -98,23 +164,23 @@ var d = ({ percent: d, detail: f, labels: p }) => {
98
164
  fontWeight: "medium",
99
165
  minW: "40px",
100
166
  textAlign: "right",
101
- style: { color: l(c) },
167
+ style: { color: f(l) },
102
168
  children: [
103
- i,
169
+ s,
104
170
  "/",
105
- s
171
+ c
106
172
  ]
107
173
  })
108
174
  ]
109
175
  }, r);
110
176
  })]
111
177
  }),
112
- m.length > 0 && h.length > 0 && /* @__PURE__ */ a(t, {
178
+ x.length > 0 && S.length > 0 && /* @__PURE__ */ a(t, {
113
179
  h: "1px",
114
180
  bg: "grey.100",
115
181
  mb: "space-8"
116
182
  }),
117
- h.length > 0 && /* @__PURE__ */ o(r, {
183
+ S.length > 0 && /* @__PURE__ */ o(r, {
118
184
  alignItems: "stretch",
119
185
  gap: "space-4",
120
186
  children: [/* @__PURE__ */ a(e, {
@@ -122,11 +188,11 @@ var d = ({ percent: d, detail: f, labels: p }) => {
122
188
  fontSize: "body.sm",
123
189
  fontWeight: "bold",
124
190
  color: "grey.400",
125
- children: p.notSpecified
191
+ children: _.notSpecified
126
192
  }), /* @__PURE__ */ a(n, {
127
193
  flexWrap: "wrap",
128
194
  gap: "space-4",
129
- children: h.map((t) => /* @__PURE__ */ a(e, {
195
+ children: S.map((t) => /* @__PURE__ */ a(e, {
130
196
  as: "span",
131
197
  fontSize: "body.sm",
132
198
  color: "grey.400",
@@ -135,11 +201,11 @@ var d = ({ percent: d, detail: f, labels: p }) => {
135
201
  px: "space-8",
136
202
  py: "2px",
137
203
  borderRadius: "full",
138
- children: p.attributes[t]
204
+ children: _.attributes[t]
139
205
  }, t))
140
206
  })]
141
207
  }),
142
- p.footer && /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ a(t, {
208
+ _.footer && /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ a(t, {
143
209
  h: "1px",
144
210
  bg: "grey.100",
145
211
  mt: "space-8",
@@ -149,12 +215,12 @@ var d = ({ percent: d, detail: f, labels: p }) => {
149
215
  fontSize: "9px",
150
216
  color: "grey.300",
151
217
  style: { lineHeight: 1 },
152
- children: p.footer
218
+ children: _.footer
153
219
  })] })
154
220
  ]
155
221
  });
156
222
  };
157
223
  //#endregion
158
- export { d as ChatScoreBreakdown };
224
+ export { g as ChatScoreBreakdown };
159
225
 
160
226
  //# sourceMappingURL=ChatScoreBreakdown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatScoreBreakdown.js","names":[],"sources":["../../../src/components/chat/ChatScoreBreakdown.tsx"],"sourcesContent":["import { Box, HStack, VStack } from '@stokelp/styled-system/jsx'\nimport { Text } from '~/components'\n\nexport type ScoreAttributeKey =\n | 'origin'\n | 'rawMaterialOrigin'\n | 'temperature'\n | 'cutting'\n | 'cooking'\n | 'treatment'\n | 'label'\n | 'packaging'\n\nexport type ScoreAttributeDetail = {\n matched: number | null\n total: number | null\n}\n\nexport type ChatScoreBreakdownLabels = {\n title: string\n compared: string\n notSpecified: string\n attributes: Record<ScoreAttributeKey, string>\n footer?: string\n}\n\nexport type ChatScoreBreakdownProps = {\n percent: number\n detail: Record<ScoreAttributeKey, ScoreAttributeDetail>\n labels: ChatScoreBreakdownLabels\n}\n\nconst ATTRIBUTE_ORDER: ScoreAttributeKey[] = [\n 'origin',\n 'rawMaterialOrigin',\n 'temperature',\n 'cutting',\n 'cooking',\n 'treatment',\n 'label',\n 'packaging',\n]\n\nfunction getHeadlineColor(percent: number): string {\n if (percent >= 70) return '#3B6D11'\n if (percent >= 40) return '#854F0B'\n return '#A32D2D'\n}\n\nfunction getBarColor(ratioPercent: number): string {\n if (ratioPercent >= 70) return '#639922'\n if (ratioPercent >= 40) return '#BA7517'\n return '#E24B4A'\n}\n\nfunction isComparable(detail?: ScoreAttributeDetail): boolean {\n return detail?.matched != null && detail?.total != null\n}\n\nexport const ChatScoreBreakdown = ({ percent, detail, labels }: ChatScoreBreakdownProps) => {\n const compared = ATTRIBUTE_ORDER.filter(key => isComparable(detail[key]))\n const notSpecified = ATTRIBUTE_ORDER.filter(key => !isComparable(detail[key]))\n\n return (\n <Box\n bg=\"white\"\n borderRadius=\"radius-8\"\n p=\"space-16\"\n minW=\"220px\"\n maxW=\"240px\"\n css={{ '& *': { fontFamily: 'inherit' } }}\n >\n <HStack justify=\"space-between\" alignItems=\"baseline\" mb=\"space-8\">\n <Text as=\"span\" fontSize=\"body.md\" fontWeight=\"bold\">\n {labels.title}\n </Text>\n <Text as=\"span\" fontSize=\"body.lg\" fontWeight=\"medium\" style={{ color: getHeadlineColor(percent) }}>\n {Math.round(percent)} %\n </Text>\n </HStack>\n\n <Box h=\"1px\" bg=\"grey.100\" mb=\"space-8\" />\n\n {compared.length > 0 && (\n <VStack alignItems=\"stretch\" gap=\"space-4\" mb=\"space-8\">\n <Text as=\"span\" fontSize=\"body.sm\" fontWeight=\"bold\" color=\"grey.400\">\n {labels.compared}\n </Text>\n {compared.map(key => {\n const matched = detail[key].matched as number\n const total = detail[key].total as number\n const ratioPercent = total > 0 ? (matched / total) * 100 : 0\n return (\n <HStack key={key} alignItems=\"center\" gap=\"space-8\">\n <Text as=\"span\" fontSize=\"body.sm\" color=\"grey.500\" minW=\"80px\">\n {labels.attributes[key]}\n </Text>\n <Box flex=\"1\" h=\"3px\" bg=\"grey.100\" borderRadius=\"full\" overflow=\"hidden\">\n <Box\n h=\"100%\"\n borderRadius=\"full\"\n style={{ width: `${ratioPercent}%`, background: getBarColor(ratioPercent) }}\n />\n </Box>\n <Text\n as=\"span\"\n fontSize=\"body.sm\"\n fontWeight=\"medium\"\n minW=\"40px\"\n textAlign=\"right\"\n style={{ color: getBarColor(ratioPercent) }}\n >\n {matched}/{total}\n </Text>\n </HStack>\n )\n })}\n </VStack>\n )}\n\n {compared.length > 0 && notSpecified.length > 0 && <Box h=\"1px\" bg=\"grey.100\" mb=\"space-8\" />}\n\n {notSpecified.length > 0 && (\n <VStack alignItems=\"stretch\" gap=\"space-4\">\n <Text as=\"span\" fontSize=\"body.sm\" fontWeight=\"bold\" color=\"grey.400\">\n {labels.notSpecified}\n </Text>\n <HStack flexWrap=\"wrap\" gap=\"space-4\">\n {notSpecified.map(key => (\n <Text\n key={key}\n as=\"span\"\n fontSize=\"body.sm\"\n color=\"grey.400\"\n border=\"1px solid\"\n borderColor=\"grey.100\"\n px=\"space-8\"\n py=\"2px\"\n borderRadius=\"full\"\n >\n {labels.attributes[key]}\n </Text>\n ))}\n </HStack>\n </VStack>\n )}\n\n {labels.footer && (\n <>\n <Box h=\"1px\" bg=\"grey.100\" mt=\"space-8\" mb=\"space-8\" />\n <Text as=\"span\" fontSize=\"9px\" color=\"grey.300\" style={{ lineHeight: 1 }}>\n {labels.footer}\n </Text>\n </>\n )}\n </Box>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ChatScoreBreakdown.js","names":[],"sources":["../../../src/components/chat/ChatScoreBreakdown.tsx"],"sourcesContent":["import { Box, HStack, VStack } from '@stokelp/styled-system/jsx'\nimport { Text } from '~/components'\n\nexport type ScoreSetAttributeKey =\n | 'origin'\n | 'rawMaterialOrigin'\n | 'temperature'\n | 'cutting'\n | 'cooking'\n | 'treatment'\n | 'label'\n | 'packaging'\n\nexport type ScoreNumericAttributeKey = 'grammageGsm' | 'recycledPct' | 'thicknessMicron'\n\nexport type ScoreAttributeKey = ScoreSetAttributeKey | ScoreNumericAttributeKey\n\n/** Proximity bucket for free numeric packaging specs: egal (equal) → non_correspondant (no match). */\nexport type ScoreLevel = 'egal' | 'proche' | 'eloigne' | 'non_correspondant'\n\nexport type ScoreAttributeDetail = {\n matched: number | null\n total: number | null\n level?: ScoreLevel | null\n}\n\nexport type ChatScoreBreakdownLabels = {\n title: string\n compared: string\n notSpecified: string\n attributes: Record<ScoreSetAttributeKey, string> & Partial<Record<ScoreNumericAttributeKey, string>>\n levels?: Record<ScoreLevel, string>\n footer?: string\n}\n\nexport type ChatScoreBreakdownProps = {\n percent: number\n detail: Record<string, ScoreAttributeDetail>\n labels: ChatScoreBreakdownLabels\n}\n\nconst ATTRIBUTE_ORDER: ScoreAttributeKey[] = [\n 'origin',\n 'rawMaterialOrigin',\n 'temperature',\n 'cutting',\n 'cooking',\n 'treatment',\n 'label',\n 'packaging',\n 'grammageGsm',\n 'recycledPct',\n 'thicknessMicron',\n]\n\n// Attributes that only exist for food products; hidden for packaging products.\nconst FOOD_ONLY_KEYS: ScoreAttributeKey[] = ['temperature', 'cutting', 'cooking']\n// Free numeric specs that only exist for packaging products; hidden for food products.\nconst PACKAGING_ONLY_KEYS: ScoreNumericAttributeKey[] = ['grammageGsm', 'recycledPct', 'thicknessMicron']\n\nconst LEVEL_COLORS: Record<ScoreLevel, { bg: string; color: string }> = {\n egal: { bg: '#E6F3D9', color: '#3B6D11' },\n proche: { bg: '#F0F6DC', color: '#5E8516' },\n eloigne: { bg: '#FDF0E0', color: '#C2740C' },\n non_correspondant: { bg: '#FCEBEB', color: '#A32D2D' },\n}\n\nfunction getHeadlineColor(percent: number): string {\n if (percent >= 70) return '#3B6D11'\n if (percent >= 40) return '#854F0B'\n return '#A32D2D'\n}\n\nfunction getBarColor(ratioPercent: number): string {\n if (ratioPercent >= 70) return '#639922'\n if (ratioPercent >= 40) return '#BA7517'\n return '#E24B4A'\n}\n\nfunction hasLevel(detail?: ScoreAttributeDetail): boolean {\n return detail?.level != null\n}\n\nfunction isFraction(detail?: ScoreAttributeDetail): boolean {\n return detail?.matched != null && detail?.total != null\n}\n\nfunction isComparable(detail?: ScoreAttributeDetail): boolean {\n return hasLevel(detail) || isFraction(detail)\n}\n\nexport const ChatScoreBreakdown = ({ percent, detail, labels }: ChatScoreBreakdownProps) => {\n // A packaging product exposes the numeric specs (comparable via level); food attributes\n // then come back fully null and must be hidden entirely, and vice versa.\n const isPackaging = PACKAGING_ONLY_KEYS.some(key => detail[key]?.level != null)\n const hiddenKeys = new Set<ScoreAttributeKey>(isPackaging ? FOOD_ONLY_KEYS : PACKAGING_ONLY_KEYS)\n const visibleKeys = ATTRIBUTE_ORDER.filter(key => !hiddenKeys.has(key))\n\n const compared = visibleKeys.filter(key => isComparable(detail[key]))\n const notSpecified = visibleKeys.filter(key => !isComparable(detail[key]) && labels.attributes[key] != null)\n\n return (\n <Box\n bg=\"white\"\n borderRadius=\"radius-8\"\n p=\"space-16\"\n minW=\"220px\"\n maxW=\"240px\"\n css={{ '& *': { fontFamily: 'inherit' } }}\n >\n <HStack justify=\"space-between\" alignItems=\"baseline\" mb=\"space-8\">\n <Text as=\"span\" fontSize=\"body.md\" fontWeight=\"bold\">\n {labels.title}\n </Text>\n <Text as=\"span\" fontSize=\"body.lg\" fontWeight=\"medium\" style={{ color: getHeadlineColor(percent) }}>\n {Math.round(percent)} %\n </Text>\n </HStack>\n\n <Box h=\"1px\" bg=\"grey.100\" mb=\"space-8\" />\n\n {compared.length > 0 && (\n <VStack alignItems=\"stretch\" gap=\"space-4\" mb=\"space-8\">\n <Text as=\"span\" fontSize=\"body.sm\" fontWeight=\"bold\" color=\"grey.400\">\n {labels.compared}\n </Text>\n {compared.map(key => {\n const entry = detail[key]\n if (hasLevel(entry)) {\n const level = entry.level as ScoreLevel\n const colors = LEVEL_COLORS[level]\n return (\n <HStack key={key} alignItems=\"center\" gap=\"space-8\" justify=\"space-between\">\n <Text as=\"span\" fontSize=\"body.sm\" color=\"grey.500\" minW=\"80px\">\n {labels.attributes[key]}\n </Text>\n <Box px=\"space-8\" py=\"2px\" borderRadius=\"full\" flexShrink=\"0\" style={{ backgroundColor: colors.bg }}>\n <Text\n as=\"span\"\n fontSize=\"body.sm\"\n fontWeight=\"medium\"\n style={{ color: colors.color, whiteSpace: 'nowrap' }}\n >\n {labels.levels?.[level] ?? level}\n </Text>\n </Box>\n </HStack>\n )\n }\n\n const matched = entry.matched as number\n const total = entry.total as number\n const ratioPercent = total > 0 ? (matched / total) * 100 : 0\n return (\n <HStack key={key} alignItems=\"center\" gap=\"space-8\">\n <Text as=\"span\" fontSize=\"body.sm\" color=\"grey.500\" minW=\"80px\">\n {labels.attributes[key]}\n </Text>\n <Box flex=\"1\" h=\"3px\" bg=\"grey.100\" borderRadius=\"full\" overflow=\"hidden\">\n <Box\n h=\"100%\"\n borderRadius=\"full\"\n style={{ width: `${ratioPercent}%`, background: getBarColor(ratioPercent) }}\n />\n </Box>\n <Text\n as=\"span\"\n fontSize=\"body.sm\"\n fontWeight=\"medium\"\n minW=\"40px\"\n textAlign=\"right\"\n style={{ color: getBarColor(ratioPercent) }}\n >\n {matched}/{total}\n </Text>\n </HStack>\n )\n })}\n </VStack>\n )}\n\n {compared.length > 0 && notSpecified.length > 0 && <Box h=\"1px\" bg=\"grey.100\" mb=\"space-8\" />}\n\n {notSpecified.length > 0 && (\n <VStack alignItems=\"stretch\" gap=\"space-4\">\n <Text as=\"span\" fontSize=\"body.sm\" fontWeight=\"bold\" color=\"grey.400\">\n {labels.notSpecified}\n </Text>\n <HStack flexWrap=\"wrap\" gap=\"space-4\">\n {notSpecified.map(key => (\n <Text\n key={key}\n as=\"span\"\n fontSize=\"body.sm\"\n color=\"grey.400\"\n border=\"1px solid\"\n borderColor=\"grey.100\"\n px=\"space-8\"\n py=\"2px\"\n borderRadius=\"full\"\n >\n {labels.attributes[key]}\n </Text>\n ))}\n </HStack>\n </VStack>\n )}\n\n {labels.footer && (\n <>\n <Box h=\"1px\" bg=\"grey.100\" mt=\"space-8\" mb=\"space-8\" />\n <Text as=\"span\" fontSize=\"9px\" color=\"grey.300\" style={{ lineHeight: 1 }}>\n {labels.footer}\n </Text>\n </>\n )}\n </Box>\n )\n}\n"],"mappings":"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"}